Documentation menu
Agents and known issues
Install and log in to the vendor CLI before starting it through nid. Run
nid agents to list built-ins, presets and repository definitions, and
nid doctor to diagnose CLI availability, login evidence and Codex sandbox setup.
The built-in drivers use Claude's structured stream, Codex's app server, and ACP
for opencode/Kimi. These protocols report tool activity and support approval routing.
Use ACP for custom agents when available; PTY mode runs an interactive terminal and
has the limitations described below. The mock driver is for tests and demos.
The known-issues table records which integrations
have actually been exercised; preset availability is not a compatibility guarantee.
Custom agents (any terminal agent)#
Besides the built-in drivers (claude, codex, opencode, kimi), nid runs any terminal coding agent
declared under agents: in ~/ (all repositories) or in a repository's
nid.yaml (that repository; it wins over the user config, which wins over the presets).
nid spawn <name>, nid up, the dashboard's Spawn agent dialog and the spawn RPC
treat them like built-ins. nid agents lists every agent the current directory can spawn and
whether its command is installed; nid doctor shows the same for custom agents.
agents:
myagent:
command: myagent # executable (PATH, or an absolute path)
args: [--auto-approve] # always passed
mode: pty # pty: drive its TUI in a pseudo-terminal | acp: Agent Client Protocol over stdio
env: { MYAGENT_THEME: plain }
promptPattern: '^> $' # pty: a turn ends when this regex (multiline) matches the rendered screen...
busyPattern: 'esc to interrupt' # pty: ...but not while this one does (the TUI's working indicator)
quietMs: 3000 # pty: ...or after this long without output (default 3000)
interrupt: esc # pty: key that stops current work: esc | ctrl-c (default) | ctrl-<letter> | "\x1b"
modelArgs: [--model, '{model}'] # added when the session has a model (claude:x@model style)
models: [fast-1, { id: big-2, description: Slower, better }] # optional: what `nid models` lists
mcp: # how the agent reaches nid's MCP tools (pty only; acp gets them via the protocol)
args: [--mcp-config, '{configPath}'] # {configPath}: a JSON {"mcpServers": {"nid": {...}}} written per session
# file: { path: .myagent/mcp.json } # or write that JSON (or `content`) into the worktree (git-excluded)
systemPrompt: prefix # prefix (default): nid instructions prepended to the first message | none
description: My agent
Templates {configPath}, {cwd}, {session}, {model} and {mcpMode} work in command, args, modelArgs,
env values and mcp. mcp: none (or no mcp) means the agent has no nid tools: it
cannot claim tasks or post notes, but it still receives its work and teammates' messages as turns.
Names use lowercase letters, digits, _ and -, and cannot redefine a built-in agent.
A session stores the definition it was spawned with; resume uses the current config when it
still defines the name.
Which models an agent can use (nid models)#
nid models [agent] lists the models each agent accepts, marks the default a new session
gets, and says how a running session switches. It never starts a model turn:
| Agent | Where the list comes from | Switching a running session |
|---|---|---|
| claude | Built in: Claude Code's aliases (default, fable, opus, sonnet, haiku, opusplan, opus[1m], sonnet[1m], each with the full id it currently means) and full claude-* ids | Resumes the same conversation with the new --model |
| codex | Codex's app-server model/list (started briefly, no thread), for your login; cached 10 minutes, --refresh asks again | Next turn, same conversation |
| ACP agents (opencode, kimi, gemini, ACP custom agents) | The models a session advertised (remembered in <home>/, for custom agents per command, since two repos can give one name to different programs); a custom agent's own models: list wins; before the first session, any name is accepted | Next turn, where the agent supports model selection |
| PTY custom agents | The definition's optional models: list (ids, or {id, description}) | Only on restart |
The marked default follows default models (--role lead shows a
role's), else the vendor's own. An agent with no known list accepts any model name unchecked.
Seeing and changing a session's model. nid ls, status, the dashboard and the terminal
app header show the model each agent reports running (Claude Code's init, Codex's thread, the ACP
session), else the configured one marked as configured, else default. nid model <session|group>
[model], /model in the terminal app and the dashboard's Session view change it as in the last
column above: never mid-turn, on the same conversation, and on the billing the session already uses.
A stopped session keeps the choice for its next resume. See undefined.
The session/group/global mcp selection defaults to project (see
MCP isolation). The generated {configPath}
contains nid plus .mcp.json project servers in that mode, and only nid in
none mode. To stop a custom CLI from also loading user servers, declare its
vendor-specific flags as mcp (this example
is for Claude). They are passed in project/none modes, including ACP launches.
There is no universal ACP/PTY flag for disabling a vendor's inherited config.
PTY agents. mode: pty runs the agent's own interactive interface in a pseudo-terminal: a
turn writes the message and Enter, and ends when promptPattern matches or the output has been
quiet for quietMs. nid keeps a model of the agent's screen. Without a busyPattern,
promptPattern is matched against the cursor line (up to the cursor) and the last non-blank line,
so an old prompt higher up the screen cannot end a new turn (the last line is compared with
trailing spaces trimmed, so write '^>' rather than '^> $' if the cursor may not sit on the
prompt). With a busyPattern it is matched
against the whole rendered screen (full-screen TUIs park the cursor anywhere), guarded by the busy
pattern as described below. The prompt only lets a turn end early: a quiet period always ends
the turn, so an agent that can work for a while without printing anything (no spinner, no
progress output) needs a larger quietMs or a busyPattern (while it is on screen the quiet
period is ten times longer). Otherwise nid will consider the turn done and type the next
message into a busy agent. The event log gets the
screen's new lines as plain text: TUI redraws, spinners and escape sequences are dropped. The raw
terminal stream is kept for terminal views.
Full-screen TUIs show their input prompt even while they work, so give them a busyPattern (their
working indicator, e.g. esc to interrupt). While the busy pattern is on screen the turn goes on,
and the prompt only counts once it has gone. Also give them interrupt: esc, because Esc, not
Ctrl-C, stops their current work.
As a demo of "any agent", here are claude, codex and opencode driven through their TUIs instead
of their structured protocols. These settings were verified on Windows against claude 2.1, codex
0.160 and opencode 1.18 (test/). The opencode one was also run end to end
through the release binary's daemon. The built-in drivers are still the better choice for these
three, since they route approvals and show tool calls:
agents:
claude-pty:
command: claude
args: [--permission-mode, auto] # no approval prompts, and no confirmation dialog
mode: pty
promptPattern: '^❯ '
busyPattern: 'esc to interrupt'
quietMs: 5000
interrupt: esc
modelArgs: [--model, '{model}']
mcp: { args: [--mcp-config, '{configPath}'] } # nid tools work: claude reads the standard JSON
codex-pty:
command: codex
args: [-c, check_for_update_on_startup=false, --dangerously-bypass-approvals-and-sandbox]
mode: pty
promptPattern: '^› '
busyPattern: 'esc to interrupt'
quietMs: 5000
interrupt: esc
mcp: none
opencode-pty:
command: opencode
args: [-m, opencode/big-pickle] # any model your opencode is logged in to
mode: pty
promptPattern: '┃'
busyPattern: 'esc interrupt'
quietMs: 5000
interrupt: esc
mcp: none
claude asks once per folder whether you trust it, and its default answer exits. Trust a parent
folder once and every worktree below it is trusted: run claude in ~/ and
accept. --dangerously-skip-permissions would add a "Bypass Permissions mode" confirmation, which
also defaults to exit, so the example uses --permission-mode auto. codex's startup update dialog
takes typed input too (its default answer runs the installer), which is why the update check is
off.
Multi-line messages (the nid instructions prefix) are sent as one bracketed paste when the agent enabled bracketed paste (most TUIs do). Otherwise each line break is sent as Enter, which is right for line-reading programs but splits the message for a REPL. Enter always goes in a separate write after the text. Keystrokes reach whatever the agent shows, including first-run, trust and update dialogs, so disable those with the agent's own flags.
PTY needs nid's release binary (Bun's built-in PTY on Windows, macOS and Linux); in a
source checkout run under Node, spawning a PTY agent fails with "PTY needs the release binary or
bun". nid cannot see or answer a PTY agent's approval prompts, so run PTY agents in their own
auto-approve mode (the presets do) and rely on the session's git worktree for isolation. Prefer
mode: acp whenever an agent speaks ACP: approvals, tool calls and MCP then work like opencode's.
Presets shipped with nid (override any of them by defining the same name):
| Name | Mode | Command | Notes |
|---|---|---|---|
aider | pty | aider --yes-always --no-pretty --no-auto-commits --no-check-update ... | Aider has no MCP support: no nid tools. Keeps aider's prompt_toolkit input so the multi-line first message arrives as one bracketed paste. Untested. |
gemini | acp | gemini --acp | Gemini CLI 0.63 (--experimental-acp is the deprecated spelling). Untested. |
copilot | acp | copilot --acp | GitHub Copilot CLI's ACP server. Untested. |
copilot-pty | pty | copilot --allow-all-tools --no-color --no-banner ... | MCP via --additional-mcp-config @{configPath}. Untested. |
cursor-agent | acp | cursor-agent acp | Cursor CLI's ACP server (Cursor's docs call the binary agent; override command if yours is named that). Untested. |
cursor-agent-pty | pty | cursor-agent --force --approve-mcps | MCP via .cursor/ in the worktree. Untested. |
"Untested" presets were assembled from each CLI's documentation or package and never run by the
nid team; expect to adjust flags or promptPattern. The -pty presets use a guessed
busyPattern (esc to interrupt/, ctrl+c to stop); copilot-pty also uses interrupt: esc.
Reports welcome.
Live PTY terminals (nid attach --tty)#
For a running custom agent with mode: pty, attach to its actual terminal:
nid attach --tty mygroup/myagent
Keystrokes and pasted bytes go straight to the agent; Ctrl-] detaches, while Ctrl-C goes to the agent. The CLI forwards terminal resizes and restores your terminal on exit. It needs interactive stdin/stdout. The PTY daemon must run under Bun or the release binary; the attach client itself can run under Node.
The dashboard's session page has an offline-bundled xterm.js Terminal panel for running PTY sessions. It opens read-only. Take control enables typing, pasting, and resizing; Release control hands the session back to nid. Only one viewer can control a session at a time. Closing the panel, navigating away, or disconnecting releases control automatically. A disconnected panel can reconnect with a fresh snapshot; it does not silently reacquire control.
While a human has control, nid holds queued messages and pauses automatic turn-end detection, quiet warnings, and stall handling. The agent process keeps running, including any in-flight work. On release it settles according to the agent's prompt/quiet settings, then queued messages resume. The terminal view keeps about 256 KiB of raw output in memory; reconnecting replays that rolling buffer, so an older screen or scrollback may be incomplete. Raw terminal views disappear when the process stops. The ordinary transcript retains the driver's readable text events.
Native takeover (nid attach --native)#
Sometimes it is easier to drive an agent yourself in its own interface. nid attach --native
<session> pauses nid's driver for a claude, codex, opencode or kimi session (state native)
and opens the agent's own interactive TUI, resumed on the same conversation, in the session's
worktree. When you exit the TUI, nid restarts its driver on that conversation (the normal resume
path) and delivers whatever was queued; it also tells the agent that a human worked in its
conversation. Ctrl-C goes to the TUI; closing the terminal still hands the session back.
| Agent | Command nid runs | Verified live (claude 2.1.294, codex-cli 0.160.1, opencode 1.18.5 on Windows) | Leave the TUI |
|---|---|---|---|
| claude | claude --resume <id> --mcp-config <file> --allowedTools mcp | conversation resumed (same session id), nid tools work without prompts, TUI turns reach nid after hand-back. A new worktree first shows Claude's "trust this folder" dialog, which preselects No, exit | /exit |
| codex | codex resume <thread> -c mcp | conversation resumed, nid tools work without prompts, TUI turns reach nid. An "Update available" chooser can appear first (Esc skips it) | /quit |
| opencode | opencode -s <id> with OPENCODE adding the nid MCP server | conversation resumed (it shows nid's first-turn instructions as the first message), nid tools work, TUI turns reach nid | /exit |
| kimi | kimi -S <id> | not verified; no nid tools (kimi has no command-line MCP injection) |
These rows come from test/ (NID, costs tokens). It plants a code
word through nid, takes over, asks for it in the TUI (driven in a Bun PTY), plants a second word
there, calls board_read, exits, hands back, and checks the queued message is delivered and that
nid's next turn knows both words. nid attach --native removes Claude Code's session markers
(CLAUDECODE, CLAUDE, ...) from the TUI's environment. Without that, a takeover
started from a shell inside Claude Code runs claude as a "child session" with transcript saving off,
and its turns never reach the conversation nid resumes.
Preserved: the conversation history (the vendor session/thread id), the worktree and branch, the
session's claimed tasks and locks, and its messages (queued, not lost). Not preserved: approvals
are asked by the TUI itself and are not routed through nid; the turns you make in the TUI do
not appear in nid's event log, transcript, cost or token counts; mid-takeover messages from
teammates wait until you exit (the agent may still see some as [nid] notices on its nid
tool results); stall and quiet checks skip the session. A session that is mid-turn or waiting on
an approval is refused unless you pass --force, which cuts that turn off. nid resume
<session> (or Hand back in the dashboard) ends a takeover whose terminal was lost; a group-wide
resume skips native sessions, and a daemon restart turns them into stopped. Sessions without a
conversation id yet, PTY sessions and custom agents cannot be taken over natively (PTY sessions
already run in a terminal: use nid attach --tty). The dashboard's Session view shows the
command to run ("Take over in terminal"), since a browser cannot host a native TUI.
Agent notes and known issues#
Billing: nid runs Claude Code, Codex and Gemini on their subscription login or on an API key
per session (billing: subscription|api|auto, default auto = subscription when logged in).
opencode and Kimi bill through their own provider configuration and are launched unchanged. See
billing for how each mode is applied per agent.
| Agent | Driver | Notes and known issues |
|---|---|---|
| Claude Code | stream-json | Approvals routed to nid; system prompt via --append-system-prompt; resumable. |
| Codex | codex app-server | Sandboxed unless --yolo (see Codex safety mode); approvals routed; resumable. |
| opencode | ACP (opencode acp) | Live-checked with opencode 1.18.5: the nid system prompt (sent as a prefix of the first message, ACP has no system-prompt channel) is honored, nid tools work (shown as nid_board_read etc.), cancel stops a running shell command within a second and the session stays usable, messages arrive in nid tool results. Known issues: no mid-turn input, so long turns are preempted to deliver messages (above); it may finish a task without calling task_update (nid reminds it, above) or start on an assigned task without task_claim (nid's offer says explicitly that assigned tasks are not claimed yet); when its provider fails (e.g. a suspended or rate-limited account) opencode retries with backoff indefinitely and reports nothing over ACP, so the turn just looks long and quiet (several minutes for trivial prompts in one of our runs, about an hour in dogfooding). If an opencode session is working quiet, run opencode in the worktree to see the provider error, and pin a working model with @model (e.g. opencode@openrouter/). No-argument tool calls are logged without input. |
| Kimi Code | ACP (kimi acp) | Same ACP mechanics as opencode (prefix system prompt, preemption); not live-checked in this round. |
| Custom ACP agents | ACP | Gemini CLI, Copilot CLI and Cursor CLI presets, or your own: same mechanics as opencode (prefix system prompt, approvals routed, preemption, task_update reminders). The presets are untested (none of these CLIs was installed on our test machine). |
| PTY agents | pseudo-terminal | Any CLI; see the custom agents section. nid cannot see or route their approval prompts or tool calls: run them in their own auto-approve mode inside the worktree, and disable first-run, trust and update dialogs. Turn end comes from the prompt/busy patterns or a quiet period; silent work longer than quietMs ends the turn early (raise quietMs or set busyPattern). Turns are never preempted for messages (Ctrl-C/Esc is not a safe way to break in), but agents with nid tools see messages in tool results and get task_update reminders. A restart starts a fresh process (the conversation is not resumed) and re-sends the system prompt prefix. Needs the release binary or bun (not available under Node). Verified on Windows with the node REPL and with claude, codex and opencode TUIs; macOS and Linux are built but not live-tested. |
Starting nid from inside another agent (for example a terminal in Claude Code) is fine. The
daemon removes Claude Code's own session markers (CLAUDECODE, CLAUDE,
CLAUDE, its messaging socket, ...) from its environment when it starts, and
attach --native removes them from the TUI it opens. Otherwise an interactive claude it starts
(a PTY agent, a native takeover) would run as a child session with transcript saving off, and
nid could not resume that work. User settings such as CLAUDE or API keys
are kept. The daemon log names the variables it removed.
test/ (opt-in, NID, costs tokens) re-runs these
opencode checks against the real CLI with its own daemon and NID_HOME; NID
dumps every event.