Expand description
Headless session backend: run the role’s [client.runtime] command as a
child of the client.
The other two backends own a terminal — zellij a pane, Orca a tab — and the fake backend owns nothing at all, so none of them can serve a coding agent that must be run headlessly and reached over a socket (an e2e case with no window manager, a CI host, a supervisor that only needs the process). This backend is that path, and it is the one place in the tree that spawns a session command directly.
Process semantics, which is what makes it different from a naive Command::spawn:
- stdin is a pipe the client holds open. An agent in RPC mode treats EOF
on stdin as “the operator left” and exits, so
Stdio::null()(immediate EOF) or a droppedChildStdinwould end it the moment it starts. The child’s stdin stays owned by theChildthis backend keeps, so the write end lives untilSessionBackend::closetakes the child out. - stdout and stderr go to
<workspace>/.onlyne/logs/session-<task>.log(both streams into one append handle, the shape an operator givesonlyne-client runfor its own log). The agent’s own diagnostics are what an operator reads when a session misbehaves, so they must not disappear into the client’s stdio. - the child gets its own process group (unix), so a signal aimed at the
client’s group — the operator’s terminal, a supervisor’s
kill— does not reach the agent behind the drain: onlySessionBackend::closeends a session, which is the contract a tab or a pane has too. - close is graceful before it is lethal:
SIGTERM, thenSIGKILLafter the grace window, andforceskips the grace. The child is reaped either way, so no zombie outlives its session. The group signal iskill(2)on the recorded pgid only (backend_ref.pgid, equal to the leader pid afterprocess_group(0)). Close never matches cmdline text and refuses pid 0/-1.