Skip to content

Audit Log

Activity page — chronological audit log with filter chips (decision: deny, action: chat.tool.execute) and per-entry expansion.

Every policy decision and every sensitive action in Agentcy produces an audit entry. The log is append-only, cryptographically ordered (monotonic seq per org), and retained for POLICY_AUDIT_RETENTION_DAYS (default 90).

What gets audited

CategoryExamples
Policy decisionsallow / deny on every middleware + tool call evaluation
Authlogin success/failure, API key create/delete, JWT validation errors
Connectorssource create/update/delete, OAuth token rotation, sync failures
Chatapproval decisions, tool call outcomes, conversation PATCH
Channelsbinding create/update/delete, credential rotation, test runs
Platform adminorg create, invite, role change, plan change
Memorymemory create/update/delete (writes; recalls optional)

Everything else (node reads, pagination, search) is not audited — volume would drown the log.

Shape

json
{
  "id": "aud_01HABCDE",
  "seq": 14201,
  "ts":  "2026-04-24T12:33:21.123Z",
  "org_id": "org_…",
  "user_id": "usr_…",
  "api_key_id": null,
  "category": "policy",
  "action": "chat.tool.execute",
  "decision": "deny",
  "resource": { "connector": "kubernetes", "tool": "delete_pod", "tool_effect": "destructive" },
  "policy_id": "pol_…",
  "rule": "agentcy.connectors.deny",
  "input":  { /* full Rego input */ },
  "context":{ "ip":"10.0.0.7","request_id":"req_…","latency_ms":12 }
}

Read

bash
# Last 100 deny decisions
curl "http://…/policies/audit?decision=deny&limit=100" \
  -H "authorization: Bearer $TOKEN" | jq

# Stream new entries (SSE)
curl -N "http://…/policies/audit/stream" \
  -H "authorization: Bearer $TOKEN"

# One entry in detail
curl "http://…/policies/audit/aud_…" -H "authorization: Bearer $TOKEN" | jq

Filters:

  • decision=allow|deny
  • action=<string> (exact match on full action path like source.sync)
  • connector=<name>
  • user_id=<id>, api_key_id=<id>
  • since=<RFC3339>, until=<RFC3339>
  • resource.tool=<name>

Export

bash
# CSV of the last 7 days of denies
curl "http://…/policies/audit.csv?decision=deny&since=$(date -u -v -7d +%Y-%m-%dT00:00:00Z)" \
  -H "authorization: Bearer $TOKEN" -o denies-last-7d.csv

# NDJSON stream of all entries for a month
curl "http://…/policies/audit.ndjson?since=2026-04-01&until=2026-05-01" \
  -H "authorization: Bearer $TOKEN" > april.ndjson

Forward to a SIEM

Agentcy can push every audit entry to an external collector. Configure a sink:

bash
curl -X POST http://…/policies/audit/sinks \
  -H "authorization: Bearer $TOKEN" -H 'content-type: application/json' \
  -d '{
    "kind":"http",
    "url":"https://siem.internal/ingest",
    "secret":"whsec_…",
    "filter":{"decision":"deny"}
  }'

Supported sinks: http (HMAC-signed like outbound webhooks), s3 (NDJSON rolled hourly), loki.

Retention

POLICY_AUDIT_RETENTION_DAYS controls local retention. For longer archive, pair short local retention with an s3 sink:

env
POLICY_AUDIT_RETENTION_DAYS=30

then push to S3 and Glacier-tier it.

Integrity

Entries are append-only at the DB level: the policy_audit_log table has no UPDATE or DELETE grants in the API's Postgres role. Admin deletion (for retention) goes through a cron job with a separate role.

Each entry includes prev_hash = SHA-256(prev_hash_prev || id || seq || ts || action || decision). Gaps in the chain (e.g. manual tampering) can be detected by replaying hashes.

Verify:

bash
curl "http://…/policies/audit/verify?from=aud_A&to=aud_B" \
  -H "authorization: Bearer $TOKEN"
# -> { "status":"ok", "entries":14201, "hash_chain_valid":true }

Performance

Writes are batched; a burst of 10k denies/second is handled without blocking the request path (mpsc channel → dedicated writer task). Reads use compound indexes on (org_id, ts DESC) and (org_id, decision, ts DESC).

Typical latency impact of enabling audit: < 1 ms per request.

Turning audit off

You can't turn off core audit. You can reduce verbosity:

env
POLICY_AUDIT_ALLOWS=false     # default true; set false to only log denies
MEMORY_AUDIT_RECALLS=false    # default false; set true to audit memory recalls

Gotchas

  • PII lives in the log. Inputs include tool args, which may include customer data. Apply retention policy to the sink too.
  • User deletion doesn't purge audit. That's by design — security events must outlive the user. The log keeps the user_id as an opaque string after the user row is gone.
  • Clock drift. Entries use the API server's clock. Sync NTP.

Next

Built by AgentcyLabs. For in-house deployment or Agentcy Cloud (PaaS) access, visit agentcylabs.com.