Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Multiplexer overview

Castellan ships an in-tree PTY multiplexer with a Multiplexer-shaped NDJSON socket API. One binary (castellan) serves daily-driver mux panes — workspaces, tabs, splits, agent detection — without requiring a separate external mux client binary. It is both a terminal manager you can drive by hand (castellan herd attach) and a coordination substrate the scheduler can drive automatically (castellan run --mux).

If you’re setting up a daily-driver pane layout, wiring an editor agent to report its own state, or trying to understand what --mux actually spawns, this page is the map.

Anatomy

castellan herd server  →  castellan-multiplexer (Unix socket)
        │
        ├── workspace / tab / pane tree      (session.snapshot, workspace.*, tab.*, pane.*)
        ├── pane.spawn / pane.attach          (PTY proxy, streaming)
        ├── events.subscribe / events.emit    (NDJSON EventBus — live Castellan events when socket healthy)
        └── pane.report_agent                 (hook protocol — authoritative agent state)
ConceptWhat it is
ServerLong-lived process owning the Unix socket and the pane tree; started by castellan herd server or castellan multiplexer server
WorkspaceTop-level grouping of tabs (roughly: one project or one logical session)
TabA BSP-splittable container of panes
PaneOne PTY — either a plain shell, or a pane running an agent (Claude/Codex/Cursor/etc.)
SessionA named server instance with its own socket + persist dir — lets you run multiple independent multiplexer servers

Pane agent status feeds the coordination substrate directly: agent_status observations can carry pheromone_deposits, which boost scheduler wake pressure on the pane zone. See Coordination model for the mechanism.

flowchart LR
    SRV[castellan herd server] --> SOCK[Unix socket]
    SOCK --> PANE[panes]
    PANE --> AGENT[agent_status]
    AGENT --> DEPOSIT[pheromone_deposits]
    DEPOSIT --> FIELD[pane zone]
    FIELD --> SCHED[scheduler wake boost]

    classDef runtime stroke:#0969da,stroke-width:2px
    classDef substrate stroke:#5b6b8c,stroke-width:2px

    class SRV,SOCK,PANE,AGENT runtime
    class DEPOSIT,FIELD,SCHED substrate

How operators use it

1. Start the server (idempotent — spawns a daemon only if none is running):

castellan multiplexer ensure
# or, Multiplexer-shaped:
castellan herd server

Default socket: ~/.config/castellan/castellan.sock (override with CASTELLAN_SOCKET or --socket).

2. Check status — the rich agent dashboard, not just “is it up”:

castellan herd status

3. Attach interactively to a pane:

castellan herd attach                    # focused pane in first workspace
castellan herd attach --pane w1:p1       # specific pane

Detach with Ctrl+Q. Use --takeover if another client already owns input.

4. Let the scheduler drive panes automatically instead of attaching by hand:

castellan run --goal "reach target" --plugin gridworld --mux
castellan swarm-demo   # scripted multi-pane demo, no goal needed

5. Wait for a pane’s agent to go idle before sending it more work (scripting/CI):

castellan herd wait --pane w1:p1 --status idle --timeout-ms 60000

6. Run over SSH when the multiplexer lives on a remote host:

castellan herd remote user@host
# or the lower-level goal runner:
castellan remote --ssh user@host --goal "reach target" --plugin gridworld

Attach modes

ModeCommandUse
Pollcastellan herd statusHeadless CI, scripts — snapshot, no session held open
Direct attachcastellan herd attachDaily-driver interactive TTY
Terminal attach (raw)castellan herd terminal attach <term_id>Attach by terminal id instead of pane label
Observe (read-only)castellan herd terminal observe --pane <id>Stream frames without taking input ownership
Named sessioncastellan herd session attach <name>Multiple independent multiplexer servers side by side

Recipes

When you want one shared pane layout across a team or CI matrix, use a named session (--session <name>) so each gets its own socket and persist directory instead of colliding on the default socket.

When an editor agent should report its own state instead of relying on screen-scraping heuristics, wire pane.report_agent from a hook script — see Agent hooks for the exact NDJSON payload and shell wrapper.

When you need to restart the multiplexer without losing your pane layout, use server.live_handoff (via the socket API) rather than killing the process — layout and visible text are preserved, but note PTY processes themselves are not preserved across handoff.

When debugging “why didn’t my mux run pick up pane activity,” confirm the run actually used --mux (in-process host with topology tracking) — without it, pane deposits never reach the scheduler’s field.

When the dashboard last_events stays empty during castellan run, ensure the mux socket is healthy (castellan multiplexer ensure). Healthy sockets receive Castellan events via events.emit; panes still use the in-process host. See Statusline.

Failure paths / troubleshooting

SymptomLikely causeFix
castellan herd status hangs or errorsNo server running, or wrong socket pathRun castellan multiplexer ensure first; check CASTELLAN_SOCKET env var
castellan herd attach immediately detachesPane doesn’t exist or was closedList panes first with castellan herd tabs; confirm the --pane id
Two terminals fight for input on the same paneBoth attached without --takeoverUse --takeover on the attach that should own input; the other becomes read-only
--mux run doesn’t show pane_tree in episode JSONRun used --no-mux, or the multiplexer socket wasn’t reachable at run startConfirm castellan herd status succeeds before the run; drop --no-mux
Server restart loses running commandsserver.live_handoff preserves layout/text only, not PTY processesExpected behavior — for long-running commands, prefer a session you don’t restart, or checkpoint work externally
Remote SSH attach failsRemote multiplexer socket not forwarded, or SSH alias misconfiguredVerify ~/.ssh/config alias resolves; try castellan remote --ssh user@host directly to isolate SSH vs. multiplexer issues