Coverage statement
Which of an agent's actions a run's locked record holds, which path recorded each one, and which actions no path sees.
After an incident, the first question about a run's record is what it could have seen. A record that misses the network calls a shell command made is still accurate, as long as it says it missed them. Read this statement beside a run's export bundle and its oxagen verify result.
The record and its lock
A run's record is a chain of frames, one per action. Each frame carries a hash: a short code made from the frame's content. Any change to the content gives a different code. Each frame's hash covers the hash of the frame before it, so a frame that is removed, inserted, or edited breaks the chain from that point on.
Locked means any change after this point shows. Every 60 seconds, the agent's computer locks each chain that has new frames, with a key that stays on that computer. When an operator exports a run, Oxagen locks the bundle with its own key. The record is tamper-evident: a change after a lock shows when anyone checks it. A lock does not stop a change, and it cannot add an action the record never held.
Two recording paths
Both paths run on the agent's computer, inside the oxagen recorder.
- The recorder receives the hooks the agent's harness fires: Claude Code, Codex, Cursor, or Stella. A hook reports one step, such as a prompt, a tool call, or the end of a turn.
- The gateway is a local proxy inside the recorder. It stands between the harness and its model vendor, and between the harness and the workspace's Oxagen tools. No Oxagen server sits on that path.
The record is what the agent's computer sent. Oxagen checks that every frame chains to the one before it, re-checks each body it receives for credentials, and sets the run's enforcement tier from what it observed, never from what the computer claims. It cannot see an action the computer never reported.
What the gateway records
The gateway carries model calls for Claude Code, Codex, and Stella. Cursor's model calls do not pass through it.
| Action | Frame | What the frame holds |
|---|---|---|
| A model call the gateway forwarded | llm_call | The usage the vendor reported, the hashes of the request and the response, the latency, and the status. The request and response bytes are kept as the frame's body when the workspace keeps that content. |
| A model call the gateway refused, for a budget, a model the agent may not use, a paused run, or a suspended computer | policy_decision | The decision and its reason. |
| A model call the gateway answered itself without forwarding it | error | Marked as not forwarded. |
| A call to one of the workspace's Oxagen tools | tool_call | The tool's name, status, and duration, and the hashes and sizes of its input and output. |
| An Oxagen tool call the gateway refused | policy_decision | The decision. The reason is kept as a hash only. |
- A request or response over 1 MiB is not kept, and the frame says which one is missing.
- A run reaches the
gatewaytier only when the gateway carried at least one of its model calls. A gateway address written in a settings file does not count.
What the recorder records
| Harness step | Frame |
|---|---|
| The session starts | agent_start |
| The person sends a prompt | turn_start, with the prompt's hash and length |
| The agent asks to run a tool | policy_decision, then tool_requested, or token_denied when Oxagen refuses it |
| The harness asks the person to approve a tool | approval_request |
| The harness refuses a tool on its own | harness_permission |
| A tool finishes | tool_call, with the hashes and sizes of the input and output and the exit code, plus a command, file_io, or network frame for what the tool was asked to do |
| A turn ends | turn_end, with the agent's last message of the turn |
| A subagent starts or stops | subagent_start, subagent_stop |
| The session ends | agent_stop, with the reason and the outcome |
The recorder also reads these:
- Claude Code's transcript and Codex's rollout file. It records each model call's usage from them as an
llm_call, and each Codex tool call, because Codex does not fire its tool hook for every tool. - The session's git worktree. After a turn, at most once every 15 seconds and once more when the session ends, it records every path git reports as changed, with line counts and the session's commits (
oxagen:worktree_reconciled).
Content and redaction
A frame carries hashes, not text. The text of a prompt, a tool's input and output, a message, or a model exchange is kept as a separate body only when the workspace keeps that kind of content. Under the digest_only retention mode, no body is kept, and the run's record says so. The agent's computer removes credentials before it hashes a body, and records each removal by kind and by the hash of what it removed. Oxagen checks every body again when it arrives.
An export bundle never carries a body. It names each body by its hash, so anyone who holds the body can check it against the record.
By harness
| Harness | Model calls | Hooks | A policy that asks a person |
|---|---|---|---|
| Claude Code | Gateway and transcript | Every step above | The harness asks the person |
| Codex | Gateway and rollout file | Every step above except the harness's own refusal and a failed tool's own hook | Refused, with the reason |
| Cursor | None | Every step above except the approval prompt and the harness's own refusal | Refused, with the reason |
| Stella | Gateway | Every step above except the approval prompt, the harness's own refusal, a failed tool's own hook, and the session's end, which the recorder takes from the process exiting | Held for a person's approval |
What neither path sees
- Cursor's model calls. A Cursor run holds no
llm_callframe, never reaches thegatewaytier, and its spend is not metered. Its replies come from the harness's response hook. - The agent's reasoning text. Claude Code's thinking is recorded as a token count only. Cursor's thoughts are not recorded.
- What a tool's own process does. The
command,file_io, andnetworkframes come from the tool's name and input. A network call, a child process, or a file a shell command touches on its own is not recorded. - Actions outside the harness. A command typed in another terminal, or another program on the computer, is not recorded. A file it changes inside the session's worktree shows up only as a changed path, with no author and no content.
- Files outside the session's git worktree, and files git ignores.
- MCP servers the harness calls directly. These reach the record only through the harness's own hooks, as tool calls. The gateway does not carry them, so their input and output are only what the harness reported.
- Calls on Oxagen's hosted MCP servers. Oxagen meters each one in a separate log, outside the run's chain, so the bundle does not hold them.
- A Codex tool call whose hook did not fire. The rollout file records it after it ran, with no policy decision before it.
- Steps while the recorder was down. A Claude Code hook that runs as a command waits on disk, up to 10,000 of them, and is recorded when the recorder starts again. Past that limit it is not kept, and no gap is recorded. The hooks Claude Code posts over HTTP, which include every finished tool call, are lost while the recorder is down. The record carries a
daemon_downgap for each session that was running then. - A call the gateway cannot tie to a session. It is recorded on the computer's own chain, which is not part of the run's record or its bundle.
- A session with no hooks. The recorder notices a Claude Code session that runs with its hooks missing or removed, but records that on the computer's own chain, outside every run's record.
- Anything before the recorder was enrolled, or before the session started.
oxagen agent backfillcan rebuild a finished session from its transcript. Every backfilled frame is marked as backfilled. - Anything after the chain ends. A session that crashes ends with an unobserved tail, and the record says so.
How the record shows a gap
When the recorder knows it missed something, it writes a telemetry_gap frame with the cause. When the run ends, Oxagen lists the record's gaps from the chain's own counts, such as a broken chain, an unobserved tail, missing bodies, or digest_only. The agent's computer can add a gap but cannot remove one.
The replay grade says how much of the run can be replayed, from least to most: inspect, view, fork, retry. Any gap holds the run at inspect. No harness reports a run that can be reproduced, so a run on a customer's computer never reaches retry.
The enforcement tier says how much of the run Oxagen could enforce, from most to least: contained, gateway, harness, observe. Oxagen sets it from what it observed.
How a run ends
- The harness ends the session. The recorder records
agent_stopand locks the chain. - The harness goes away. When the harness process exits, or a session is quiet for 6 hours (1 hour for Cursor), the recorder ends it as
crashedwith an unobserved tail. - The run goes quiet. After 12 hours with no frame, Oxagen closes the run with the outcome
unknown. The next frame reopens it. A closed run like this cannot be exported until the computer ends it. - An operator ends it.
seal_runlocks the run's record and stops its agent.
A live run cannot be exported. The oxagen verify reference covers checking a bundle once a run is locked.