Data handling
This is the page a data-protection officer reads. It describes the proxy log and run ledger as a dataset: what it contains, where it lives, how long it is kept, who can see it, and how a person’s data is removed from it. Everything here is enforced in code.
What is stored
Section titled “What is stored”Every governed call — model, tool, skill, agent-to-agent — writes one row to the proxy log. A row has two layers:
| Layer | Fields | Covered by the audit hash chain |
|---|---|---|
| Record — who called what, and what was decided | timestamp, proxy type, endpoint, model, tool name, actor and subject ids, guardrail and policy decisions, status, cost, run and turn ids | Yes |
| Content — what was said | request and response body previews, request and response headers | No |
The record layer is what assurance is computed from. The content layer is the tenant’s most sensitive data: prompts, tool arguments, model output. The two are separated on purpose so that content can be redacted, removed or erased without touching what the hash chain attests.
Runs are rollups over proxy-log rows and hold counters, outcomes and hashes, not content. Portal thumbs feedback holds an optional free-text comment. Headers are stored with authorization, x-api-key and cookie values replaced by [REDACTED] before they reach the writer.
What the proxy stores — store_bodies
Section titled “What the proxy stores — store_bodies”Each tenant sets a retention policy under Tenant settings → Data retention (or PUT /v1/admin/tenants/{id} with a data_retention object):
{ "store_bodies": "redacted", "body_days": 30, "log_days": 180, "run_days": 730}store_bodies |
Behaviour |
|---|---|
full |
Bodies stored as received, capped at 64 KiB each. The default. |
redacted |
Every stored body passes through the local PII redactor at write time — independent of whether a PII guardrail is configured for the request. |
none |
No body or header text is stored. Record-layer fields are unaffected. |
The mode is applied in the proxy’s audit writer at flush time, so it holds for every surface — LLM, MCP, skills, A2A — without per-route wiring. The proxy caches the policy for 60 seconds; a change takes effect within a minute.
Whatever the mode, a SHA-256 digest of each body is sealed before it runs and kept on the row, inside the tamper-evident hash. none and redacted therefore still let a recipient who holds the original bytes prove what was sent and returned — the policy decides whether content is stored, never whether what it was can be established. Field by field: The audit row.
How long it is kept — the retention sweeper
Section titled “How long it is kept — the retention sweeper”The three windows are each optional (null keeps forever; 1–3650 days otherwise) and body_days may not exceed log_days:
| Window | What happens after it | Effect on the audit chain |
|---|---|---|
body_days |
Bodies and headers removed from the row; bodies_pruned_at set |
None — the preview text is outside the hash; the content digests (request_content_hash, response_content_hash) stay on the row and inside it |
log_days |
Row deleted | Declared: the removed sequence numbers and the hash at the end of every contiguous run are recorded in the same transaction |
run_days |
Run row and its feedback deleted | None — runs are not chained |
The sweeper runs hourly on the Control Plane and drains at most 5 000 rows per pass per tenant, so a backlog is removed at a bounded rate.
Who can read it
Section titled “Who can read it”Two permissions, deliberately separate:
| Permission | Grants |
|---|---|
proxy:logs:read |
The record layer: rows, filters, statistics, traces |
proxy:logs:read_bodies |
The content layer: stored bodies and headers |
Without read_bodies, the API omits bodies and reports body_state: withheld; body text is also excluded from free-text search so content cannot be probed through filters. Every body read is an admin-audit event. On upgrade, every role that could already read logs is granted read_bodies so nobody loses access silently — remove it from roles that should not see content. The log viewer shows each row’s body state: available, withheld, pruned, erased, or none.
Where it lives
Section titled “Where it lives”The ledger is a table in the tenant’s PostgreSQL. In the self-hosted bundle and in customer-hosted deployments that database is yours; the proxy and control plane connect to it and store nothing elsewhere. Brutor does not receive ledger content.
Data residency governs where upstream requests are routed — which provider regions may serve a tenant — and stamps residency_class on each row. It does not move the ledger; the ledger is wherever the database is.
Erasing a data subject
Section titled “Erasing a data subject”curl -X POST http://localhost:5050/v1/admin/data-erasure \ -H "Authorization: Bearer $ADMIN_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "email": "a.person@example.com", "dry_run": true }'Requires the data:erasure permission and takes any of end_user_id, idp_user_id, email, subject_id. With dry_run: true it returns the counts without changing anything.
Removes
- bodies and headers on every proxy-log row attributed to the subject (
erased_atset); - direct identifiers on those rows: IdP email and username, gateway username, client IP, user agent;
- the subject’s feedback rows;
- the end-user record itself, which cascades to its API keys, memberships and connector identities and nulls its references on usage logs, batch jobs, skill executions and agent ownership.
Keeps
- the record layer of each row and its pseudonymous keys (
gateway_user_id,idp_user_id,subject_id,actor_id).subject_idis covered by the audit hash chain; rewriting it would break the verification the chain exists to provide. Once the end-user record is gone, those keys no longer identify a person (GDPR Recital 26 — pseudonymised data); - the append-only inbox trail: the decision that a finding was reviewed is retained; the finding’s content lives in the rows above and is erased with them.
Every request writes an append-only erasure event carrying a SHA-256 fingerprint of the identifiers named (never the identifiers), the requesting admin, and the counts of what each table gave up. GET /v1/admin/data-erasure lists them.
Ingested telemetry
Section titled “Ingested telemetry”Rows that arrived through the OpenTelemetry inlet (source = otel) carry no bodies, headers or costs: the exporter never sends them and the gateway never had them. Retention, erasure and read permissions apply to those rows exactly as above.
What this page does not claim
Section titled “What this page does not claim”- Redaction is pattern-based. A
redactedstore removes the PII categories the local detector recognises; it does not guarantee that free text contains no personal data. - Erasure covers the gateway’s own stores. Content that reached an upstream provider is governed by that provider’s terms; content in a knowledge base is governed by the KB’s own lifecycle.
- Retention windows are enforced by the Control Plane scheduler; if the Control Plane is down, nothing is deleted until it returns — the failure direction that cannot lose evidence.
Related
Section titled “Related”- Logs & audit trail — the streams themselves
- Compliance — GDPR Article 30 and the other framework tags
- Evidence sources — witnessed versus reported rows

