---
description: >-
  How Watcher organizes recorded coding-agent activity: sessions, the
  trajectories inside them, and which messages a grade applies to.
---

> Part of the Watcher docs. Index of all pages, with summaries: https://watcher-docs.apolloresearch.ai/llms.txt
> Before upgrading Watcher, read the release pages and full changelogs for every version crossed: https://watcher-docs.apolloresearch.ai/releases/index.md
> Found an issue with this page? Report it: https://github.com/ApolloResearch/watcher-bin/issues/new?title=Docs%20feedback%3A%20Sessions%20and%20trajectories&body=Page%3A%20Sessions%20and%20trajectories%0AURL%3A%20https%3A%2F%2Fwatcher-docs.apolloresearch.ai%2Fconcepts%2Fsessions-and-trajectories%2F%0A%0A

# 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](graders.md) 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](../supported-agents.md) conversation on a developer's
machine. The [Watcher client](../client/index.md)'s hooks upload the
conversation to your Watcher deployment at the end of each agent turn. A session
is visible in the [Analyzer](analyzer.md) 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 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](../integrations/index.md) before relying on
recording or tool blocking.

A session's transcript does not necessarily include the full conversations of
its subagents. See [Subagent coverage](subagent-coverage.md) 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 captured through Aperture compacts and Watcher already holds
  earlier turns with the same Codex thread ID, Watcher continues the existing
  chain. That trajectory keeps the earlier turns, then Codex's summary, a
  compaction notice and the turns after it. See
  [Gateway hooks](../api/hooks.md#configure-aperture).
- **Two recorders.** The [Watcher client](../client/index.md) and the Aperture
  [gateway hook](../api/hooks.md) 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](../client/index.md#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](blocking-and-trailing-review.md) evaluates a tool call
  before it runs. It can allow it, deny it, or escalate it to a human.
- [Trailing review](blocking-and-trailing-review.md) 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.

| Case | What the record shows |
| --- | --- |
| **1b. The client reports a human rejection without evaluating the call itself** | Record the supplied human rejection and execution status. Keep the gateway's evaluation and linked grades. |
| **1c. The human rejection arrives before the gateway evaluation** | Add 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 reported** | Repeated 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 grades** | An outcome-only client report still preserves that evaluation and its available evidence. |
| **1f. The client performs its own evaluation** | Its upload can replace the stored evaluation. A grading failure counts as an evaluation attempt too. |

[Observe mode](blocking-and-trailing-review.md#enforcement-modes) 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.
