Interactive sessions
Partly built: a session run (session runs)
goes on after its task's turn to an operator's prompts, through a bridge
that listens on the runner's tailnet name behind tailscale serve. What follows is the earlier design note: a person, or another
program, attaches to the agent's session and works with it, while the
agent stays inside the same sandbox,
inference proxy and safe-outputs
limits as an unattended run. It is why the agent is driven over ACP.
The operator's words: "Another use case I have btw is interactive chats this is why ACP. We just mix in Tailscale or equiv. Should be a clean mode flag to wait for a connection. Or start from a prompt but accept dynamic connections."
The plan defers this work. The proposed
first bridge is a Unix socket on the
runner, reached by an attach command over Tailscale SSH that copies
its standard input and output to the socket: ACP stays a byte stream,
nothing new listens on the network, and the tailnet's own identity
decides who gets in. Everything below is a proposal.
What exists
One seam, in session/clients.rs. The session holds
ACP's Client role toward the agent. Every message of the agent's
passes one tap, which records it and hands the agent's notifications
to a list of attached clients. The list is empty for a task run; in a
session run it holds the bridge's one client, which
the spec describes with its trust boundaries.
The ACP SDK's Proxy role is not what this builds on. It speaks
_proxy/* to a conductor that owns both of its connections, and a run
with no client side has nothing for a conductor to own.
Still to design
The session spec settles the way from a client to the agent (prompts and cancels over the bridge), who answers a permission request when a person is watching (the run's policy, still), how a prompt sent during a turn is held (it is refused: Claude Code's adapter would end the running turn for it) and what ends a session with a client (the client leaving, or an idle timeout). Still open is a client that joins a session already running: the bridge's client is attached when the session starts.
Who may attach
A client is a new source of instructions with the authority of whoever is typing, but it cannot widen the run: the safe-output types, the target, the egress policy and the limits are fixed before the agent starts, and nothing sent over ACP changes them.
This assumes run without --runner-home, where the agent is a second
user (the reusable workflow's agent-user: sandbox). By default the
reusable workflow, and review.yml, pass --runner-home, which
makes the agent the runner's user and refuses session.listen, since
the agent could reach the bridge's port ahead of the operator, so no
client can attach there.
The user a client logs in as must not be the runner's user. That user holds the job's credentials even after setup removes its root access. SSH as that user would admit a credentialed user, not a chat client. Attaching needs a user of its own that can open the socket and nothing else. The tailnet's rules must also keep one runner from logging in to another.
Also open: several clients at once, a read-only client, and a WebSocket bridge for a browser client, which would have to authenticate the client itself.