Audit log

Who changed what, when, and from where - across permissions, secrets, tokens, releases, agents, and everything an agent did on its own.

Reading it

Workspace settings → Audit, for owners and admins. It is session-authenticated only: a scoped token cannot read the record of what tokens did. Newest first, 50 to a page.

Three filters, and they answer the three questions people arrive with:

FilterQuestion
actoreverything this person or agent did here
actiona dotted prefix - secret for every secret verb, secret.set for one
taskeverything one agent run changed

What gets recorded

Roughly forty verbs, grouped by what they are about:

AreaVerbs
Accessmember.invite, member.remove, collab.remove
Repositoriesrepo.push, repo.visibility, repo.rename, repo.default_branch, repo.delete
Protectionprotection.set, protection.delete
Secretssecret.set, secret.access, secret.rotate, secret.delete
Agentsagent.create, agent.update, agent.gate.allow, agent.remember, agent.token.mint, agent.token.revoke
Identitysso.login, sso.login.denied, sso.provision, sso.deprovision, sso.scim.enable, sso.disconnect
Publishingdoc.publish, doc.share, doc.grant, doc.export, release.create, release.delete
Infrastructurerunner.create, runner.delete, webhook.create, webhook.update, llm.credential.create, workspace.export

Each row carries the actor, the target - acme/web, acme/web#12, a token's name - the IP, and a metadata blob for whatever the columns cannot hold: the ref and sha on a push, the before and after roles on a permission change.

Agent writes

The verbs above are admin and security shaped. An agent that opened forty issues would have left forty issues and nothing you could read as a list of what it had done - so every successful write an agent makes through the API is recorded too, centrally rather than route by route. The next tool anybody adds is covered without somebody remembering to cover it.

Only writes that succeeded. A refused call changed nothing, and the task's own step log already records every attempt with its result. Mixing the two would make "what did this run change" a question you had to filter to answer. Filter by task to get exactly that list.

System actors

Some rows have no person behind them, and they say so rather than guessing at one. A SCIM write records source: "scim" with no actor, because the thing that acted was a directory. A runner reading secrets, and Stripe telling us about a subscription, are the same shape.

Retention

Recording is universal. Every workspace on every plan writes to the audit log from its first day - history a workspace did not pay to record cannot be backfilled when it upgrades.

The plan gates the window, never the existence. Below Business, rows are kept 90 days; Business and Enterprise keep everything, and "everything" is genuinely unbounded rather than a very large number. The sweep runs every six hours, which is as sharp as a boundary expressed in days needs to be.

What it will not tell you

A failed write is not here - the action that did not happen left no row. A rejected push is the clearest case: branch protection refuses it before anything lands, and repo.push is written by the hook that only runs for the pushes that were allowed.

And a logging failure never fails the action. A permission change that succeeded has succeeded; turning a logging hiccup into an error would tell somebody their change did not land when it did, and they would make it again.