oxagenDocs
Security

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.

ActionFrameWhat the frame holds
A model call the gateway forwardedllm_callThe 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 computerpolicy_decisionThe decision and its reason.
A model call the gateway answered itself without forwarding iterrorMarked as not forwarded.
A call to one of the workspace's Oxagen toolstool_callThe tool's name, status, and duration, and the hashes and sizes of its input and output.
An Oxagen tool call the gateway refusedpolicy_decisionThe 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 gateway tier 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 stepFrame
The session startsagent_start
The person sends a promptturn_start, with the prompt's hash and length
The agent asks to run a toolpolicy_decision, then tool_requested, or token_denied when Oxagen refuses it
The harness asks the person to approve a toolapproval_request
The harness refuses a tool on its ownharness_permission
A tool finishestool_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 endsturn_end, with the agent's last message of the turn
A subagent starts or stopssubagent_start, subagent_stop
The session endsagent_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

HarnessModel callsHooksA policy that asks a person
Claude CodeGateway and transcriptEvery step aboveThe harness asks the person
CodexGateway and rollout fileEvery step above except the harness's own refusal and a failed tool's own hookRefused, with the reason
CursorNoneEvery step above except the approval prompt and the harness's own refusalRefused, with the reason
StellaGatewayEvery 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 exitingHeld for a person's approval

What neither path sees

  • Cursor's model calls. A Cursor run holds no llm_call frame, never reaches the gateway tier, 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, and network frames 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_down gap 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 backfill can 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_stop and 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 crashed with 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_run locks 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.

On this page