Skip to main content
Working with an agent? Give them a link to this page as markdown.

Sessions and trajectories

Watcher records what a coding agent does as a session made of messages. It organizes each session's messages into one or more trajectories. Graders review trajectories. These two terms tell you exactly what a grade covers.

Sessions​

A session is one recorded interaction with a coding agent. For example, a single Claude Code or Codex conversation on a developer's machine. The Watcher client's hooks upload the conversation to your Watcher deployment at the end of each agent turn. A session is visible in the Analyzer while it is still in progress.

Each session records:

  • The messages exchanged: user prompts, assistant replies, tool calls with their results, and the system prompt.
  • Which coding agent produced it, such as Claude Code, Codex or GitHub Copilot CLI. Watcher can recognize agents from native captures or retained SDK adapter IDs. Unrecognized or conflicting identities appear as Other.
  • The developer it belongs to.
  • The hostname of the machine it ran on, as the Watcher client reports it. The Analyzer shows it beside the developer's name on the session page. Sessions recorded without the client, such as through the Aperture gateway, usually have none.

The coding agent is separate from the model it uses. For example, a Copilot session using a Claude model is still a GitHub Copilot CLI session.

Session lists, details and new alerts use the recognized agent name. Agent filters use that same name; Other excludes recognized agents. Older sessions can gain a name when their adapter ID is still retained, even after transcript content is removed. This changes reporting only. Stored identities, ownership, recording and tool rules stay the same. Exports and alerts already delivered elsewhere are not rewritten.

A recognized name does not establish support for every monitoring feature. Check the integration's limits before relying on recording or tool blocking.

A session's transcript does not necessarily include the full conversations of its subagents. See Subagent coverage for the distinction between tool-call review and recorded conversations.

Trajectories​

A trajectory is one linear path through a session's conversation. It is an unbroken chain of messages, in order, ending at one of the session's latest messages. Most trajectories start at the session's first message. A restarted chain starts where the restart happened. Its trajectories do not include the messages before it.

A straightforward conversation produces a single trajectory. A session contains more than one trajectory when its message history stops being a single chain.

Three cases produce multiple trajectories:

  • Branching. The developer rewinds the conversation and continues from an earlier point. Watcher keeps both branches, each as its own trajectory. They share the messages up to the branch point.
  • Restarted chains. The agent restructures its own message history mid-session. For example, context compaction or a change of system prompt. This starts a new chain in the same session. One exception: when a Codex conversation compacts, or an OpenCode conversation captured through Aperture does, and Watcher already holds earlier turns of the same conversation (the same Codex thread ID, or the same OpenCode session ID and system prompt), Watcher continues the existing chain. That trajectory keeps the earlier turns, then the agent's summary, a compaction notice and the turns after it. This applies to Codex transcripts from the Watcher client and to Codex traffic captured through Aperture. See Gateway hooks.
  • Two recorders. The Watcher client and the Aperture gateway hook can both record the same Claude Code or Codex conversation. The client records the agent's own transcript. Aperture records the requests the agent sent to the model. Both name the conversation by the agent's own conversation ID, so Watcher stores both copies in one session, whichever recorder is first. The two copies never share a message, so the session has separate trajectories for each recorder. Trailing review grades only the latest trajectory, which can come from either copy. Aperture attributes its copy to the developer its login identity names. The session belongs to that developer when the client signs in as them or uses an organization API key. When the client signs in as a different developer, the session belongs to whichever recorder was first, and Watcher rejects the other recorder's copy. A rejected Watcher client stops uploading that conversation and tells the developer once (see How sessions reach your deployment).

Within one organization, a Claude Code or Codex conversation ID names one session. An upload is attributed to a developer when the client signs in as them, or when Aperture's login identity names them. Three rules decide who owns the session:

  • An upload attributed to a developer joins a session that belongs to no one, and that developer becomes its owner. Sessions created with an organization API key belong to no one.
  • An upload attributed to no developer, such as one sent with an organization API key, joins the session and leaves its owner unchanged.
  • An upload attributed to one developer is rejected when the session belongs to another developer. POST /v1/ingest answers 409 with code session_owner_conflict and stores nothing from that trajectory. Earlier trajectories in the same request stay stored, and later ones are not processed. Aperture skips such captures.

Two consequences matter when you choose credentials:

  • An API key with only sessions:ingest can add turns to an existing conversation's session, whoever owns it, when it sends that conversation's Claude Code session ID or Codex thread ID. It still cannot read any session. Treat such a key as able to append to any member's conversation whose ID it knows, and revoke it if it leaks. A sessions:write:any key that names no developer could already append to any session.
  • A signed-in developer whose upload takes over a session that belonged to no one can then read everything already stored there, including turns another recorder uploaded with an organization API key.

Sessions in other organizations are never matched.

A fork that gets its own conversation ID is not a branch. For example, Claude Code's --fork-session copies a conversation under a new session ID. When the fork shares no conversation ID with the original, Watcher records it as a separate session that starts with a copy of the shared history. The original conversation keeps its session and ID.

What a grade covers​

Graders review a trajectory, not a whole session. Each grade attaches to one specific trajectory. The last message the grader saw identifies it. A session accumulates grades over time. They come from different graders, from different models, and from re-grading the same trajectory.

While a session runs, review happens in two ways:

  • Blocking review evaluates a tool call before it runs. It can allow it, deny it, or escalate it to a human.
  • Trailing review grades the session's latest trajectory on the server. This happens as the session is ingested. It settles with a final pass once the session goes quiet.

Tool-call decisions​

A tool call's record separates Watcher's evaluation from what happened on the developer's machine. An evaluation includes Watcher's judgment, the policy settings used, and any grades. The outcome records the human response and whether the tool completed, was blocked, or was cancelled.

For example, a gateway's Watcher integration can allow a command. Then the developer can reject it in the coding agent's own permission dialog. The record keeps both facts. Watcher allowed the call, and the human rejected it. An allow judgment alone does not prove that the command ran.

CaseWhat the record shows
1b. The client reports a human rejection without evaluating the call itselfRecord the supplied human rejection and execution status. Keep the gateway's evaluation and linked grades.
1c. The human rejection arrives before the gateway evaluationAdd the evaluation when it arrives and keep the reported human and execution outcomes.
1d. The client repeats an outcome or omits an outcome it already reportedRepeated values leave the record unchanged. Missing human fields or a pending execution status do not erase known outcomes.
1e. The gateway's evaluation failed or produced only some gradesAn outcome-only client report still preserves that evaluation and its available evidence.
1f. The client performs its own evaluationIts upload can replace the stored evaluation. A grading failure counts as an evaluation attempt too.

Observe mode records a judgment without applying it. A later hook observation can fill a missing judgment. It can also improve a failed evaluation that applied no action. It preserves completed judgments and decisions Watcher already applied.

The same tool-call ID can occur on multiple trajectories within a session. Each trajectory containing that ID shows the shared decision and its linked grades. A trajectory without the call does not show its record.

Prefer one monitoring integration per conversation. A gateway integration and the Watcher client might both evaluate a call. They may repeat grading. Watcher keeps one current decision per tool call in each session. A gateway that sends no Claude Code or Codex conversation ID can record the conversation as a separate session. Each session then keeps its own decision and grades. Linked grades can come from different attempts. Watcher does not keep a separate decision history for each integration. Conflicting human outcomes use upload arrival order. A late upload can replace a newer human response.

Counting messages​

Session lists show a session's message count. This is the length of its longest trajectory, not the total number of stored messages. A heavily branched session holds more messages than its count shows.