Remote-control sessions (the Agents pane)¶
The Agents pane drives agent sessions inside a shed — claude (REPL and
remote-control broker), codex, cursor-agent, opencode, and plain shells:
detached tmux sessions named rc-<slug>, created and torn down over SSH. The pane
lists them with a live state, a "made by … · age" provenance line, an Open console
button (attach a terminal to the session's tmux), an Open in Claude button (the
claude.ai/code URL — Claude kinds only; the other agents have no browser URL), and a
kill button.
The create sheet's kind chips are gated on the shed's capabilities: the app reads
shed-ext-rc capabilities (kinds + per-agent install/version + features) and only
offers kinds whose agent is installed on that shed's image. A shed whose baked-in
shed-ext-rc predates multi-agent RC advertises no capabilities block and offers only
the Claude/shell kinds it supports; recreate the shed to pick up the newer agents.
RC Session Convention v2 (SHED_RC_*)¶
shed-desktop is a conformant client of the tool-neutral RC Session Convention
v2 (published by shed-remote-agent; spec:
docs/reference/rc-session-convention.md there). The tmux session is the single
source of truth — all durable metadata lives in its session environment, and
state/url are always derived fresh from the pane, never stored. This lets
shed-remote-agent, shed-desktop, the shed CLI, and future clients discover,
classify, attach to, and tear down each other's sessions without a registry.
The SSH+tmux choreography (bootstrap, classification, the SHED_RC_* metadata,
workspace-trust pre-seeding) lives in the shed-ext-rc guest binary baked into
the shed image; shed-desktop invokes it over SSH (shed-ext-rc create --wait /
list / kill) and decodes its neutral JSON DTO. So every tool produces
byte-compatible sessions. The metadata the binary writes:
| Key | Meaning |
|---|---|
SHED_RC_V |
Schema version (a positive integer; 2). |
SHED_RC_ID |
Stable opaque id (a lowercase UUIDv4), generated once at create. |
SHED_RC_DISPLAY_NAME |
Human name; also claude --name. |
SHED_RC_KIND |
claude-broker | claude-rc | codex | cursor | opencode | shell (v2 renamed v1's agent/repl). An unrecognized value is preserved verbatim and rendered neutrally (name + state only) — never aliased to claude-broker. |
SHED_RC_WORKDIR |
Working directory at create. |
SHED_RC_CREATED_BY |
Provenance <tool>/<version>, e.g. shed-desktop/0.1.0. |
SHED_RC_CREATED_AT |
Creation time, RFC 3339 UTC with a trailing Z. |
SHED_RC_TARGET |
Optional, advisory target label (shed:<name>@<host>); non-authoritative. |
Managed vs legacy¶
A session is managed when SHED_RC_V is 2 or higher (a higher version stays
managed — known fields rendered, unknown keys ignored, never dropped). A v1
session (or an unrecognized kind) is treated as legacy — v2 does not alias the old
agent/repl values.
Any rc-* session without a valid (≥ 2) SHED_RC_V is legacy/unmanaged: it is
still listed and killable, but rendered with defaults (kind = claude-broker, a
<shed>/<slug> fallback name, the default workdir), any stray SHED_RC_* /
SRA_* values are ignored, and the Agents pane shows a legacy badge and
confirms before killing it.
Clean break. v2 renamed the kinds (
agent→claude-broker,repl→claude-rc) and bumpedSHED_RC_Vto2with no aliasing; earlier builds also used an app-namedSRA_*prefix, no longer written or read. Until every tool you use adopts v2, each renders the other's older sessions as legacy/unmanaged (still attachable/killable, defaults only); kill + recreate a session to restore its metadata. The shed image must shipshed-ext-rcbefore this build can create sessions.
Derived state¶
state and url are computed from a tmux capture-pane by the pure classifier
(rc.classify over IPC), never stored:
state |
Meaning |
|---|---|
starting |
No URL / status line yet (incl. claude still in first-run setup). |
ready |
Terminal-good for the kind (a URL for claude-broker/claude-rc; any output for shell). |
reconnecting |
claude remote-control is reconnecting (claude-broker). |
needs-trust |
claude refused — workspace not trusted (attach via the console button to trust). |
needs-auth |
claude needs a claude.ai login (attach + claude auth login). |
dead |
The tmux session is gone. |
Transport¶
Listing pipes a single batched script to a remote bash over stdin (not
bash -c) and runs tmux directly: on shed images where the user has no
controlling terminal, tmux invoked under bash -c fails with "open terminal
failed: not a terminal", but works when bash reads from stdin. Random per-call
markers (@@RC:<nonce>:…) frame each session's env dump + pane so neither pane
text nor a metadata value can forge a delimiter.
As of this release the server enriches session listings with this metadata
server-side: GET /api/sessions and GET /api/sheds/{n}/sessions return an rc
object per rc-* row (populated by the server execing shed-ext-rc list over the agent
channel). HTTP-only clients no longer fan out one SSH connection per shed to read RC
state (closing the shed follow-up). Pass
?rc=0 to skip enrichment on a hot poll path. GET /api/overview aggregates the same
data — server info (with a features discovery array), disk usage, and every shed with
its rc-enriched sessions and per-shed rc_capabilities — into one call for
mobile-style clients.
Live activity (the rc hub)¶
On VZ sheds a resident guest daemon — the RC activity hub (shed-ext-rc serve) —
derives a live activity dimension for each session and, for codex, a message feed and
gated input. Two server endpoints expose it to clients (advertised via the rc-proxy
and rc-events feature tokens on GET /api/info / GET /api/overview):
GET/POST /api/sheds/{name}/rc/*reverse-proxies the hub's/v1API (session list, SSE/v1/events, the codex/messagesfeed, andPOST /input), ensure-starting the hub on demand. The server is the authorization boundary; the hub is loopback-only inside the guest.GET /api/rc/eventsis a single demand-driven aggregate SSE stream carryingactivity.changed/session.updated/message.appendedacross every shed, so a client subscribes once for the whole host.
When a hub is running, enriched session listings additionally carry activity,
activity_at, and last_message inside each row's rc block (absent when no hub is
running — e.g. the hub hasn't started yet or the image predates it — in which case the
proxy degrades to 503 RC_HUB_UNAVAILABLE and clients hide activity affordances). This
is backend-agnostic: both VZ and Firecracker reach the loopback hub through the guest
agent's vsock TCP proxy. The full contract — lifecycle-trumps-activity
precedence, SSE envelopes, message-feed and input semantics — lives in
shed-ext-rc.