Getting started
Audit log
What the audit trail records, filtering it by group, primitive, action, actor and time, and retention per plan.
Every change to a primitive is recorded in an audit trail you can read for the whole workspace, one group or one primitive. Entries are written in the same transaction as the change they record, so the trail never drifts from the data.
What's recorded
| Area | Actions |
|---|---|
| Groups | group.create |
| Primitive lifecycle | primitive.create, primitive.delete, primitive.pause, primitive.resume, primitive.export, snapshots, restore and fork |
| Database | db.exec, db.batch (the SQL text, never parameter values) and branch create, promote and discard |
| Filesystem | file writes, moves and deletes |
| Queue | sends, acks, retries, dead letters, purges and topic publishes (never message bodies) |
| Memory | remember, forget and supersede |
| Vault | set, delete, grant and revoke, every use (vault.use with host, method, path and status) and every refusal (vault.denied) |
Reads are not audited. Secret values never appear in an entry.
Each entry is an AuditEntry:
{
"id": 42,
"ts": 1760600000000,
"group": "agent-42",
"primitive": "db",
"primitive_id": "01jbz3k8m5q2w7x9v4n6t0r1ce",
"instance": "orders",
"actor": "key:01k…",
"action": "db.exec",
"target": null,
"detail": { "sql": "insert into tasks …" }
}primitive is "group" for group-level events such as group creation, and then primitive_id and instance are null. Every event about a primitive, including lifecycle events such as create, pause, snapshot, fork and move, carries that primitive's type, primitive_id and instance name. actor is who did it: a dashboard user's id, key:<id> for an API key, key:oag_<id> for an MCP client signed in with OAuth, local for primitives dev.
Reading the trail
// The whole workspace, newest first
const { data, next_cursor } = await p.audit({ limit: 50 });
// Filters combine
await p.audit({ group: "agent-42", type: "db", action: "db.", since: Date.now() - 86_400_000 });
// One group, one primitive
await p.group("agent-42").audit({ actor: "key:01k…" });
await orders.audit({ action: "db.exec" });
// Every page
for await (const entry of p.auditAll({ group: "agent-42" })) console.log(entry.action);page = await p.audit(group="agent-42", action="db.", limit=50)
async for entry in p.audit_all(type="vault"):
print(entry.action, entry.target)
trail = await orders.audit(action="db.exec")| Filter | Meaning |
|---|---|
group | One group |
type | group for group-level events, or db, fs, queue, memory, vault for every event about primitives of that type |
primitive | One primitive by id: its data events and its lifecycle events (create, pause, snapshot, restore, fork, move, branches), never another primitive's |
action | Exact (db.exec) or a prefix ending in . or * (db., db.branch*) |
actor | Exact actor (key:01k…, local) or a prefix ending in * (key:* for every API key) |
since, until | Inclusive bounds in milliseconds since the epoch |
limit, cursor | Page size (up to 200) and the previous page's next_cursor |
The trail includes SQL text and secret-use hosts, so it is account-only: it needs a workspace API key, and scoped keys get 403 forbidden. Reading it is not an operation.
In the dashboard
Audit logs (under Manage) shows the workspace trail with the same filters, kept in the URL so a filtered view can be shared. Each primitive's page links to its own entries, and each group's page to the group's.
Retention
| Plan | Audit log |
|---|---|
| Starter | Not included: the API answers 403 plan_limit |
| Pro | The last 30 days |
| Enterprise | Custom |
On Pro, since is raised to 30 days ago if you ask for more. Self-hosted engines keep the full trail.