Skip to main content

Session Threads

Every Session has a durable primary Thread. A coordinator can start persistent child Threads through its private model tools. The Session remains the aggregate resource while every Thread owns its execution and conversation.

GET /v1/sessions/{session_id}/threads
GET /v1/sessions/{session_id}/threads/{thread_id}
POST /v1/sessions/{session_id}/threads/{thread_id}/archive
GET /v1/sessions/{session_id}/threads/{thread_id}/events
GET /v1/sessions/{session_id}/threads/{thread_id}/stream

The primary identity and its initial execution projection are inserted in the same PostgreSQL transaction as the Session, its immutable Skill and Vault pins, initial events, and orchestration outbox. Existing databases receive deterministic primary identities and backfilled projections during migration. Deleting a Session cascades to its Threads.

Projection model

Each Thread owns an independent PostgreSQL projection of its immutable Agent snapshot, status, cumulative usage, and timing. The primary Thread has a null parent_thread_id. Its Agent omits multiagent; the resolved coordinator roster remains on Session.agent, matching the upstream response boundary.

The runtime updates an owning Thread before recomputing the Session aggregate in the same PostgreSQL transaction. The Session remains running while any Thread is running; usage is the sum of independently accumulated Thread usage. Session-only title, metadata, and resource changes do not mutate Thread state.

Thread Event list and stream select the chosen Thread from the Session-wide ordered event ledger. Pagination uses forward-only opaque cursors bound to both Session and Thread IDs. Streaming supports the same opt-in event_deltas[] values as the Session stream, but child previews use Thread-scoped NATS subjects and never leak into the primary stream.

Archiving the primary Thread uses the Session's idle-only archive fence. A running Thread returns 409; after archive the Thread reports terminated and its duration is frozen.

Archiving a child uses its own idle/rescheduling lifecycle fence and never archives the aggregate Session. PostgreSQL atomically freezes the child projection, closes its unresolved client-action barrier, flushes queued input, emits session.thread_status_terminated on the child and primary ledgers, and upgrades the child orchestration outbox to a termination intent. That intent dominates a stale wake for the same Thread, and the relay retries the idempotent Temporal termination until it succeeds. Session deletion installs the same intent for every child before stopping the primary Workflow and releasing the sandbox.

Coordinator execution

A coordinator receives list_agents and send_to_agent as private model tools; they do not emit generic agent.tool_use/agent.tool_result events. A new send atomically captures the resolved roster Agent, creates the child projection, appends directional message and lifecycle events, and writes the child outbox. The relay starts a stable per-Thread Temporal Workflow. Follow-up sends reuse that Thread and its provider-native transcript. A completed child answer is reported asynchronously to the primary Thread, which is then scheduled for a separate synthesis turn. The model-facing projection wraps the report with its from_agent_name and from_session_thread_id; providers still receive a legal user-role input, while the coordinator can distinguish internal Agent traffic from user-authored messages and address follow-ups to the existing Thread.

Client-action routing

When a child requires a tool confirmation, custom-tool result, or self-hosted tool result, its canonical action and session.thread_status_idle boundary are written to the child ledger. The server cross-posts a client-visible copy to the primary stream with session_thread_id, and the aggregate session.status_idle references those visible event IDs.

Clients answer the cross-posted event ID using the ordinary user.tool_confirmation, user.custom_tool_result, or user.tool_result shape. They may echo session_thread_id as a redundant routing hint. The server resolves the event ID to its owning Thread, rejects a conflicting hint, persists the result in the child ledger, and wakes only that child after every action in the barrier has a result. A companion system.message follows the same route. A waiting child does not block work on the primary or a sibling, and it emits no terminal report until its barrier is resolved.

Interrupts use the ordinary Session Events endpoint. Omitting session_thread_id durably fans the control event out to the primary and every active child ledger; providing it targets and wakes only the named Thread. Every Thread independently applies the same PostgreSQL finish-vs-interrupt ordering fence, active-Activity cancellation, and idle no-op behavior.

Compacted child context projections are durably checkpointed, and agent.thread_context_compacted is emitted on the owning Thread ledger. The remaining multi-agent boundary includes exact hosted preview suppression for report-only turns. The v1.62 advisor Thread is not represented as an ordinary child placeholder; it remains unsupported until its reserved, automatically terminating consultation lifecycle can be implemented end to end. Session budgets are also deferred until list cost is aggregated across all independently running Threads.

Start with the multi-agent guide.