Agent Runner
Launch any CLI coding agent with live status pills — registry at agents.toml, real-time hooks, output-silence fallback.
Launching agents
Launch Claude Code, Codex, Gemini, Grok, OpenCode, Pi — or anything you add — from the top bar. Each launch spawns the agent in its own PTY (optionally inside a worktree) and registers it with the status tracker.
Every running agent shows a status pill with one of seven states: working (yellow spinner) · done (emerald check) · blocked / waiting / permission / interrupted (red) · idle (gray). Click a pill to jump to that agent's tab.
Status comes from two sources:
- Hook pushes — real-time events POSTed by the agent (below)
- Output-silence fallback — PTY output means
working; 2 s of silence meansidle; process exit meansdone
The registry: agents.toml
Agents are defined in agents.toml in xNAUT's config directory (~/Library/Application Support/xnaut/ on macOS, ~/.config/xnaut/ on Linux). It's seeded with six defaults on first launch. Full per-agent schema:
[[agents]]
id = "myagent" # stable identifier
label = "My Agent" # UI label
detect_cmd = "myagent" # binary checked on PATH
launch_cmd = "myagent" # binary actually executed
extra_args = [] # args before the prompt
expected_process = "myagent"
prompt_injection_mode = "argv" # see below
# draft_prompt_flag = "--prefill" # for flag-prompt modes
# draft_prompt_env_var = "PROMPT" # also export prompt as env var
# preflight_trust = "codex" # cursor | copilot | codex
[agents.env] # extra env vars on launch
# OPENAI_BASE_URL = "http://localhost:8090/v1"The five prompt-injection modes — every coding CLI has a quirk; one of these covers it:
| Mode | Behavior | Default agent using it |
|---|---|---|
argv | prompt as last positional argument | codex |
flag-prompt | <flag> <prompt> (e.g. --prefill) | claude |
flag-prompt-interactive | flag + prompt, then -i | gemini |
flag-interactive | just -i; prompt sent later | grok |
stdin-after-start | prompt pasted to stdin (bracketed-paste) 1.5 s after launch | opencode, pi |
Real-time hooks
Claude Code: automatic since 1.9.3. When you launch claude through the Agent Runner, xNAUT writes Stop and Notification hooks into the worktree's .claude/settings.local.json — merge-safe, idempotent, and in the git-ignored local settings file so your committed project config is never touched. The pill flips to done the instant Claude stops and shows permission when it asks for one, instead of waiting on the silence timeout. The hooks call a bundled script (Resources/scripts/hooks/xnaut-hook.sh) that is silent and non-blocking — it can never wedge the agent.
Any other agent: one curl. At launch, xNAUT injects XNAUT_HOOK_URL and XNAUT_HOOK_TOKEN into the agent's environment. Report state from any hook or wrapper:
curl -s -m 2 "$XNAUT_HOOK_URL" \
-H "X-Xnaut-Session: $XNAUT_HOOK_TOKEN" \
-H "Content-Type: application/json" \
-d '{"state": "done"}'- Endpoint:
POST http://127.0.0.1:51737/v1/hook— loopback only. The port is persisted in settings (default 51737; if taken, a random port is used and the app prints the live URL on startup). stateis required and lowercase (working,blocked,waiting,done,idle,permission,interrupted). Optional fields:prompt,tool_name,interrupted.- The token is a per-session UUID — one per launched agent.
Agents without hooks keep working via the output-silence fallback.