Audit & undo

Agent platforms need two answers on demand: “what did the agent change and when?” and “put it back.” Weft treats both as first-class API surface.

The audit trail

Every write — API commit, git push, repo create/delete, ref change, token mint — records who acted, a timestamp, and, for commits, your context blob. “Who” is the person when there is one (user:01hx…), whichever credential they reached for, and the token when it acts for nobody (token:01hx…):

{ "context": { "agent_run": "r-42", "prompt": "p-991", "user": "u-7" } }

Query it per repo, per person, per action, or by time:

curl -H "Authorization: Bearer $TOKEN" \
  "https://api.weft.sh/v1/orgs/acme/audit?repo=session-8412&limit=100"
Parameter What it narrows to
repo One repo, by name
user Everything one person did, by user id
principal One exact actor string — user:…, token:…, system:…
action One action, e.g. token.mint
since / until A time window, epoch milliseconds
order asc (default, oldest first) or desc (newest first)
after / before The pagination cursor for that order
limit Up to 1000; 100 by default
format=csv The same rows as text/csv

Paging follows the order you asked for. Reading forwards, the response carries next_after; reading backwards it carries next_before. Feed the cursor back on the next request until a page comes back short:

# Newest first, then the page before it.
curl -H "Authorization: Bearer $TOKEN" \
  "https://api.weft.sh/v1/orgs/acme/audit?order=desc&limit=100"
curl -H "Authorization: Bearer $TOKEN" \
  "https://api.weft.sh/v1/orgs/acme/audit?order=desc&limit=100&before=<next_before>"

format=csv returns the same rows for a spreadsheet or a ticket. Every field is quoted and inner quotes are doubled, so a context blob’s commas stay inside their cell:

curl -H "Authorization: Bearer $TOKEN" \
  "https://api.weft.sh/v1/orgs/acme/audit?format=csv&since=1735689600000" \
  -o activity.csv

A credential scoped to one repo sees only that repo’s trail — filter or no filter, JSON or CSV. That is deliberate: a per-repo CI token satisfies org-level org:read so it can read its own repo’s metadata, and without this it would also be reading the org’s credential inventory. Asking it for a different repo answers 404, the same way a foreign org does.

The log is append-only at the application layer, and batches ship write-once to object storage — the durable record can’t be rewritten, by us or by you.

In the dashboard the same trail is Settings → Activity: newest first, the same filters, and the CSV export behind one button.

Undo primitives

Reset moves a branch pointer — three commits forward, one request back:

POST /v1/orgs/acme/repos/session-8412/reset
{ "branch": "main", "to": "<good-commit>", "expected_head": "<bad-commit>" }

expected_head makes it race-safe: if someone else moved the branch first, you get 409 with the current tip. After a reset, the abandoned commits remain reachable by SHA until garbage collection — auditors can still fetch exactly what the agent did.

Revert appends instead of rewinding — a new commit whose tree undoes the branch head, preserving history:

POST /v1/orgs/acme/repos/session-8412/revert
{ "branch": "main" }

Branch checkpoints cheaply before risky work:

POST /v1/orgs/acme/repos/session-8412/branches
{ "name": "checkpoint-12", "from": "main" }

Tags work the same way (POST /tags, DELETE /tags/{name}).

The wire stays strict

Over git, pushes remain fast-forward-and-create-only — forge semantics for humans and tools. The REST API is the authority that may move refs backwards and delete branches. That asymmetry is deliberate: your product owns undo; git clients can’t accidentally invoke it.