Skip to main content
Trinity
Architecture/Audit Trail

Audit Trail

Append-only log of administrative actions across the platform. Records who did what for compliance, incident investigation, and security monitoring.

Concepts

TermDescription
Event typeCategory of action (agent_lifecycle, authentication, etc.)
ActorWho performed the action (user, agent, mcp_client, system)
CredentialWhich key acted, when the call was authenticated with an MCP API key — its id, name and scope, recorded beside the owner
TargetWhat was affected (agent, user, schedule, etc.)
SourceWhere the action originated (api, mcp, scheduler)
Hash chainOptional SHA-256 chain over consecutive entries — proves the log wasn't tampered with between two checkpoints

What Gets Logged

Event TypeActions
agent_lifecyclecreate, start, stop, delete, rename, recover
authenticationlogin_success, login_failure, logout
authorizationpermission_granted, permission_denied
configurationsettings_changed, quota_updated
credentialsinjected, exported, imported
mcp_operationtool_call — every MCP tool call, captured via a transparent wrapper, including calls the tool refused
git_operationsync, pull, push
systemstartup, shutdown, migration

All entry-emitting code paths now write to the audit log; the earlier “Phase 1 coverage” caveat no longer applies.

An action taken with an MCP API key is recorded against its owner (actor_type stays user, since the owner is the accountable party) and against the key: mcp_key_id, mcp_key_name and mcp_scope say which credential acted. That is what makes “what did that leaked key touch?” answerable — filter the list on mcp_key_id. A browser session records no key fields.

Refused MCP Calls

An MCP tool that refuses a call — for example chat_with_agent, fan_out or run_agent_loop naming an agent the caller has no permission for — still answers the caller, and its mcp_operation entry records the refusal. The entry's details read success: false and denied: true, with the reason in error:

{
  "tool": "chat_with_agent",
  "duration_ms": 32,
  "success": false,
  "error": "Permission denied: Agent 'agent-a' is not permitted to communicate with 'agent-b'. Configure permissions in the Trinity UI.",
  "denied": true
}

A backend 403 that surfaces through a tool is marked denied too. An error that is not a refusal carries no deniedflag, so you can tell a refused call from a broken one. Where the caller only sees a generic “not found or not accessible” (for example a loop id), the entry carries the specific reason.

How It Works

Dashboard

Admins get a searchable dashboard at Enterprise → Audit Log in the UI. The dashboard is part of the OSS bundle but is gated by an auditentitlement on the route — instances without the entitlement bounce to the dashboard catalogue.

The dashboard surfaces:

•Stats tiles — total events in window, distinct event types, distinct actors.
•Time presets — Last 1h, 24h, 7d, 30d, All time. Manual edits flip the preset to “custom”.
•Filters — event type, actor type, actor id, target type, target id, source. Drop-down options come from live /distinct/event-types and /distinct/actor-types endpoints, so the UI never goes stale.
•Heatmaps (collapsible card with two tabs): Weekly pattern — day-of-week × hour-of-day grid showing when activity happens; Calendar — GitHub-style per-day grid showing when in calendar time.
•Paginated table with inline drill-down — click any cell to apply that value as a filter.
•Hash-chain verify badge — runs a SHA-256 verification over the currently visible window and reports whether the chain is intact.
•Export — CSV or JSON download of the current filter window.

Retention

Audit entries are kept for 365 days by default (AUDIT_LOG_RETENTION_DAYS). A daily background job at 04:15 UTC prunes entries older than the retention window. The retention floor is 365 days — the database trigger refuses DELETE on younger rows.

For Agents

API Endpoints: see the Backend API Docs (/docs) for full schemas. All endpoints are admin-only.

EndpointMethodDescription
/api/audit-logGETList entries (filterable, paginated)
/api/audit-log/{event_id}GETSingle entry by UUID
/api/audit-log/statsGETAggregate counts by event_type and actor_type
/api/audit-log/heatmapGETDay-of-week × hour-of-day grid
/api/audit-log/calendarGETPer-day activity grid
/api/audit-log/distinct/event-typesGETSorted unique event types (for filter dropdowns)
/api/audit-log/distinct/actor-typesGETSorted unique actor types
/api/audit-log/exportGETCSV or JSON export of a time window
/api/audit-log/verifyPOSTVerify SHA-256 hash chain over an id range
/api/audit-log/hash-chain/enablePOSTToggle hash chain computation for new entries

Common query parameters (apply across list, stats, heatmap, calendar, export):

•event_type, actor_type, actor_id, target_type, target_id, source
•start_time, end_time (ISO 8601)
•limit (default 100, max 1000), offset

The list endpoint also takes request_id (joins the MCP and backend rows of one call), mcp_key_id, and mcp_scope (user, agent, system, connector, portal_delegate, ops).

Entry Format

{
  "event_id": "550e8400-e29b-41d4-a716-446655440000",
  "event_type": "agent_lifecycle",
  "event_action": "create",
  "actor_type": "user",
  "actor_id": "42",
  "actor_email": "admin@example.com",
  "mcp_key_id": null,
  "mcp_key_name": null,
  "mcp_scope": null,
  "target_type": "agent",
  "target_id": "my-agent",
  "timestamp": "2026-05-14T10:30:00Z",
  "source": "api",
  "endpoint": "/api/agents",
  "request_id": "9a7c…",
  "details": { "template": "github:Org/repo" }
}

Data Integrity

The audit log is append-only at the database level:

•No updates — entries cannot be modified after creation. Enforced by an unconditional SQLite trigger.
•No deletes within the retention window — the SQLite trigger refuses DELETE on rows newer than 365 days.
•Hash chain — when enabled, each entry stores previous_hash and entry_hash; the verify endpoint walks the chain and reports the first broken link.