| name | nex-agentic |
| description | Use the Nex terminal multiplexer and its CLI to orchestrate multi-agent development workflows. Enables spawning named child panes, starting Claude agents in them, farming out parallel work, coordinating results via markdown files and direct pane messaging, AND driving / observing a web app from an agent via the `nex web` pane (open URL, capture page text or screenshot, drain console buffer, arm element picker to paste structured payloads into an agent pane). Trigger when: the user asks to "spawn agents", "fan out work", "create worker panes", "orchestrate panes", "use nex to coordinate", "multi-agent", "farm out tasks", or any variation of parallelizing work across Nex panes. Also trigger when an agent needs to "drive the browser", "watch the page console", "capture a screenshot of the app", "pick an element from the page", or coordinate with the web pane in any way.
|
Nex Agentic Development Skill
Orchestrate multi-agent development workflows using the Nex terminal
multiplexer. Spawn named child panes, start Claude agents in them, distribute
work, and collect results.
Prerequisites
You must be running inside a Nex pane. Verify with:
nex pane id
Exit 0 with the pane UUID on stdout means you're in Nex; exit 1 with
empty output means you're not, and every other nex command below will
silently no-op. This command is purely local (no socket, no shell $
expansion) so it allowlists cleanly as Bash(nex pane id).
Required up-front questions
Before spawning any panes, confirm two choices with the user. Skip a
question only if the answer is unambiguous from the invoking prompt
(e.g. "spawn headless workers with --dangerously-skip-permissions").
Otherwise ask via AskUserQuestion — do not assume defaults.
-
Execution mode — headless or interactive?
- Headless (
claude -p "<prompt>"): non-interactive, runs to
completion, exits when done. Best for fan-out: workers write
result files, coordinator polls. Default for most automation.
- Interactive (
claude then pane send the prompt): Claude
stays open in the pane for follow-ups. Use when the user wants to
supervise, iterate, or intervene mid-task.
-
Permission mode — which --permission-mode flag?
default — prompt on each tool use (safest; requires the user to
babysit each worker)
acceptEdits — auto-accept file edits, still prompt on Bash/other
plan — planning only, no writes
auto — auto-approves all tool calls with background safety
checks. Covers Bash/Edit/Read/web, unlike acceptEdits which is
file-writes only. Good middle ground for trusted fan-outs that
still want some guardrails
bypassPermissions (aka --dangerously-skip-permissions) — full
autonomy, no prompts, no checks. Common for trusted fan-outs in
worktrees or sandboxed VMs
dontAsk — auto-denies any tool not pre-approved via
/permissions or an allowlist. Strictest mode for unattended
orchestration with an explicit whitelist; non-whitelisted calls
silently fail
Ask both in a single AskUserQuestion call with two questions. Record
the answers and reuse them for every worker spawn in the session unless
the user changes them.
Once chosen, the worker-start command shape is:
nex pane send --target worker-1 claude -p --permission-mode <mode> "<prompt>"
nex pane send --target worker-1 claude --permission-mode <mode>
sleep 2
nex pane send --target worker-1 "<prompt>"
Nex CLI Reference
The nex CLI communicates with the Nex app over a Unix socket at /tmp/nex.sock.
Pane Commands
nex pane split [--direction horizontal|vertical] [--path /dir] [--name <label>] [--target <name-or-uuid>] [--workspace <name-or-uuid>] [--json]
nex pane create [--path /dir] [--name <label>] [--target <name-or-uuid>] [--workspace <name-or-uuid>] [--json]
nex pane close
nex pane name [--target <name-or-uuid>] [--workspace <name-or-uuid>] <label>
nex pane resize [--target <name-or-uuid>] [--workspace <name-or-uuid>] (--ratio <0..1> | --grow [amt] | --shrink [amt])
nex pane send [--bare] [--json] --target <label-or-uuid> [--workspace <name-or-uuid>] <command...>
nex pane list [--workspace <name-or-id> | --current] [--json] [--no-header]
nex pane capture [--target <name-or-uuid>] [--workspace <name-or-uuid>] [--lines N] [--scrollback]
nex pane move --target <name-or-uuid> (--above|--below|--left-of|--right-of) <anchor> [--workspace <name-or-uuid>] [--json]
nex pane move <left|right|up|down>
nex pane move-to-workspace --to-workspace <name-or-uuid> [--create]
nex group list [--json] [--no-header]
nex workspace list [--json] [--no-header]
nex pane id
Keep the layout readable (grid + prominent coordinator)
nex pane split always bisects the target pane 50/50 with no
rebalancing. So if you loop the same split direction against the
same pane — for w in ...; do nex pane split --direction horizontal; done — the 1st worker takes half, the 2nd a quarter, the 3rd an
eighth… after 4-5 workers the panes are unreadable slivers. Do NOT do
that. Instead, after spawning the workers, reflow with the built-in
tmux-style layouts:
nex layout select tiled
nex layout select main-vertical
nex layout select main-horizontal
Caveat — which pane becomes "main". layout select main-* makes
the currently focused pane the large "main" pane, and every
nex pane split moves focus to the pane it just created. So right
after the spawn loop the focused pane is the last worker, not the
coordinator — running main-vertical then would enlarge a worker, and
there is no nex pane focus CLI to re-focus the coordinator first.
The focus-independent lever is nex pane resize, which addresses the
pane by name. --ratio is the pane's share of its split, so use
a value > 0.5 to make the coordinator the larger side:
nex layout select tiled
nex pane resize --target coordinator --ratio 0.65
What resize does and doesn't do. pane resize only rebalances the
coordinator's immediate split (its boundary with the one neighbour it
shares that split with) — it does not make the coordinator dominate
the whole grid the way a main-* layout's root split does. So:
- Want a readable grid where the coordinator is merely the larger cell?
layout select tiled + pane resize --target coordinator --ratio 0.65.
Focus-independent, safe to run straight from the coordinator script.
- Want one pane that dominates the entire workspace?
layout select main-vertical — but it enlarges whatever is focused, so it's only
reliable when the coordinator is the focused pane at call time.
pane resize also lets a coordinator fine-tune any pane at any time
(--grow / --shrink to nudge, --ratio to set exactly) without
touching the GUI. The effective share clamps to [0.1, 0.9].
nex doctor — when CLI commands stop working
If nex commands suddenly start failing with Error: nex …: cannot reach Nex … or no response from Nex, run nex doctor first.
It runs seven named checks and prints [PASS|FAIL|WARN] <name>: <detail>
plus a concrete repair line for any failure:
transport — Unix socket path or TCP destination in use.
socket / resolve — file exists on disk (Unix) or hostname resolves (TCP).
ping — round-trip a ping command and parse the JSON reply.
process — scans the process list (ps) for Nex.app. If ping failed
but the process is up, the app is wedged (restart it); if no process, Nex
isn't running.
version — CLI version vs running-app version. Warn on drift (rebuild Nex).
hooks — the user-level Claude Code hook config (~/.claude/settings.json
plus settings.local.json) still carries every nex hook, and the
SessionStart entry fires for resumed sessions. Warn on drift: a stale
matcher means claude --continue / --resume panes never bind their
session id, so scripts that gate on agent_session_id != null silently
skip them (issue #181). Repair by re-running the bundled
install-hooks.sh. Skipped when no ~/.claude directory exists.
codex-hooks — same idea for ~/.codex/hooks.json (Codex CLI ≥ 0.142,
issue #101). Skipped when no ~/.codex directory exists; WARN-only.
Doctor cannot see codex's hook trust state — if the wiring passes but
codex panes never change status, run /hooks inside codex and trust
the nex hooks.
Pass --json for a machine-readable report. Exits non-zero only when a
check FAILs; WARNs are reported but leave the exit code 0. Use this as
the first triage step before restarting the app.
Every CLI error now emits an Error: …\nRepair: … pair pointing at the
matching repair step. Fire-and-forget commands (nex event …, hooks)
print a Warning: … to stderr but still exit 0 so Claude Code Stop
hooks etc. don't break — set NEX_SILENT=1 to suppress entirely.
nex event … (the Claude Code hook entrypoint) suppresses the warning
by default to avoid stderr spam when Nex is closed; set
NEX_VERBOSE_HOOKS=1 to surface them again.
pane list — reconcile with live state
pane list is Nex's read-only pane inventory. Use it whenever a coordinator
needs to know what panes actually exist right now — panes can be closed by
the user, crash, or be moved between workspaces. pane send exits non-zero
with a structured error on a missing/ambiguous target, but checking
pane list first lets a coordinator skip sends to dead workers and surface
a clearer message.
nex pane list
nex pane list --json
nex pane list --current
nex pane list --workspace nex
The default table's ID column prints the full pane UUID, so you can
copy it straight into --target <uuid> (the agent session id in
SESSION stays truncated — it's never a --target). Scripts should
still prefer --json.
Each JSON entry includes: id, label, type (shell / markdown /
scratchpad / diff / web), title, workspace_id, workspace_name,
optional group_id and group_name (both absent for top-level workspaces),
working_directory, git_branch, status (idle/running/
waitingForInput), agent_session_id, is_focused,
is_active_workspace, created_at, last_activity_at.
Exit codes: 0 on success (including empty list), 1 on usage error,
transport failure, or ok: false from the server. Empty output with
exit 1 and "upgrade required" on stderr means the running Nex is
older than v0.20 and doesn't support pane list.
Common recipes:
nex pane list --json | jq -r '.[].label | select(startswith("worker-"))'
alive=$(nex pane list --json | jq -r '.[].label')
for w in worker-1 worker-2 worker-3; do
echo "$alive" | grep -qx "$w" && echo "$w: alive" || echo "$w: gone"
done
nex pane list --json | jq -r '.[] | select(.label | startswith("worker-"))
| "\(.label)\t\(.status)"'
uuid=$(nex pane list --json | jq -r '.[] | select(.label == "build") | .id')
nex pane list --json | jq -r --arg pane "$NEX_PANE_ID" \
'.[] | select(.id == $pane) | .group_name // "top-level"'
group list — inspect workspace grouping
Use group list when an orchestrator needs the full sidebar grouping rather
than the parent of one pane's workspace. Groups and member workspaces retain
their sidebar order.
nex group list
nex group list --json
Each JSON group includes id, name, optional color, and workspaces.
Each workspaces entry contains the member workspace's id and name.
workspace list — inventory every workspace
group list only shows grouped workspaces. When an orchestrator needs the
full workspace inventory (top-level and grouped together, in sidebar order),
use workspace list. Unlike the sidebar itself, members of a collapsed group
are still listed.
nex workspace list
nex workspace list --json
Each JSON entry includes id, name, color, pane_count, is_active,
and optional group_id / group_name (both absent for a top-level
workspace). The group_name field matches the one on pane list, so
.group_name // "top-level" answers "which group is this workspace in".
Event Commands (Agent Lifecycle)
nex event start
nex event stop
nex event error --message "..."
nex event notification --title "..." --body "..."
nex event session-start
nex event session-end
nex event start --agent codex
session-start / session-end read the session_id from the hook's
stdin JSON and are wired to Claude Code's SessionStart / SessionEnd
hooks by install-hooks.sh. session-end clears the pane's tracked
session id (only when it still matches the ending session) so Nex does
not claude --resume a session that has already exited.
On stop / notification, nex event also reads the hook payload's
background_tasks array — Claude Code's live snapshot of the
run_in_background shells and background subagents still in flight — and
forwards the running count to Nex. While that count is non-zero the pane
stays running (not "waiting for input") and the synthetic
"waiting" notification is suppressed, so a pane whose turn ended but
still has background work in flight reads correctly. The count shows in
nex pane list --json as background_tasks and in the pane header as
"· N running". No extra hook wiring is needed — it rides on the existing
Stop / Notification hooks.
Codex CLI panes (issue #101): workers running Codex CLI (≥ 0.142) get
the same native status tracking — install-hooks.sh wires Codex's
UserPromptSubmit / Stop / PermissionRequest / SessionStart hooks
to these verbs with --agent codex. pane list --json then reports an
agent field ("claude" / "codex"). Two orchestration caveats:
agent is the last-known kind for the pane, not proof an agent is
attached right now (codex has no SessionEnd, so its session id and
kind persist after codex exits).
- Run codex workers in their own panes. Codex hooks inherit
NEX_PANE_ID, so a claude agent shelling out to codex exec inside
its own pane overwrites that pane's session id and kind with the
throwaway codex session's.
Workspace Commands
nex workspace create [--name "..."] [--path /dir] [--color blue|green|red|yellow|purple|orange|pink|gray] [--group <name>] [--profile <name>] [--worktree <name> [--branch <name>] [--repo <path>] [--update-main]] [--json]
nex workspace profile <name-or-id> (<profile> | --clear)
nex workspace delete <name-or-id> [<name-or-id> ...] [--force|-y] [--prune-worktree] [--json]
Workspace Profiles (multi-account env injection)
A workspace profile is a named env-var set defined in
~/.config/nex/config, one variable per line (repeated lines with the
same name merge, later lines win). Manage them by hand in the config
file or via Settings → Profiles (both write the same lines):
profile = work:CLAUDE_CONFIG_DIR=~/.claude-accounts/work
profile = personal:CLAUDE_CONFIG_DIR=~/.claude-accounts/personal
Assign one to a workspace (workspace create --profile, workspace profile, the inspector picker, or the sidebar context menu) and every
pane PTY spawned in that workspace from then on gets those vars plus
NEX_PROFILE=<name>. The flagship use case is running multiple Claude
Code accounts side by side — one workspace per CLAUDE_CONFIG_DIR —
and the injection survives restart: restored panes respawn with the
profile env, so auto-resumed agent sessions (claude --resume) stay
on their workspace's account.
The built-in default profile is the baseline: a workspace with no
explicit assignment is on default, so every pane always carries
NEX_PROFILE (default unless assigned). It always exists (virtual
until customized), can't be renamed or deleted in Settings, and adding
vars to it (Settings → Profiles or profile = default:KEY=value lines)
applies them to every unassigned workspace. Selecting default in the
UI, nex workspace profile <ws> default, and --clear are equivalent.
Rules worth knowing:
- Spawn-time only. Assignment changes never touch live PTYs; open
a fresh pane after assigning. Corollary:
pane move-to-workspace
moves a live PTY, so the pane keeps its birth env even when the
destination workspace has a different profile. Workspaces created
with a profile up front (the New Workspace sheet's Profile picker or
workspace create --profile) spawn their first pane already on it.
- Profile definitions are re-read from the config file on every spawn,
so editing values applies to new panes without restarting Nex.
- Profile names cannot contain
: or =; values may contain both.
Quotes in values are literal (no stripping). A leading ~ in the
value is tilde-expanded.
NEX_PANE_ID and PATH are reserved — profile entries for them are
ignored.
- Assigning a profile with no definitions in the config is allowed
(
workspace create --profile works before the config lines exist);
panes then get only NEX_PROFILE=<name> and a warning is logged.
workspace profile is fire-and-forget: an unknown or ambiguous
workspace name is a silent no-op (same semantics as workspace move). UUIDs always win over names.
File Commands
Open a file in the right pane type — handy for surfacing a worker's
output (a rendered report, a diff, an HTML artifact) to the human
without leaving Nex. Relative paths resolve against the caller's cwd,
and the pane lands in the caller's workspace (via NEX_PANE_ID).
nex open [--here] <path-or-url>
nex md [--here] <file>
nex diff [<path>]
So a worker that just wrote report.html can nex open report.html
to render it in a web pane, or nex open summary.md to drop a
live-reloading markdown preview beside the terminal — no manual
file:// or pane-type juggling. The same command also opens a live
site: nex open localhost:3000 or nex open example.com drops the
app into a web pane without reaching for the longer nex web open.
Web Pane Commands
A web pane is a full in-pane browser with URL bar, multi-tab
strip, console capture, an element picker, private mode, and a
cookies editor. The CLI surface is the orchestration angle: an
agent in pane A can drive or observe a web app in pane B with
semantic verbs over the same DOM that the picker sees.
The agent-driving surface splits into four layers. Reach for the
lowest layer that solves your problem; exec is the escape hatch.
| Layer | Verbs | Use when |
|---|
| Action | click, type, select, scroll, hover, key | mutate the page |
| Query | text, attr, count, exists, dom | read state |
| Wait | wait | block until DOM / URL transition fires |
| Exec | exec | compose actuator calls + custom logic in one call |
Infrastructure (pane + tabs + capture + cookies)
nex web open [--private] <url>
nex web navigate <url> [--target <name-or-uuid>] [--workspace <name-or-uuid>]
nex web url --target <name-or-uuid> [--workspace <name-or-uuid>]
nex web back --target <name-or-uuid>
nex web forward --target <name-or-uuid>
nex web reload --target <name-or-uuid> [--hard]
nex web capture --target <name-or-uuid> --mode meta|text|screenshot
nex web tabs --target <name-or-uuid> [--json] [--no-header]
nex web tab-new --target <name-or-uuid> [<url>] [--no-focus]
nex web tab-close --target <name-or-uuid> <ref>
nex web tab-select --target <name-or-uuid> <ref>
nex web console --target <name-or-uuid> [--since N] [--level error|warn|info|log|debug] [--clear] [--json]
nex web inspect --target <name-or-uuid> [--send-to <pane>] [--submit] [--disarm]
nex web inspect-result --target <name-or-uuid> [--clear] [--json]
nex web private on|off --target <name-or-uuid>
nex web cookies list --target <name-or-uuid> [--json]
nex web cookies clear --target <name-or-uuid> [--domain X] [--all]
nex web cookies delete <name> --target <name-or-uuid> [--domain X]
Action verbs
nex web click --target <X> <selector> [--double] [--right] [--at x,y] [--json]
nex web type --target <X> <selector> <text> [--submit] [--no-replace] [--json]
nex web select --target <X> <selector> <value-or-label> [--json]
nex web scroll --target <X> <selector> [--top|--bottom|--smooth] [--json]
nex web hover --target <X> <selector> [--json]
nex web key --target <X> <key-name> [--selector <sel>] [--json]
click always synthesises a full pointerdown → mousedown →
pointerup → mouseup envelope (with real centre coords) so
libraries that listen for pointer / mouse events (react-dnd,
framer-motion, custom dropdowns) fire. --at x,y overrides the
centre offset and routes the final click through a synthesised
MouseEvent('click') so listeners read the coords; without
--at the final click goes through target.click() (form /
anchor / disabled semantics intact, but clientX/Y = 0 on the
click event itself).
type uses the prototype native setter so React / Vue / Svelte
controlled inputs accept the write, then dispatches input +
change. --submit fires Enter and form.requestSubmit().
--no-replace appends instead of overwriting.
select matches <option>s by value first, then by visible
label.
key accepts enter, return, tab, escape/esc, space,
backspace, delete, up/down/left/right (also
arrowup etc.), home, end, pageup, pagedown. Without
--selector the keystroke goes to document.activeElement.
Query verbs
nex web text --target <X> <selector> [--max-bytes N] [--json]
nex web attr --target <X> <selector> <attribute> [--json]
nex web count --target <X> <selector> [--json]
nex web exists --target <X> <selector>
nex web dom --target <X> <selector> [--max-bytes N] [--json]
text clips at 1MB by default and reports truncated in the
envelope; dom at 16KB; attr at 64KB.
attr distinguishes attribute absent (exit 1, no output) from
attribute present with empty value (exit 0, empty stdout) via a
present field in --json mode.
exists is the cheap one-shot "is it there?" check — exit code
is the signal, no stdout. For polling, prefer wait.
Wait
nex web wait --target <X>
(--selector <sel> | --url-match <substring-or-regex>)
[--for visible|hidden|exists|count=N|text=X]
[--timeout 10]
[--json]
One socket roundtrip blocks until the condition is met or the
timeout fires. Polls inside the page at 100ms — replaces shell
until ...; do sleep 1; done loops at significantly lower
overhead (a single 100ms tick past the event vs. the next 1s
sleep boundary).
Conditions:
--for | Meaning |
|---|
exists (default with --selector) | selector resolves to a non-null element |
visible | element isConnected AND getClientRects().length !== 0 AND getComputedStyle().visibility !== 'hidden' (catches display:none via the no-rects path; correctly classifies position:fixed overlays as visible) |
hidden | element absent OR not visible (above) |
count=N | findAll(selector).length === N |
text=X | element matches AND its trimmed textContent equals X (or matches /regex/flags) |
url-match (default with --url-match) | location.href matches the substring or regex |
Exit 0 on match (prints matched <condition> in <N> ms); exit 1
on timeout (nex web wait: timeout to stderr; waited_ms is in
the --json envelope).
Selector forms
A single string carries the selector. Four forms, one CLI flag:
| Form | Example | Behaviour |
|---|
css:<sel> | css:button.primary | document.querySelector(sel) |
text:<exact> | text:Add to order | smallest element whose trimmed textContent equals <exact> |
text:/<pattern>/<flags> | text:/^Add to (cart|order)$/i | same, regex matching |
role:<role>[:name=<name>] | role:button:name=Confirm | first element with the ARIA role (explicit or implicit) and matching accessible name |
| bare | .foo, #bar, [data-x], Add to order | auto: CSS if starts with . # [ > * :, otherwise text: |
Text matching uses the smallest-enclosing-element rule (Playwright-
style): text:Submit on a page with <button>Submit</button>
resolves to the button, not <html> or <body>. Skips <script>,
<style>, and <template> subtrees.
Advanced: web exec for composition
When you need to compose several actuator calls plus custom JS
logic in one CLI invocation, reach for exec:
nex web exec --target <X> (--file <path> | <js>) [--timeout 30] [--json]
The author script runs inside an async wrapper with three aliases
bound:
| Alias | Resolves to |
|---|
$ | __nexAct.find (single element by selector) |
$$ | __nexAct.findAll |
nex | the full __nexAct namespace (nex.click, nex.type, nex.wait, ...) |
A single trailing expression returns its value implicitly. Source
containing return / throw / if / for / while / switch
/ try / do / let / const / var switches to statement-
body mode where the author owns the explicit return.
nex web exec --target X 'document.title'
nex web exec --target X 'window.__REDUX__.getState().cart.items.length'
nex web exec --target X '$$("li.product").map(e => e.dataset.sku)'
nex web exec --target X '
await nex.wait({selector: "text:Add to order", for: "visible"});
await nex.click("text:Add to order");
await nex.wait({selector: "[role=alert]", for: "exists"});
return nex.text("[role=alert]").text;
'
Reply envelope matches every other actuator verb: {ok:true, result:<json>} on success; {ok:false, error:<message>, js_error:{name, message, line, column}} on a page-side exception.
--timeout extends the CLI's socket read budget (default 30s) so
exec scripts with embedded nex.wait(...) calls don't get cut
off; the JS-side wait timeout is independent.
All web verbs follow the same --target / --workspace
scoping as pane send: label targets need an origin
pane or --workspace; UUID targets resolve globally. All are
reply-allowlisted — they return JSON and the CLI exits non-zero on
failure.
Known limitation. WKUserContentController user-script injection
is unreliable on data: URLs (opaque origin). Console capture, the
element picker, and the actuator (__nexAct.*, hence every action /
query / wait / exec verb) rely on injected scripts, so reach for a
real http(s):// or file:// URL when validating those paths.
Smoke tests can still use data: URLs for navigation, capture --mode text, and tab management.
Key Behaviors
-
Target resolution for pane send / pane send-key / pane close /
pane capture / pane split / pane create / pane name / pane resize /
pane move --target:
UUIDs are matched globally. Labels are scoped to the sender's own
workspace (via NEX_PANE_ID) unless --workspace <name-or-id> is
passed; a bare label without either explicit or implicit scope is
rejected, so coordinators can't silently route into the
wrong workspace.
-
Works from outside Nex: pane send / split / create /
name (like send-key / close / capture) no longer require
NEX_PANE_ID. From a plain shell, address a pane with a UUID --target,
or --target <label> --workspace <name-or-id>; create/split also accept
--workspace alone. These are request/response: success prints the resolved
(or new) pane id, failure exits non-zero with an actionable error — so an
orchestrator can tell a real delivery from a no-op. Add --json for a
machine-readable reply.
-
--name flag: names the new pane at creation time so it can be
immediately targeted by pane send.
-
--target flag: on split/create, specifies which existing pane to
split by name or UUID (defaults to the current pane via NEX_PANE_ID). This
lets a coordinator split any named pane, not just itself.
-
Silent fallback vs. loud failure: the fire-and-forget event hooks
(nex event …) still exit 0 when Nex is unreachable. The request/response
pane commands above instead exit non-zero with an Error: … / Repair: …
message when the target can't be resolved or Nex isn't running.
-
pane send mechanics: text is sent directly to the target pane's PTY
followed by an Enter keypress. If a shell is running, the text executes as a
shell command. If Claude is running in interactive mode, the text becomes a
prompt.
-
TUI submit caveat: when the target opts into bracketed-paste
mode (Claude Code, vim, ...), the trailing Enter from pane send is
intermittently captured inside the paste envelope and the message lands as
pasted text without submitting. For interactive Claude/TUI workers, prefer
the explicit two-step submit:
nex pane send --target worker-1 "<prompt>"
nex pane send-key --target worker-1 enter
pane send-key accepts enter, return, tab, escape/esc, space,
backspace, up/down/left/right, and ctrl-c. It uses the same
--target / --workspace resolution as pane send.
Broadcasting to every worker pane (pane sync)
When you want the same keystrokes (e.g. /compact, clear, an interrupt)
to land in every worker pane of the workspace at once, toggle tmux-style
synchronise-input:
nex pane sync on
nex pane sync off
nex pane sync toggle
nex pane sync status --json
Opt a single pane out of the active sync group (handy for a coordinator
pane you don't want broadcasting into):
nex pane sync exclude --target coordinator
nex pane sync include --target coordinator
Scope defaults to the calling pane's workspace via NEX_PANE_ID. Pass
--workspace <name-or-uuid> to target another workspace from an
external script. New panes opened while sync is on auto-join the group;
closed panes drop out automatically.
Multi-Agent Workflow Patterns
Pattern 1: Fan-Out with Markdown Communication (Recommended)
The coordinator creates named child panes, assigns tasks via markdown files,
and collects results from markdown output files.
Step 1: Set up the workspace
nex pane name coordinator
mkdir -p .nex-tasks .nex-results
Step 2: Write task files
Write a markdown file for each worker describing its task:
Each task file should include:
- Clear description of the work to do
- Input files/context needed
- Expected output format
- Where to write results (e.g.,
.nex-results/worker-1.md)
Step 3: Spawn named worker panes
nex pane split --name worker-1
nex pane split --name worker-2
nex pane split --name worker-3
nex layout select tiled
nex pane resize --target coordinator --ratio 0.65
Timing: add a short delay (1-2 seconds) between spawning panes to allow
each surface to initialize before sending commands.
Step 4: Start Claude agents in worker panes
sleep 2
nex pane send --target worker-1 claude -p "Read .nex-tasks/worker-1.md and complete the task described. Write your results to .nex-results/worker-1.md"
sleep 1
nex pane send --target worker-2 claude -p "Read .nex-tasks/worker-2.md and complete the task described. Write your results to .nex-results/worker-2.md"
sleep 1
nex pane send --target worker-3 claude -p "Read .nex-tasks/worker-3.md and complete the task described. Write your results to .nex-results/worker-3.md"
Step 5: Poll for results
WORKERS=(worker-1 worker-2 worker-3)
while true; do
all_done=true
for w in "${WORKERS[@]}"; do
[ -f ".nex-results/$w.md" ] || { all_done=false; break; }
done
$all_done && break
alive=$(nex pane list --json | jq -r '.[].label')
for w in "${WORKERS[@]}"; do
if ! echo "$alive" | grep -qx "$w" && [ ! -f ".nex-results/$w.md" ]; then
echo "worker $w disappeared before producing output" >&2
exit 1
fi
done
sleep 5
done
Then read each result file and synthesize.
Step 6: Clean up
nex pane send --target worker-1 exit
nex pane send --target worker-2 exit
nex pane send --target worker-3 exit
Pattern 2: Direct Messaging Between Panes
For simpler coordination, send commands directly between panes without markdown
files. Best for short, one-off commands.
nex pane split --name build
sleep 2
nex pane send --target build make build
nex pane split --name test
sleep 2
nex pane send --target test make test
Pattern 3: Interactive Agent Swarm
Start multiple Claude agents in interactive mode that can message each other.
nex pane split --name reviewer --direction vertical
nex pane split --name coder --direction horizontal
sleep 2
nex pane send --target reviewer claude
sleep 2
nex pane send --target reviewer "You are a code reviewer. Review any code written to .nex-results/code.md and write your review to .nex-results/review.md"
nex pane send --target coder claude
sleep 2
nex pane send --target coder "You are a coder. Write code for the task in .nex-tasks/feature.md and save it to .nex-results/code.md"
Pattern 4: Agent driving / observing a web app via web pane
The web pane closes the loop where an agent in one pane drives or
observes a web app in another. The action / query / wait verbs
cover the common case; reach for web exec only when composing
them with custom logic. Common shapes:
(a) Drive a flow with semantic verbs. No JS authoring needed
— the verbs cover the typical "find element, act on it, wait for
the response, read the result" loop. Example: add an item to a
restaurant cart from a fresh session.
nex web open --private https://example-restaurant.test
WEB=<the-printed-uuid>
nex web wait --target $WEB --selector "text:Choose your table" --for visible
nex web type --target $WEB "css:input[type=text]" "5"
nex web click --target $WEB "text:Confirm"
nex web wait --target $WEB --selector "text:Margherita" --for visible
nex web click --target $WEB "text:Margherita"
nex web wait --target $WEB --selector "text:Add to order" --for visible
nex web click --target $WEB "text:Add to order"
nex web wait --target $WEB --selector "[role=alert]" --for exists
nex web text --target $WEB "[role=alert]"
(b) Capture-then-fix loop — agent runs the app in a web pane,
polls its console for errors, fixes the code, reloads, repeats.
nex web open http://localhost:3000
nex pane create --name dev-agent
sleep 2
nex pane send --target dev-agent claude --permission-mode acceptEdits
(c) Compose multi-step flows in one call via web exec. Use
this for branches that depend on intermediate values, framework
state reads, or anything that would otherwise round-trip through
the shell three times.
nex web exec --target $WEB '
// Add each available size to the cart until we hit 3 items.
for (const size of ["Small", "Medium", "Large"]) {
await nex.click("text:" + size);
await nex.wait({selector: "[role=alert]", for: "exists", timeout: 3000});
await nex.wait({selector: "[role=alert]", for: "hidden"});
const count = $$("li.cart-item").length;
if (count >= 3) break;
}
return $$("li.cart-item").map(e => e.dataset.sku);
'
(d) Click-to-locate-source — the human clicks an element on
the page, an agent gets the selector + outerHTML and finds the
code that rendered it. Still the right tool when the agent doesn't
know the selector up front and a human is at the keyboard.
nex web inspect --target <web-uuid> --send-to dev-agent
The pasted payload includes selector, xpath, tag, id,
outer_html (clipped 16KB), attributes, rect, surrounding
text, context_html (clipped 4KB), and url. ANSI / C0 control
bytes are stripped before paste so the agent's prompt can't be
smuggled into.
(e) Headless visual diff — capture screenshots before/after a
change to gate a deploy.
nex web capture --target <web-uuid> --mode screenshot
(f) Sandbox a flaky integration — open the integration target
in a private web pane so the agent's exploration doesn't pollute
the user's real session.
nex web open --private https://staging.example.com
Reach order: lowest layer that works (see the table at the
top of the Web Pane section); exec for composition; inspect
when a human is at the keyboard and the agent doesn't know the
selector. The picker auto-disarms on tab switch / close / Escape,
sticky mode is only reachable via the chrome's batch-annotate
panel, and page JS cannot spoof inbound payloads.
Task File Format
When creating task files for workers, use this structure:
# Task: <short description>
## Context
<background information, relevant files, architecture notes>
## Objective
<clear, specific description of what to accomplish>
## Inputs
- <file paths, data sources, or references the worker needs>
## Expected Output
- Write results to: `.nex-results/<worker-name>.md`
- Create/modify source files as described below
## Constraints
- <any boundaries, e.g., "do not modify files outside src/components/">
- <time/scope limits>
Result File Format
Workers should write results in this format:
# Result: <task description>
## Status
<completed | partial | failed>
## Summary
<1-3 sentence overview of what was done>
## Changes Made
- <list of files created/modified with brief descriptions>
## Notes
- <any issues encountered, decisions made, or follow-up needed>
Practical Tips
-
Always name your coordinator pane first (nex pane name coordinator)
so workers can message back if needed.
-
Use claude -p for workers (print mode). It runs non-interactively
with full tool access and exits when done. This is better than interactive
mode for autonomous workers.
-
Add delays between pane operations. The terminal surfaces need time to
initialize. A 1-2 second sleep between pane split and pane send prevents
race conditions.
-
Use the .nex-tasks/ and .nex-results/ convention for the shared
communication directory. This keeps agent artifacts organized and
.gitignore-able.
-
Poll with sleep loops for results, not busy-waits. Check every 5-10
seconds for result files.
-
Keep task descriptions self-contained. Workers run in fresh Claude
sessions with no shared context. Include all necessary information in the
task file.
-
Workers should use absolute paths or paths relative to the project root
to avoid working directory confusion.
-
For large fan-outs (>4 workers), create panes in batches to avoid
overwhelming the terminal. Spawn 3-4, wait for them to complete, then spawn
the next batch.
-
Reflow the layout after spawning; never loop a single split direction.
nex pane split bisects 50/50 with no rebalancing, so repeated
same-direction splits collapse into unreadable slivers. After the spawn
loop, run nex layout select tiled for a balanced grid, then
nex pane resize --target coordinator --ratio 0.65 to keep the coordinator
prominent. Prefer pane resize over layout select main-* for the
coordinator: main-* enlarges the focused pane, and focus sits on the
last-spawned worker after the loop.
Coordinator Script Template
Here is a complete coordinator script you can adapt:
#!/bin/bash
set -e
PROJECT_DIR="$(pwd)"
TASK_DIR="$PROJECT_DIR/.nex-tasks"
RESULT_DIR="$PROJECT_DIR/.nex-results"
WORKERS=("worker-1" "worker-2" "worker-3")
nex pane name coordinator
mkdir -p "$TASK_DIR" "$RESULT_DIR"
rm -f "$RESULT_DIR"/*.md
for worker in "${WORKERS[@]}"; do
nex pane split --name "$worker"
sleep 2
done
nex layout select tiled
nex pane resize --target coordinator --ratio 0.65
for worker in "${WORKERS[@]}"; do
nex pane send --target "$worker" "cd $PROJECT_DIR && claude -p 'Read $TASK_DIR/$worker.md and complete the task. Write results to $RESULT_DIR/$worker.md'"
sleep 1
done
echo "Waiting for workers to complete..."
while true; do
all_done=true
for worker in "${WORKERS[@]}"; do
[ -f "$RESULT_DIR/$worker.md" ] || { all_done=false; break; }
done
$all_done && break
alive=$(nex pane list --json | jq -r '.[].label')
for worker in "${WORKERS[@]}"; do
if ! echo "$alive" | grep -qx "$worker" && [ ! -f "$RESULT_DIR/$worker.md" ]; then
echo "worker $worker disappeared before producing output" >&2
exit 1
fi
done
sleep 5
done
echo "All workers complete. Results in $RESULT_DIR/"
Error Handling
- If a worker fails, its result file won't appear. The coordinator should
implement a timeout (e.g., 5 minutes) and report which workers didn't
complete.
- Use
nex pane list to detect dead workers before timeout. If a
worker's label no longer appears in the list, the pane was closed
externally and its result file will never arrive — bail out instead of
polling forever.
- Workers can signal errors via
nex event error --message "description".
- Workers can send desktop notifications via
nex event notification --title "Done" --body "Task complete".