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 ~/.nid/config.yaml (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:

AgentWhere the list comes fromSwitching a running session
claudeBuilt 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-* idsResumes the same conversation with the new --model
codexCodex's app-server model/list (started briefly, no thread), for your login; cached 10 minutes, --refresh asks againNext turn, same conversation
ACP agents (opencode, kimi, gemini, ACP custom agents)The models a session advertised (remembered in <home>/cache/models, 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 acceptedNext turn, where the agent supports model selection
PTY custom agentsThe 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.isolationArgs: [--strict-mcp-config] (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/pty-tui.live.test.ts). 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 ~/.nid/worktrees 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):

NameModeCommandNotes
aiderptyaider --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.
geminiacpgemini --acpGemini CLI 0.63 (--experimental-acp is the deprecated spelling). Untested.
copilotacpcopilot --acpGitHub Copilot CLI's ACP server. Untested.
copilot-ptyptycopilot --allow-all-tools --no-color --no-banner ...MCP via --additional-mcp-config @{configPath}. Untested.
cursor-agentacpcursor-agent acpCursor CLI's ACP server (Cursor's docs call the binary agent; override command if yours is named that). Untested.
cursor-agent-ptyptycursor-agent --force --approve-mcpsMCP via .cursor/mcp.json 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/cancel, 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.

AgentCommand nid runsVerified live (claude 2.1.294, codex-cli 0.160.1, opencode 1.18.5 on Windows)Leave the TUI
claudeclaude --resume <id> --mcp-config <file> --allowedTools mcp__nid --append-system-prompt <nid prompt>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
codexcodex resume <thread> -c mcp_servers.nid.{command,args,env,tool_timeout_sec,default_tools_approval_mode="approve"}conversation resumed, nid tools work without prompts, TUI turns reach nid. An "Update available" chooser can appear first (Esc skips it)/quit
opencodeopencode -s <id> with OPENCODE_CONFIG_CONTENT adding the nid MCP serverconversation resumed (it shows nid's first-turn instructions as the first message), nid tools work, TUI turns reach nid/exit
kimikimi -S <id>not verified; no nid tools (kimi has no command-line MCP injection)

These rows come from test/native-live.test.ts (NID_LIVE_NATIVE=1, 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_CODE_CHILD_SESSION, ...) 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.

AgentDriverNotes and known issues
Claude Codestream-jsonApprovals routed to nid; system prompt via --append-system-prompt; resumable.
Codexcodex app-serverSandboxed unless --yolo (see Codex safety mode); approvals routed; resumable.
opencodeACP (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 CodeACP (kimi acp)Same ACP mechanics as opencode (prefix system prompt, preemption); not live-checked in this round.
Custom ACP agentsACPGemini 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 agentspseudo-terminalAny 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_CODE_CHILD_SESSION, CLAUDE_CODE_SESSION_ID, 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_CODE_USE_BEDROCK or API keys are kept. The daemon log names the variables it removed.

test/opencode-live.test.ts (opt-in, NID_LIVE_OPENCODE=1, costs tokens) re-runs these opencode checks against the real CLI with its own daemon and NID_HOME; NID_LIVE_LOG=<file> dumps every event.