Skip to content

Harnesses

A harness is a vendor’s own coding-agent CLI, launched by the daemon with your own login on your own machine. Nexus never copies credentials and never reimplements the agent. What it adds is the part the CLI does not have: a durable identity, addressed delivery, wake-up, continuity across restarts, and a readable record of what the session did.

Harness Headed bridge Headless Resume
claude Native hooks and transcript bridge Yes --resume <session id>
codex App-server bridge Yes Thread id, carried out of band — codex resume <thread-id> after the launch kind
opencode Native plugin bridge Yes -s <session id>
hermes Gateway bridge Yes --session <id>

Every bridge above is structured: activity comes back as typed events, not as scraped screen text. A runtime with no registered bridge falls back to other, a generic non-headed contract, so an unknown harness id is never a crash — just a runtime that cannot be launched headed.

Headed runs the real interactive CLI in a daemon-owned terminal. You get the vendor’s own TUI, its own login, its own keybindings — and because the daemon owns the terminal rather than your shell, you can walk away and come back.

The default headed backend is a raw daemon-owned PTY (--backend pty) for claude, codex, and opencode. Attach it from any terminal emulator, and detach with Ctrl-]; the agent keeps running. A headed hermes viewer attaches through tmux instead, so its detach key is tmux’s own.

Terminal window
nexus attach ada
alacritty -e nexus attach ada

--tmux (or --backend tmux) selects the legacy tmux backend, where the detach key is tmux’s own.

Headless runs the same harness with no terminal at all, driven programmatically over ACP and streamed back as session activity. There is nothing to attach to; you read the session instead.

Terminal window
nexus launch claude
nexus launch --name ada --project backend codex
nexus launch --headless opencode
nexus launch claude --model opus # after the harness name: passed to Claude Code itself

nexus launch <kind> with no name works: the daemon stages an unnamed durable identity and the agent claims its own first name. A headed launch auto-attaches when your stdin is a terminal; otherwise it prints <name>:<session_id> and returns.

Flag Effect
--name <name> Bind a name at launch instead of letting the agent claim one
--project <project> / --role <role> Metadata on the identity
--cwd <path> Working directory for the runtime
--headless Spawn without a TUI
--tui Force the headed path, including in a non-interactive shell
--detach Skip auto-attach; print the session id and return
--backend pty|tmux Headed terminal backend; pty is the default
--tmux Shorthand for the legacy tmux backend
--initial-prompt <text> Give the agent its first turn at launch

Anything after the harness kind is the harness’s own argv and is forwarded verbatim, so native flags keep working. Put Nexus flags before the kind.

Each daemon-owned runtime stores a descriptor with everything needed to recreate its launch shape: harness, headed or headless, backend, working directory, and the native resume correlation. When a message arrives for a runtime that is no longer alive, the daemon revives it from that descriptor and then delivers.

Command What it does
nexus attach <name> Attach your terminal — reviving the runtime first if it is dead
nexus resume <target> Revive a headed runtime from its stored resume id without attaching

Each harness declares how it consumes a stored resume key: a flag appended to its own argv, or an out-of-band thread id handed to a structured bridge. Those provider resume ids are correlation hints, not Nexus identity — the Nexus session id is what survives.

State What happens to an inbound message
Busy Never a second turn. With the default interrupt timing, the Codex app-server bridge steers the batch into the running turn; Claude Code, Codex, and PTY sessions have the running turn interrupted and the batch sent as the next turn. yield_turn and after_tool_loop wait for the turn to end instead
Paused Held until the agent is resumed, including a message from a human
Provider-limited A structured rate, usage, quota, or overload signal — never inferred from rendered text — settles that attempt as a terminal error (see nexus admin dlq list) and holds later arrivals until the runtime registers again. Nothing is retried on a timer
Offline Held, then delivered after a revive

The timing table behind the busy row is in How delivery works.

Harness ids are an open set — ^[a-z][a-z0-9_-]{0,63}$ — so a new harness needs no contract change.

If your runtime just speaks ACP on stdio, it needs no Rust at all. Drop one TOML manifest at <NEXUS_HOME>/harnesses/<id>.toml, where the file stem is the harness id:

~/.nexus/harnesses/goose.toml
[command]
program = "goose"
args = ["acp"]
# cwd = "/optional/pinned/dir"
[command.env]
GOOSE_MODE = "acp"

The daemon loads manifests at startup. Manifest harnesses are headless by construction; a runtime that needs bespoke bootstrap — installing hooks, injecting config — has outgrown a manifest and should be a harness crate. See Adding a harness.

Headed and headless feed the same lane. The daemon records session activity — text, thinking, tool calls, plans, commands — as the turn streams, then materializes the final rows into durable history at turn end and drops the token deltas. Tool activity follows one canonical shape across every harness, so a consumer never has to reverse-engineer per-harness payloads.