Skip to main content

Runtime sessions and worktrees

Every ContextRelay project runs at least one runtime session: the live Claude + Codex pair working on your repository. Most of the time you never think about sessions at all - you launch one pair, it operates on the project root, and the shared ledger records everything. This page explains that default, when you might want a second named session bound to a separate git worktree, and exactly what is (and is not) isolated between them today.

The default runtime session

When you start the pair the normal way, ContextRelay uses a single session whose id is literally default:

# Default pair - no session flag needed.
ctxrelay claude
ctxrelay codex

or, to launch both in their own terminals plus the control TUI:

ctxrelay pair

The default session needs no extra setup. It operates on the project root, owns the live message routing between Claude and Codex, and is the target for every ledger read and write that does not name a different session. The daemon creates default during startup, so it always exists.

Instance vs session

A project instance is one checkout plus one daemon, state directory, auth token, and port group. A session is one collaboration context inside that instance - its own participant set, routing state, and registry metadata. Sessions never outlive their instance: stop the instance and its sessions go with it. See Architecture overview for how the daemon ties these together.

Named sessions

A named session is an optional additional collaboration context inside the same instance and daemon. You give it a short, filename-safe id - for example review, debug, or worktree-a - and it tracks its own participants and routing alongside default. Named sessions are the mechanism for running a second Claude + Codex pair that works in parallel without crossing wires with your main pair.

Named sessions are recorded in a durable registry so they survive daemon restarts:

  • .contextrelay/sessions.json (mirrored at .contextrelay/state/sessions.json) holds the session list: id, created/last-seen timestamps, participants, optional label, lifecycle state, and an optional bound worktree path.
  • .contextrelay/state/runtime-sessions/<id>/ holds the per-session runtime state for a launched named pair (for example, the managed Codex TUI metadata).

The registry is project metadata, not daemon process state - runtime attachments such as sockets, PIDs, and ports are rediscovered after a restart.

Enabling named runtime launch

Launching a named runtime (an actual live Claude or Codex attached to a non-default session) is gated behind an explicit environment opt-in, because multi-session live routing is still experimental:

export CONTEXTRELAY_ALLOW_NAMED_SESSIONS=1

Without it, a non-default live launch fails closed with a clear experimental-gate error. With it set, you can join a named pair from either side:

# Named pair, Claude first.
CONTEXTRELAY_ALLOW_NAMED_SESSIONS=1 ctxrelay claude --session review
CONTEXTRELAY_ALLOW_NAMED_SESSIONS=1 ctxrelay codex --session review

# Named pair, Codex first - order does not matter.
CONTEXTRELAY_ALLOW_NAMED_SESSIONS=1 ctxrelay codex --session debug
CONTEXTRELAY_ALLOW_NAMED_SESSIONS=1 ctxrelay claude --session debug

The first --session <id> launch on either side ensures the registry session exists, binds it to the current folder if it is not already bound, starts the daemon-side named runtime/proxy, and attaches the agent. Both --session review and --session=review forms work.

Launching a named pair with ctxrelay pair

ctxrelay pair --session <id> launches a named pair inside the current daemon and injects CONTEXTRELAY_ALLOW_NAMED_SESSIONS=1 into the spawned Claude and Codex commands for you - so on the pair path you do not have to export the variable by hand. You still need it for standalone ctxrelay claude --session / ctxrelay codex --session launches.

Binding a session to a git worktree

A named session can record a worktree path - its folder of operation. This is how you isolate parallel work at the filesystem level: point one session at your main checkout and another at a separate git worktree of the same repository, and each pair edits its own working tree.

ContextRelay does not create worktrees

ContextRelay remembers and validates a worktree path; it never runs git worktree add for you, takes file locks, or otherwise manages git. Create the worktree yourself with git worktree add, then bind a session to it. The binding is advisory metadata plus a launch guard - nothing more.

Create the worktree, then create a session bound to it:

# 1. You create the worktree (plain git - ContextRelay does not do this).
git worktree add ../myrepo-review

# 2. Register a session bound to that path.
ctxrelay session create review --label "PR review pair" --worktree ../myrepo-review

If you omit --worktree, the session binds to the current folder. ContextRelay normalizes the path (realpath) and warns if it does not look like a git worktree root, but it will still record it.

Once bound, named launch commands fail closed when run outside the bound worktree or its subdirectories - a guardrail so a review pair cannot accidentally start in the wrong tree. A shared-worktree guard also blocks launching a named Codex runtime when another launched named session is already bound to the same path; the error names the conflicting session and points you at ctxrelay kill --session <id> or a rebind to recover. The default session bypasses both guards.

Managing the session registry

Use the session subcommand to inspect and manage registry-backed sessions. None of these commands touch git or start a runtime by themselves - they manage metadata and registry focus only:

ctxrelay session list # active sessions (add --archived to include archived)
ctxrelay session create <id> [--label <text>] [--worktree <path>]
ctxrelay session select <id> # change which session the registry treats as focused
ctxrelay session archive <id> # metadata-only; rejects default/active/launched sessions
ctxrelay session rebind <id> [--worktree <path>] # move a session's bound worktree path
ctxrelay session prune # preview orphan-state cleanup (dry-run)
ctxrelay session prune --apply # reclaim it

ctxrelay session list reports each session's lifecycle state, whether Claude/Codex are attached, and its operation path, so you can see at a glance which folder each pair is working in. Add --json to any of these for machine-readable output. A full flag breakdown lives in the CLI command reference.

session prune is the registry's housekeeping: it reclaims runtime-sessions/<id>/ directories that no longer have a registry entry and clears stale named-session Codex runtime hints. It is a dry-run preview unless you pass --apply.

select does not move runtimes

Selecting a session changes the registry's focus for inspection. It does not move participants, relocate ledger entries, or start a runtime - live routing still requires a launched Claude/Codex pair for that session.

The equivalent MCP tools - create_session, select_session, archive_session, and rebind_session - are available to both Claude and Codex, but they are metadata-only: they manage the registry, they do not launch or tear down live runtimes. See the MCP tools reference.

Routing and the current isolation limit

The daemon routes live messages and ledger reads to the correct runtime using a runtimeSessionId tag. Missing or default is the compatibility path and targets the default session; an unknown runtimeSessionId fails closed; and with the named-session gate enabled, a launched named session receives its own live Claude/Codex routing.

Named-session isolation is worktree-level, not fully partitioned ledgers

This is the most important honest limit to understand. In the current slice, named-session ledger reads are scoped and writes are tagged with runtimeSessionId, but transcript storage, viewer focus, finality state, and some backup/autonomy execution state are not yet fully isolated per session. Several status fields also stay biased toward default for backward compatibility.

Treat named-session separation as worktree-level isolation (each pair edits its own files) rather than fully partitioned ledgers and surfaces. If you need a clean, independently auditable record of a piece of work today, a separate project instance gives you a stronger boundary than a named session. The Session Lifecycle design document tracks the staged path toward full per-session scope.

Lifecycle and teardown

A named session moves through activeinactivearchived states. To stop just one named session's live Codex runtime without disturbing anything else:

ctxrelay kill --session review

This stops only that named session's daemon-owned Codex runtime and its managed Codex TUI. The daemon, Claude attachments, the default session, other named sessions, worktree metadata, and archive state are all left untouched. (The default session uses plain ctxrelay kill; archived or unknown sessions are rejected.) --session cannot be combined with --all.

After the runtime is stopped you can archive the session (metadata-only) or rebind it to a different worktree. Archiving and rebinding both fail closed while a runtime is still launched, so exit the Codex TUI or run ctxrelay kill --session <id> first.

When to reach for named sessions

For most work, stick with the single default pair - it is simpler and its ledger behavior is the most complete. Consider a named session bound to a worktree when you want a second pair working in parallel on an isolated checkout: for example, a review pair examining a branch while your main pair keeps building, or a debug pair reproducing an issue in a throwaway worktree. Just remember the isolation caveat above: you get filesystem separation today, not fully separate ledgers.

Next steps