Skip to main content
A session is a shared object. Several people can be in one at once, and the session page shows four things without anyone having to explain them: who else is there, who is typing, whether the agent can hear a message before you type it, and whose turn an interrupt would cancel. This page describes the rules the API enforces. The web app is one client of them; any client that talks to the API gets the same answers.

What is logged and what is not

Watching and typing are never written anywhere durable. A record of who looked at whose session is surveillance, not memory. They travel over the session’s SSE stream as non-event frames (event: presence, event: typing) with no id:, so they are outside the reconnect cursor.

Can the agent hear me?

Every session has a reach, and the API publishes it: sessions.list, sessions.detail, the terminal’s session reads and the get_session tool all return reach and reachReason. It is the same answer the API checks a message against, from the same call, so a client that shows it never tells someone the agent can hear them and then has the message refused. Clients show it; none works it out for itself. reachReason says why, so every surface explains a none the same way, and a refused message names the same reason:

How Orchestra knows a captured session is running

A captured session runs in its owner’s own terminal, so Orchestra cannot ask it directly. The capture tailer on the owner’s machine can see the agent’s process: when a hook registers a session, it records which process is the agent, and every 15 seconds the tailer reports the ones that are still running. Whether a session is live comes from that report, never from how recently it wrote something. An agent thinking, running a long build or waiting on CI stays live however quiet it is. When the process stops without Claude Code’s SessionEnd hook saying so, the tailer reports it, and the session records an ending with the reason process_gone. When nothing is heard from a session for ten minutes, the API records stopped_reporting. Both are recorded once, and neither claims the session exited: they say what Orchestra observed. A session heard from again afterwards (a laptop waking up, say) is live again in place. A session that has never been heard from (a client older than this, or a machine that runs no tailer) is judged by its last activity as before, and its reason says so: recent_activity or quiet, never hooks. steerable and openRun are still returned for older clients. They are deprecated: read reach and reachReason instead. For a native session the fact is an open run (a run_started with no run_ended), not how recently the session spoke. A loop waiting for input is reachable however quiet it has been, and a fork nobody has started is not, however fresh. The page’s stream refines this live: its liveness frames carry the open runs, and a run its supervisor has let go of does not count. sessions.postMessage takes target: "agent" | "room". Without one, it defaults by session kind: agent on native, room on captured. A message to the agent on a session whose reach is none is refused, with the reason and what to do instead. It is never accepted as steering that nothing will read. A message to the room is always accepted. The terminal’s postMessage and the post_session_message tool are refused in exactly the same cases, with the same words.

Turning messages on and off

A teammate’s message reaches a captured session only with its owner’s consent, because it runs in their terminal with their credentials and files. Consent is recorded in the log, and it is a history rather than a switch:
  • The first time the hooks orchestra enable installs run in a session, they record consent for it.
  • orchestra disable in that repo removes the hooks and records a revocation for each of your sessions there. From then on the session’s reach is none (revoked): a message to its agent is refused, and messages that were still waiting are withdrawn, never handed over later. A hook that keeps running from the settings Claude Code read at startup delivers nothing and does not turn messages back on.
  • orchestra enable again records consent again for the sessions you turned off. Messages sent from then on are delivered; the withdrawn ones are not.
Only your own sessions are affected, and only in that directory and below. Offline, the change is queued and recorded as soon as Orchestra is reachable; until then the session still reads as taking messages, and says so.

Interrupts

An interrupt cancels the agent’s in-flight turn. It never deletes a message.
  • Your own turn stops on one call.
  • Someone else’s turn costs one confirmation that names them. Until the caller confirms, sessions.interrupt appends nothing and answers with needsConfirmation:
Send the call again with confirmed: true and that turnRequestEventId. The confirmation counts for that request only: if someone else asked for something in between, the API asks again, naming them. Whose turn it is comes from the log, over the whole session: the latest message to the agent since the last interrupt or end. A room aside is not a request. The interrupt event records turnRequestEventId and, when the request was someone else’s, turnRequestedBy. The same rule holds on every door. The terminal’s interrupt answers with a confirmation naming the requester, and the next Esc confirms that request. The interrupt_session tool answers needs_confirmation until it is called again with confirmed: true and the turnRequestEventId it returned. An agent going through the tools meets exactly the friction a person in the browser does.

Re-running a cancelled request

sessions.rerun({ sessionId, interruptEventId }) re-queues the cancelled request’s stored words to the agent as a new message from the caller, with rerunOf pointing at the original. The client names only the interrupt; the words come from the log, so a request far outside the page’s window still re-runs, and an escaped slash is never re-read as a command. Pressing it twice re-runs once. Like any message to the agent, it is refused when nothing would hear it. Agents do the same with the rerun_session_request tool.

Two people steering at once

There are no locks and no turn-taking. Both messages land, in the log’s total order, attributed to whoever authenticated. The agent receives both, in that order, by name, and the timeline shows exactly what it saw.

Who can see the room

The session’s visibility governs everything here. A principal who cannot see a session cannot get a stream for it, cannot post into it, cannot interrupt it, and never appears in its watcher list or typing line. The stream checks visibility when it connects, on every batch of frames, and on every heartbeat, so a revoked watcher stops receiving frames and drops out of the room’s presence without reconnecting. Granted again, they are back in the room’s presence on the next heartbeat, also without reconnecting.