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)
| Concept | What it is |
|---|---|
| Server | Long-lived process owning the Unix socket and the pane tree; started by castellan herd server or castellan multiplexer server |
| Workspace | Top-level grouping of tabs (roughly: one project or one logical session) |
| Tab | A BSP-splittable container of panes |
| Pane | One PTY — either a plain shell, or a pane running an agent (Claude/Codex/Cursor/etc.) |
| Session | A 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
| Mode | Command | Use |
|---|---|---|
| Poll | castellan herd status | Headless CI, scripts — snapshot, no session held open |
| Direct attach | castellan herd attach | Daily-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 session | castellan 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
| Symptom | Likely cause | Fix |
|---|---|---|
castellan herd status hangs or errors | No server running, or wrong socket path | Run castellan multiplexer ensure first; check CASTELLAN_SOCKET env var |
castellan herd attach immediately detaches | Pane doesn’t exist or was closed | List panes first with castellan herd tabs; confirm the --pane id |
| Two terminals fight for input on the same pane | Both attached without --takeover | Use --takeover on the attach that should own input; the other becomes read-only |
--mux run doesn’t show pane_tree in episode JSON | Run used --no-mux, or the multiplexer socket wasn’t reachable at run start | Confirm castellan herd status succeeds before the run; drop --no-mux |
| Server restart loses running commands | server.live_handoff preserves layout/text only, not PTY processes | Expected behavior — for long-running commands, prefer a session you don’t restart, or checkpoint work externally |
| Remote SSH attach fails | Remote multiplexer socket not forwarded, or SSH alias misconfigured | Verify ~/.ssh/config alias resolves; try castellan remote --ssh user@host directly to isolate SSH vs. multiplexer issues |
Related
- Socket API — full NDJSON method catalog
- Agent hooks —
pane.report_agentand lifecycle reporting - Coordination model — how pane deposits feed the scheduler
- Daily driver loop
castellan herdreference