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 reasonprocess_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 enableinstalls run in a session, they record consent for it. orchestra disablein that repo removes the hooks and records a revocation for each of your sessions there. From then on the session’s reach isnone(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 enableagain records consent again for the sessions you turned off. Messages sent from then on are delivered; the withdrawn ones are not.
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.interruptappends nothing and answers withneedsConfirmation:
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.