Skip to main content

Review the audit trail

Do this to answer “who changed this, when, and from where” about configuration: agents, skills, MCP servers, triggers, tasks and policy rules. The audit log records the change, the actor, the request context and a field-level diff. Do not use it to reconstruct what an agent did while running. Tool calls, denials, budget warnings and approvals are in the task event stream, not in the audit log — the two are separate stores. If your question is about a run, skip to the second trail.

Prerequisites

  • An authenticated session in the workspace you want to inspect. Reads are workspace-scoped, and any authenticated member can read their workspace’s full audit log.
  • Read audit for what each trail covers.
Examples assume API=http://localhost:8000 and a bearer token in $TOKEN.

Steps

1. Read the most recent events

Events come back newest first. The default page is 50 and the maximum is 100.

2. Narrow with filters

All filters combine, and all are optional.
The action names are hierarchical and follow <resource>.<verb>. The set written today is agent, skill, mcp_server, mcp_instance and trigger create/update/delete, task.create, and governance_policy.create, .update, .set_enabled and .delete.

3. Page through a long window

Pass the previous response’s next_cursor as cursor:
The cursor is the id of the last event on the previous page; the next page returns events strictly older than it.

4. Read what actually changed

Update and delete events carry a changes array of {field, before, after}:
created_at and updated_at are excluded from diffs. Create events have no diff, because there is no before-state. source_ip comes from X-Forwarded-For when present and the direct client otherwise, so it is only as trustworthy as your proxy configuration. request_id is the inbound X-Request-ID header, or a generated UUID when the caller did not send one — send your own to correlate with upstream logs.

Verify

Make a change you can predict, then find it:
The top event should be governance_policy.create with your user id as actor_id, timestamped within seconds of the call.

When the audit log has no answer

Several things are deliberately or accidentally absent. Knowing which is which saves an investigation. Runtime governance decisions are not there. No tool call, policy denial, budget denial or approval produces an audit row. The pipeline’s audit observer is registered but constructed without an event sink, so it logs at debug level and writes nothing. Use the task event stream instead:
That stream carries tool.call, tool.result (including denied_by_policy), llm.call.*, approval.request, approval.response, BudgetWarning, BudgetExceeded and the terminal states. Authorization grant changes are not there. Writing or revoking a relationship through /v1/access-control/relationships, and the owner grants written automatically when a resource is created, produce no audit event. Who has access is recorded in the graph itself — read it with GET /v1/access-control/relationships and POST /v1/access-control/resolve. API key lifecycle is not there. Creating and deleting keys under /v1/api-keys/ writes no audit event.

Troubleshooting

An action you expected is missing. Check it is one of the covered actions above. Beyond coverage, two mechanics drop events silently: the decorator skips auditing when the service has no repository factory, and it catches and logs failures from the audit write so the mutation still succeeds. A warning in the API log reading Failed to record audit event for <action> is the signal. actor_type says user for an API-key call. The column exists to distinguish user, service, system and api_key, but no call site sets it, so everything is recorded as user with the resolved user id. You cannot tell interactive from programmatic activity from this field. Use user_agent and source_ip as a weaker proxy. limit=500 returned 100 rows. The parameter is validated to 1-100 and the repository clamps it again. Page with cursor. Events stop at a certain date. There is no retention or archival job in core, so this is not expiry — check whether the workspace filter is what you expect. Reads are scoped to the caller’s current workspace, and switching workspaces changes the result set entirely. You need the log somewhere else. Core has no export endpoint. Enterprise deployments can register an audit_sink extension that receives every recorded event for SIEM or object-storage delivery; a forwarding failure is logged and does not fail the write. You are relying on immutability. The repository only inserts and reads, and the table is documented as append-only, but nothing in the application enforces that. Grant only INSERT and SELECT on audit_events at the database level if the guarantee matters.