claude-desktop agent do against pipeline X.
It is separate from the Agent Monitor, which is a live, in-memory view of the last 200 MCP tool calls. The audit log is durable, admin-only, and queryable. With the audit log on it covers every platform write (not just MCP). By default delivery is best effort: if the config store cannot keep up, entries can be dropped and are counted. Strict mode refuses a request instead of losing its entry.
Enabling it
Off by default — existing installs are unchanged until you opt in.-
Add the flag to your
.env: -
Recreate the Datris container:
- Open Configuration → Audit Log. With user authentication on, the sub-tab is visible to admins only.
Who is recorded
Every entry names an actor. The type tells you which kind of identity it was:
Agents are identified by their API key, humans by their session. The key’s label is the identity you already see in the API-Keys tab, version history, and the Agent Monitor. The key’s value is a secret and is never written anywhere — not hashed, not truncated.
The key id exists because a label can be revoked and re-issued. Rotating a key keeps the identity (same label, new secret). Revoking
claude-desktop and issuing a new claude-desktop next week is a different key wearing the same name; the id tells them apart. Keys issued before this feature have no id and are attributed by label only.
Actions the Assistant takes for you show up as
assistant with your username — not as the platform’s internal ui key. The same attribution applies to pipeline and tap version history, so “who changed this?” has the same answer in both places.USE_USER_AUTH=false, USE_API_KEYS=false) there is no identity to record; entries are attributed to anonymous.
What is recorded
With the audit log on: writes, runs, deletes, logins, key and user management, and every denied request (best effort by default; see Strict mode for the complete guarantee). Reads are not recorded by default — they are high-volume and rarely what an audit is about — with one exception: reading a secret is always logged.
Each entry also carries the outcome (
success, failure, denied, or warning for an action that succeeded but needs follow-up, such as a pipeline delete that leaves a Unity Catalog table behind), the HTTP status, how long the request took, the caller’s IP address (first hop of X-Forwarded-For when the UI sits behind a proxy) and user agent, and the name of the resource touched.
To record reads as well — every query, search, and listing an agent runs — set AUDIT_LOG_READS=true in .env and recreate the container. Expect a lot more rows: each query is its own entry, so filter by category when you’re looking for changes rather than activity.
What is never recorded
Passwords, password hashes, API-key values, session cookies, secret values, and credentials embedded in connection strings. Request bodies are not stored. Where an entry does carry extra detail (a role change recordsfrom and to, for example), every field whose name looks like a credential is masked before it is written. With user authentication on, entries made through the Configuration assistant carry via: config-chat in their detail and are attributed to the signed-in admin.
Reading it
Configuration → Audit Log shows the newest entries first with filters for time range, category, action, actor type, actor, outcome, and resource name. Click a row for the full entry. Export CSV downloads the current filter (up to 50,000 rows); the export itself is recorded as anaudit / export event.
The same data is available programmatically:
since, until (ISO-8601 or epoch millis), category, action, actor (label), actorType, outcome, resource (substring match), limit (default 100, max 1000), and cursor from the previous page’s nextCursor. /api/v1/audit-log/facets lists the values seen in the last 30 days; /api/v1/audit-log/export returns CSV with the same filters.
Reading requires the admin role for a user session, or the audit:read capability for an API key. Full-access keys hold it implicitly; a scoped key must be issued with it. Entries cannot be edited or deleted through the API.
Retention and long-term storage
Entries live in the platform’s MongoDB config store (collection{environment}-audit-log) and expire after AUDIT_LOG_RETENTION_DAYS (default 90). Set it to 0 to keep them until you remove them yourself.
Every entry is also written to the server log on the logger ai.datris.audit, one line per event. Under the production Spring profile that line is a JSON object, so any log aggregator or SIEM already reading the container’s output gets the full audit trail with no extra integration. That stream is the long-term record; Mongo is the queryable window. Turn the mirror off with auditLog.emitLogLine: false if you only want the database copy.
Two Prometheus metrics track health on the existing /actuator/prometheus endpoint: datris_audit_events_total{category,outcome} and datris_audit_dropped_total. By default (best effort) entries are written asynchronously and never slow a request; if the config store cannot keep up, the oldest pending entries are dropped and counted in datris_audit_dropped_total, and the Audit Log tab shows a warning. If you need no entry to be lost, turn on strict mode.
Strict mode
By default the audit log favours availability: a request is never blocked, and an entry that cannot be written is dropped and counted. If you need the opposite trade, a request that cannot be recorded does not happen, set:AUDIT_LOG_STRICT does nothing while USE_AUDIT_LOG is off (datris doctor warns about that combination under audit.strict).
With strict mode on:
- Nothing is evicted. A full queue never pushes an older entry out, and
datris_audit_dropped_totalstays at 0. - A failed write is retried, not skipped. While the config store (MongoDB) is down, the writer retries the same entry with backoff and keeps the rest of the queue in order. When the store comes back, every queued entry is written once, in the order it was accepted.
- Audited requests are refused while the queue is backed up. Once the pending queue reaches its high-water mark (9,000 of 10,000 entries), every request that would be audited (writes; reads too with
AUDIT_LOG_READS=true; secret reads always) gets HTTP 503 with aRetry-Afterheader and a JSON message namingAUDIT_LOG_STRICT, before the request is handled. Tap callbacks and HTTP ingest triggers fail with the same 503. Triggers that do not come over the API wait instead: the MinIO bucket webhook answers 503 and MinIO redelivers the event later (this relies on MinIO’s webhook event queue: the bundledminio-initsetsqueue_dirwith a limit of 1,000 undelivered events, past which MinIO drops events, and a MinIO you configured yourself withoutqueue_dirdoes not redeliver at all), the Kafka consumer stops reading (no offset is committed past an unprocessed message), and scheduled tap runs are skipped until the queue drains, then fire on the next tick. Unaudited reads keep working, andGET /api/v1/audit-log/statusis never refused, so the UI banner and scripts can see the state (that status read is not recorded while the gate is closed). Requests are accepted again as soon as the queue drains. - An entry that still cannot be queued is counted. A request refused by authentication or authorization while the gate is closed is not held: its denial entry is counted straight away. If a request already past the gate waits more than a few seconds for room, its entry is counted in
datris_audit_unrecorded_totaland written whole at ERROR level on theai.datris.auditlogger, so the log stream still has it.
GET /api/v1/audit-log/status reports strict, unrecorded, and acceptingWrites alongside dropped and queueDepth.
Residual risk. Accepted entries wait in an in-memory queue until the writer stores them. A hard kill of the server (out-of-memory kill, docker kill, host crash) loses entries that were accepted but not yet written; the ai.datris.audit log line is emitted when an entry is written, not when it is accepted. A normal stop (docker stop, docker compose stop or down) flushes the queue first. The flush is bounded to 10 seconds, even when the config store hangs rather than fails: entries it cannot store in that time are counted in datris_audit_unrecorded_total (strict mode), and, under strict mode or whenever an entry was left behind, the total is printed to the container’s stderr as audit-unrecorded total=N at shutdown, so it survives the server’s logging shutdown. The Kafka consumer stops before the flush, and an entry submitted after the flush (for example by a tap run still finishing) is refused and counted the same way. Compose gives the server 30 seconds to stop before killing it.
