summon-cc

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

summon-cc — conduct Claude Code sessions from any agent

summon-cc — 从任意 agent 管理 Claude Code 会话

Deprecated. summon-cc is archived and unmaintained. Its successor is conclave (https://github.com/anfreire/conclave), which spawns agents on Claude Code, Codex or OpenCode through one CLI, with no tmux pane to drive. Prefer it for new work; what follows still describes this skill as it stands.
You are the orchestrator. You spawn full interactive Claude Code sessions ("workers") in detached tmux panes, hand each a scoped prompt, and supervise them through append-only file signals. You orchestrate; workers do the work. Three laws make this cheap and calm:
  1. Never parse the terminal. The signals carry everything; the pane is a debug surface, read only when a signal says something is wrong.
  2. Never idle-poll. One waiter per worker blocks silently until something happens.
  3. Act on signals, verify by signals. Every move — spawn, prompt, answer — leaves a signal proving it landed. The scripts verify their own steps; you verify yours.
Any agent on any harness can be the orchestrator — every capability here is a plain shell command. Only the worker side is Claude Code, and that's the point: this skill hands you Claude Code sessions as a building block, unrestricted — what you build with them (pipelines, review loops, fan-outs, watchdogs) is your call.
已弃用。 summon-cc 已归档且不再维护。它的后继项目是 conclave(https://github.com/anfreire/conclave),可通过一个 CLI 在 Claude Code、Codex 或 OpenCode 上启动 agent,无需驱动 tmux 窗格。新任务建议优先使用它; 以下内容仍按此 skill 当前的状态进行描述。
你是编排者。你会在分离的 tmux 窗格中启动完整的交互式 Claude Code 会话(“worker”), 向每个 worker 分配范围明确的 prompt,并通过仅追加的文件信号来监管它们。 你负责编排;worker 负责执行。让这一切保持低成本且平稳的三条法则是:
  1. 绝不解析终端。 信号承载全部信息;只有在信号表明出问题时,才把窗格当作调试界面读取。
  2. 绝不空闲轮询。 每个 worker 配一个 waiter,静默阻塞,直到有事件发生。
  3. 根据信号行动,根据信号验证。 每一步操作——启动、提示、回答——都会留下 信号,证明操作已生效。脚本会验证自身步骤;你也验证自己的步骤。
任意 harness 上的任意 agent 都可以充当编排者——这里的所有能力都只是普通 shell 命令。只有 worker 一侧是 Claude Code,而这正是重点:此 skill 将 Claude Code 会话作为一个不受限制的构建模块交给你——你用它们构建什么(流水线、审查循环、任务分发、 watchdog)由你决定。

Requirements

环境要求

  • Linux or macOS;
    bash
    ,
    tmux
    ,
    jq
    , and a
    claude
    CLI with
    --settings
    and
    --effort
    (any current one —
    claude --help
    to confirm).
  • Nothing to install or wire. Spawn injects the six lifecycle hooks per worker via
    --settings
    on the launch line: no settings file of the user's is ever touched, and sessions you didn't summon never load these hooks at all.
  • The two scripts sit next to this SKILL.md in
    scripts/
    ; invoke them by absolute path (your working directory is usually elsewhere).
    <skill-dir>
    below means this skill's directory.
  • Linux 或 macOS;需要
    bash
    、
    tmux
    、
    jq
    ,以及支持
    --settings
    和
    --effort
    的
    claude
    CLI(任意当前版本均可——运行
    claude --help
    确认)。
  • 无需安装或配置任何内容。 Spawn 会在启动命令中通过
    --settings
    为每个 worker 注入六个生命周期 hooks:绝不会触碰用户的任何 settings 文件,你也未 summon 的会话 绝不会加载这些 hooks。
  • 两个脚本位于此 SKILL.md 旁边的
    scripts/
    中;必须通过绝对路径调用(你的工作目录 通常位于其他位置)。下文的
    <skill-dir>
    表示此 skill 所在目录。

Quickstart — one worker, start to finish

快速入门——一个 worker,从启动到完成

brief="$(mktemp)"   # a fresh file per worker
printf '%s\n' 'Fix the failing test in src/foo. END YOUR TURN with a one-paragraph report.' > "$brief"
bash <skill-dir>/scripts/spawn.sh fixer "$brief"
brief="$(mktemp)"   # a fresh file per worker
printf '%s\n' 'Fix the failing test in src/foo. END YOUR TURN with a one-paragraph report.' > "$brief"
bash <skill-dir>/scripts/spawn.sh fixer "$brief"

→ SUMMONED id=fixer-6a53bd1e00002f40 state=/home/you/.local/state/summon/workers/fixer-6a53bd1e00002f40

→ SUMMONED id=fixer-6a53bd1e00002f40 state=/home/you/.local/state/summon/workers/fixer-6a53bd1e00002f40

bash <skill-dir>/scripts/wait.sh fixer-6a53bd1e00002f40 # silent until → EVENT=STOP + the report tmux kill-session -t '=fixer-6a53bd1e00002f40' # done with it ('=' = exact match)

`SUMMONED` prints your two handles: the **id** (wait, send-keys, kill) and the **state
dir** (every signal file) — `<id>` and `<state>` below. Everything composes these two.
bash <skill-dir>/scripts/wait.sh fixer-6a53bd1e00002f40 # silent until → EVENT=STOP + the report tmux kill-session -t '=fixer-6a53bd1e00002f40' # done with it ('=' = exact match)

`SUMMONED` 会打印两个句柄:**id**(用于 wait、send-keys、kill)和**状态目录**
(包含所有信号文件)。下文所有操作都围绕这两项展开。

Spawn

Spawn

Write the worker's prompt to a file (multi-line pastes as one block). It must not begin with
/
,
!
, or
#
— those are TUI input prefixes (slash command / bash mode / memory), so the text would run as a command instead of submitting; spawn rejects such files loudly. Then:
bash <skill-dir>/scripts/spawn.sh <name> <prompt-file> [effort] [cwd] [permissions]
  • effort
    — handed to
    claude --effort
    verbatim: claude owns the vocabulary (
    claude --help
    lists it);
    default
    (the default) keeps the session's own setting. Fire-and-forget tuning: an unknown value doesn't abort — claude silently runs at its default, with no pane warning; the STOP line shows the effective effort (
    effort=…
    ) as claude reports it — the one place to confirm a value took.
  • cwd
    — the worker's project dir (default: yours).
  • permissions
    —
    bypass
    (default →
    --dangerously-skip-permissions
    : a detached pane can't answer permission prompts, so workers self-gate instead — see Worker prompts) ·
    inherit
    (no flag: claude's own default in that cwd) · anything else is handed to
    --permission-mode
    verbatim (
    claude --help
    lists the modes). A bad mode fails boot with claude's own error.
  • SUMMON_CLAUDE_ARGS
    — optional env var of extra flags appended verbatim to the launch line:
    --model haiku
    for cheap scouts,
    --resume <session_id>
    to revive a dead worker's conversation in a fresh pane (its session_id is in
    <state>/events.jsonl
    ).
Spawn is self-verifying end to end (boot, paste, submit — all against hook signals, with its own retries). If it fails it says why, loudly, with the pane tail and the remedy; the two first-run consent dialogs are the usual cause (Gotchas).
将 worker 的 prompt 写入一个文件(多行内容应作为一个整体粘贴)。文件内容不能 以
/
、
!
或
#
开头——这些是 TUI 输入前缀(斜杠命令 / bash 模式 / memory),因此文本会被当作命令执行,而不是提交;spawn 会明确拒绝此类文件。然后运行:
bash <skill-dir>/scripts/spawn.sh <name> <prompt-file> [effort] [cwd] [permissions]
  • effort
    ——原样传给
    claude --effort
    :具体词汇由 claude 决定 (
    claude --help
    会列出);
    default
    (默认值)会保留会话自身的设置。这是一种即发即弃式 调优:未知值不会中止运行——claude 会在默认级别静默运行,窗格中不会显示警告; STOP 行会按照 claude 的报告显示实际生效的 effort(
    effort=…
    ),这是确认参数是否 生效的唯一位置。
  • cwd
    ——worker 的项目目录(默认使用你的当前目录)。
  • permissions
    ——
    bypass
    (默认 →
    --dangerously-skip-permissions
    :分离的窗格 无法回答权限提示,因此 worker 应自行设限——参见“Worker prompt”);
    inherit
    (不传 flag:使用 claude 在该 cwd 下的默认设置);其他值会原样传给
    --permission-mode
    (可用模式由
    claude --help
    列出)。无效模式会导致启动失败, 并显示 claude 自身的错误。
  • SUMMON_CLAUDE_ARGS
    ——可选环境变量,用于将额外 flag 原样追加到启动命令: 例如使用
    --model haiku
    启用低成本 scout,或使用
    --resume <session_id>
    在新窗格中恢复已死亡 worker 的对话(其 session_id 位于
    <state>/events.jsonl
    中)。
Spawn 会端到端地自我验证(启动、粘贴、提交——全部依据 hook 信号完成,并带有自己的 重试机制)。如果失败,它会明确说明原因,同时给出窗格尾部内容和修复方法;最常见的原因是 首次运行时的同意对话框(见“常见陷阱”)。

The loop

循环

spawn → wait → act on the event → wait again → … → down
bash <skill-dir>/scripts/wait.sh <id> [max_secs]     # default 1800
Run it in the background if your harness supports that (it's silent until it exits); otherwise run it blocking. It costs you nothing while the worker runs, and exits with one self-contained block:
  • EVENT=STOP
    — the worker ended its turn; its final message follows, captured by the Stop hook (the transcript is never parsed). Usually the report you asked for — but a worker sometimes ends a turn just to tell you something: read it, then poke or re-wait. A Stop line that can't be read is still
    EVENT=STOP
    — the block says the message is unreadable and points at the transcript, so a Stop is never hidden behind an IDLE.
  • EVENT=QUESTION
    — paused on AskUserQuestion; the numbered options follow. Answer (next section), then wait again.
  • EVENT=IDLE
    — cap hit, nothing happened; heartbeat age + recent tool activity follow. Long silence is normal (thinking, subagents, long subprocesses) — re-arm the same command. Intervene only on real evidence of a stall.
  • EVENT=DEAD
    — the worker is gone: pane killed, or claude exited leaving a bare shell behind (the block says which, with post-mortem pointers).
Re-arming is literally the same command again — the cursor (kept in the worker dir) resumes from the last event reported.
spawn → wait → 根据事件行动 → 再次 wait → … → 结束
bash <skill-dir>/scripts/wait.sh <id> [max_secs]     # default 1800
如果你的 harness 支持后台执行,请在后台运行它(它会静默等待直至退出);否则可以使用 阻塞方式。worker 运行期间它不会产生任何开销,退出时会返回一个自包含的区块:
  • EVENT=STOP
    ——worker 已结束本轮;随后是由 Stop hook 捕获的最终消息 (绝不解析 transcript)。这通常就是你要求的报告——但 worker 有时结束一轮只是为了 向你传递信息:阅读消息,然后 poke 它或重新 wait。 即使无法读取 Stop 行,仍会返回
    EVENT=STOP
    ——区块会说明消息不可读并指向 transcript, 因此 Stop 绝不会隐藏在 IDLE 后面。
  • EVENT=QUESTION
    ——worker 暂停并等待 AskUserQuestion;随后会列出编号选项。 回答问题(见下一节),然后再次 wait。
  • EVENT=IDLE
    ——已达到等待上限,期间没有发生任何事件;随后会给出 heartbeat 时长 和近期工具活动。长时间静默很正常(思考、subagents、长时间运行的 subprocess); 重新执行同一条命令即可。仅在有确凿证据表明任务停滞时才介入。
  • EVENT=DEAD
    ——worker 已消失:窗格被杀死,或 claude 退出后只留下一个空 shell (区块会说明具体情况,并提供事后排查线索)。
重新等待只需再次执行完全相同的命令——保存在 worker 目录中的 cursor 会从上次报告的 事件处继续。

Answer a question

回答问题

Options are numbered in
question.json
order; the pane appends its own entries ("Type something", "Chat about this") after the real options, so your numbers are the real ones.
  • Single question — a digit selects and submits:
    tmux send-keys -t <id> 2
  • Several questions — digits auto-advance; after the last, Enter submits the review step:
    tmux send-keys -t <id> 1; sleep 1; tmux send-keys -t <id> 3; sleep 1; tmux send-keys -t <id> Enter
  • [multi-select]
    — digits toggle, Enter submits.
  • Free text exists (the "Type something" option) but is fiddly — prefer a listed option.
  • Verify, don't assume. The landed answer appends a
    QuestionAnswered
    line (with the chosen labels) to
    <state>/events.jsonl
    , and the next wait moves past the question. A single-question digit commits instantly — there is no review screen — so when the choice matters, read the QuestionAnswered line. Multi-question flows end on an Enter-gated review screen: before submitting a destructive choice, peek it:
    tmux capture-pane -t <id> -p | tail -30
选项按照
question.json
中的顺序编号;窗格会在真实选项之后追加自己的条目 (“Type something”“Chat about this”),因此前面的数字才是真实选项的编号。
  • 单个问题——输入数字即可选择并提交:
    tmux send-keys -t <id> 2
  • 多个问题——数字会自动前进;完成最后一个后,按 Enter 提交审查步骤:
    tmux send-keys -t <id> 1; sleep 1; tmux send-keys -t <id> 3; sleep 1; tmux send-keys -t <id> Enter
  • [multi-select]
    ——数字用于切换选择,按 Enter 提交。
  • 存在自由文本选项(“Type something”),但操作较麻烦——优先选择列出的选项。
  • 验证,不要假设。 已提交的答案会向
    <state>/events.jsonl
    追加一条 QuestionAnswered 行(其中包含所选标签),下一次 wait 会越过该问题。 单问题模式输入数字后会立即提交——没有审查界面——因此,当选项很重要时, 请读取 QuestionAnswered 行。多问题流程会进入一个需按 Enter 确认的审查界面: 提交破坏性选择前,先查看界面:
    tmux capture-pane -t <id> -p | tail -30

Poke a running worker

Poke 正在运行的 worker

Only when no question is pending (
<state>/question.json
absent). While a question modal is up, typed text is captured by the modal and a trailing Enter selects whatever is highlighted — it answers the question by accident instead of queueing your message. Answer the question first, or interrupt to cancel it.
  • Queue a follow-up (read at the worker's next turn boundary;
    -l
    = literal, so a one-word message like
    Up
    isn't sent as the tmux key of that name):
    tmux send-keys -t <id> -l 'your message' && tmux send-keys -t <id> Enter
  • Long or quote-heavy follow-up — paste it the way spawn does (per-worker buffer name, so parallel pokes can't cross):
    tmux load-buffer -b poke-<id> <file> && tmux paste-buffer -t <id> -b poke-<id> -p -d && sleep 1 && tmux send-keys -t <id> Enter
  • Never start a poke with
    /
    ,
    !
    , or
    #
    — TUI input prefixes, typed or pasted alike:
    /
    runs a slash command (
    /exit
    kills the worker),
    !
    executes the rest as a shell command in the worker's cwd,
    #
    opens the memory picker; none submits a prompt (no UserPromptSubmit lands). Need a literal one? Lead with something else ("Note: /foo …").
  • Interrupt generation to redirect:
    tmux send-keys -t <id> Escape
    , then poke. Escape is the TUI's own interrupt key and a no-op on an idle worker — but send it once: a rapid double Escape opens the Rewind menu, a modal the signals can't see that can restore code and conversation. If a poke seems swallowed (no UserPromptSubmit line lands), resend Enter first — a lagged paste leaves your text sitting in the input box, and one more Enter just submits it (re-pasting first would stack a second copy). Still nothing? A modal is up: one Escape dismisses it, then re-poke. Never C-c — a quick double C-c exits claude entirely, and a misjudged stall is exactly when you'd hit an idle one. An interrupted turn emits no Stop — the next completed turn (your poke's) does. Interrupting cancels a pending question; the orphaned question.json is archived automatically the moment your next message submits.
  • A fresh spawn is a blank context — to continue a worker, poke its pane; don't respawn.
仅在没有待处理问题时操作(
<state>/question.json
不存在)。当问题模态框打开时, 输入的文本会被模态框捕获,末尾的 Enter 会选择当前高亮项——这会意外地回答问题, 而不是将消息加入队列。请先回答问题,或者中断并取消它。
  • 排入一条后续消息(会在 worker 下一轮开始时读取;
    -l
    表示按字面文本发送, 因此像
    Up
    这样的单词不会被当作同名 tmux 按键发送):
    tmux send-keys -t <id> -l 'your message' && tmux send-keys -t <id> Enter
  • 较长或包含大量引号的后续消息——按 spawn 的方式粘贴(使用每个 worker 独立的 buffer 名称,因此并行 poke 之间不会串扰):
    tmux load-buffer -b poke-<id> <file> && tmux paste-buffer -t <id> -b poke-<id> -p -d && sleep 1 && tmux send-keys -t <id> Enter
  • 绝不要让 poke 以
    /
    、
    !
    或
    #
    开头——无论手动输入还是粘贴,这些 TUI 输入 前缀都会生效:
    /
    会运行斜杠命令(
    /exit
    会终止 worker),
    !
    会在 worker 的 cwd 中将其余内容作为 shell 命令执行,
    #
    会打开 memory picker;它们都不会提交 prompt (不会产生 UserPromptSubmit)。如果必须使用字面量字符,请以其他内容开头,例如 “Note: /foo …”。
  • 中断生成以改变方向:
    tmux send-keys -t <id> Escape
    ,然后再 poke。Escape 是 TUI 自身的 interrupt 按键,对空闲 worker 无效——但只能发送一次:快速连按两次 Escape 会打开 Rewind 菜单,这是一个信号无法看到的模态界面,并且可能恢复代码和对话。 如果 poke 似乎被吞掉(没有产生 UserPromptSubmit 行),请先再次发送 Enter——粘贴延迟会让 文本留在输入框中,再按一次 Enter 只会提交该文本(如果先重新粘贴,反而会叠加第二份副本)。 仍然没有反应?说明模态界面已打开:按一次 Escape 将其关闭,然后再 poke。 绝不要按 C-c——快速连按两次会直接退出 claude,而误判任务停滞时恰恰最容易这样做。 被中断的一轮不会发出 Stop——下一轮完成的任务(也就是你的 poke)才会发出。 中断会取消待处理问题;孤立无主的 question.json 会在下一条消息提交时自动归档。
  • 新 spawn 的上下文是空白的——若要继续现有 worker,请 poke 它的窗格,不要重新 spawn。

Results, status, debugging

结果、状态与调试

  • Final message — printed by wait on STOP; re-read any time:
    grep '"event":"Stop"' <state>/events.jsonl | tail -1 | jq -r .last_assistant_message
  • Answers given —
    grep '"event":"QuestionAnswered"' <state>/events.jsonl | tail -1 | jq -c .answers
  • All workers —
    find "${SUMMON_STATE:-$HOME/.local/state/summon}/workers" -name meta.json -exec jq -r '"\(.id)  \(.name)  \(.cwd)"' {} + 2>/dev/null
    — cross-check liveness with
    tmux ls
    , pending questions with
    find "${SUMMON_STATE:-$HOME/.local/state/summon}/workers" -name question.json 2>/dev/null
    .
  • Instant probe —
    bash <skill-dir>/scripts/wait.sh <id> 0
    returns at once: any unconsumed event, else an IDLE snapshot (heartbeat age + recent activity). Same single-shot, cursor-safe semantics as any wait — probing costs nothing.
  • Peek (debug only — every pane capture costs orchestrator context; the signals above are the normal path):
    tmux capture-pane -t <id> -p | tail -40
  • 最终消息——wait 在 STOP 时打印;可随时重新读取:
    grep '"event":"Stop"' <state>/events.jsonl | tail -1 | jq -r .last_assistant_message
  • 已提交的答案——读取方式如下:
    grep '"event":"QuestionAnswered"' <state>/events.jsonl | tail -1 | jq -c .answers
  • 所有 worker——
    find "${SUMMON_STATE:-$HOME/.local/state/summon}/workers" -name meta.json -exec jq -r '"\(.id)  \(.name)  \(.cwd)"' {} + 2>/dev/null
    — 使用
    tmux ls
    交叉检查存活状态,使用以下命令查找待处理问题:
    find "${SUMMON_STATE:-$HOME/.local/state/summon}/workers" -name question.json 2>/dev/null
    。
  • 即时探测——
    bash <skill-dir>/scripts/wait.sh <id> 0
    会立即返回:任何尚未消费的 事件,或一份 IDLE 快照(heartbeat 时长与近期活动)。它与普通 wait 具有相同的单次执行、 cursor 安全语义——探测不产生任何额外开销。
  • 查看窗格(仅用于调试——每次捕获窗格都会消耗编排者上下文;正常使用上述信号即可):
    tmux capture-pane -t <id> -p | tail -40

Teardown

清理

When you've consumed a worker's result:
tmux kill-session -t '=<id>'
— keep the
=
: it pins the exact name, where a bare
-t
falls back to prefix matching once the exact session is gone (Gotchas). The Claude transcript persists on disk regardless. Remove the state dir when you're done with its evidence:
rm -rf <state>
. Ids are never reused — a stale dir can't collide.
消费完 worker 的结果后:
tmux kill-session -t '=<id>'
——务必保留
=
: 它会锁定精确会话名,而裸用
-t
在精确会话消失后会退化为前缀匹配(见“常见陷阱”)。 无论是否执行此操作,Claude transcript 都会保留在磁盘上。处理完相关证据后,删除状态目录:
rm -rf <state>
。Id 绝不会被复用——陈旧目录不会发生冲突。

Worker prompts — what makes orchestration work

Worker prompt——让编排真正奏效的关键

  • Self-contained and scoped. The worker shares none of your context: state the goal, the exact scope, what's out of scope, and "read X and Y yourself". One job per worker.
  • Tell it how to finish: "END YOUR TURN with a report of A/B/C." Stop is your completion signal, so finishing the turn — with the facts you need in the final message — is part of the worker's job. The final message is all you should ever need.
  • Self-gate risky actions (essential under
    bypass
    ): instruct the worker to pause and ask via AskUserQuestion before anything irreversible, destructive, or out-of-scope — that routes the decision to you through the QUESTION event, with numbered options you answer by digit.
  • Independent review catches more: for pipelines, have one worker do the step and a fresh worker review it; repeat until a reviewer that changed nothing approves.
  • 自包含且范围明确。 worker 不共享你的任何上下文:说明目标、确切范围、 范围外的事项,并要求它“自行读取 X 和 Y”。每个 worker 只负责一项工作。
  • 告诉它如何完成。 例如:“END YOUR TURN with a report of A/B/C.” Stop 是你的 完成信号,因此结束本轮——并在最终消息中给出所需事实——本身就是 worker 工作的一部分。 最终消息应该就是你所需的全部信息。
  • 让 worker 自行设限高风险操作(在
    bypass
    下至关重要):指示 worker 在执行任何 不可逆、破坏性或超出范围的操作之前暂停,并通过 AskUserQuestion 提问——这会通过 QUESTION 事件将决策路由给你,并提供可由数字回答的编号选项。
  • 独立审查能发现更多问题:对于流水线,让一个 worker 执行步骤,再由一个全新的 worker 审查;重复此过程,直到一位没有做出任何修改的审查者批准为止。

Decision routing

决策路由

When a worker asks: answer it yourself if the task's constraints already determine the answer, or the choice is recoverable — keep the work moving. Escalate to the human only what is genuinely new, material, or irreversible. When unsure whether something is reversible, treat it as escalate-worthy. Agree this boundary with the human up front.
当 worker 提问时:如果任务约束已经确定了答案,或者该选择可以恢复,就由你自行回答, 让工作继续推进。只有真正新颖、重大或不可逆的事项才升级给人类。如果无法确定某个选择 是否可逆,就将其视为需要升级的事项。请事先与人类约定好这条边界。

One-by-one vs batch

逐个执行与批处理

  • One-by-one: spawn → wait to completion → act → down → next. Each result gates the next step; trivial bookkeeping. Default for dependent steps.
  • Batch: spawn N workers (distinct names, a brief file per worker; keep each printed id and state), arm one waiter per worker, handle whichever exits first, re-arm just that one. Every signal file is per-worker — concurrent workers never cross wires. No background execution on your harness? Round-robin with short caps —
    wait.sh w1 60; wait.sh w2 60; …
    — waits are single-shot and cursor-safe, so taking turns loses nothing. Cap concurrency to what the machine and your attention can handle — a fleet booting at once can push claude's start past spawn's 45s boot window.
  • 逐个执行:spawn → 等待完成 → 采取行动 → 结束 → 下一个。每一步结果都会决定 下一步是否执行;这适用于简单 bookkeeping。存在依赖关系时默认采用此方式。
  • 批处理:spawn N 个 worker(使用不同名称和各自的 brief 文件;保留每个 worker 打印出的 id 与状态),为每个 worker 启动一个 waiter,先处理最先退出的那个,然后只重新等待 该 worker。每个信号文件都独立属于对应 worker——并发 worker 之间绝不会串扰。 如果你的 harness 不支持后台执行?使用短时限进行轮询——
    wait.sh w1 60; wait.sh w2 60; …
    ——wait 是单次执行且 cursor 安全的,因此轮流等待 不会丢失任何信息。将并发数量限制在机器和你的注意力所能承受的范围内——同时启动大量 worker 可能会使 claude 的启动时间超过 spawn 的 45 秒启动窗口。

The state dir — reference

状态目录——参考

One per worker:
${SUMMON_STATE:-~/.local/state/summon}/workers/<id>/
(spawn prints it).
filemeaning
meta.json
what was spawned: name, cwd, prompt file, effort, permissions, extra args
settings.json
what
--settings
injected into this worker: the six hooks,
promptSuggestionEnabled: false
events.jsonl
lifecycle: SessionStart, UserPromptSubmit, AskUserQuestion, QuestionAnswered, Stop (carries the final message, permission mode, effort), SessionEnd — one line per event, whole or absent
question.json
the pending AskUserQuestion; presence == pending (answered/stale copies are archived beside it)
heartbeat
/
activity.log
epoch of last tool call / one attributed line per tool call
claude_exited
exit sentinel: the pane's shell drops it the moment claude exits, for any reason (spawn fails fast on it during startup; wait reads it as DEAD when claude dies without a SessionEnd — hard kill, OOM)
cursor
waiter bookkeeping — automatic, don't touch
每个 worker 拥有一个目录:
${SUMMON_STATE:-~/.local/state/summon}/workers/<id>/
(由 spawn 打印)。
文件含义
meta.json
启动内容:名称、cwd、prompt 文件、effort、permissions 和额外参数
settings.json
通过
--settings
注入此 worker 的内容:六个 hooks,以及
promptSuggestionEnabled: false
events.jsonl
生命周期事件:SessionStart、UserPromptSubmit、AskUserQuestion、QuestionAnswered、Stop(携带最终消息、permission mode 和 effort)、SessionEnd——每个事件占一行,可能完整存在或完全缺失
question.json
待处理的 AskUserQuestion;文件存在即表示有待处理问题(已回答或陈旧的副本会归档在同目录旁)
heartbeat
/
activity.log
最近一次工具调用的 epoch 时间 / 每次工具调用对应一行带归属信息的记录
claude_exited
退出哨兵:无论 claude 因何原因退出,窗格中的 shell 都会立即将其删除(spawn 在启动期间快速失败时也会触发;claude 未产生 SessionEnd 就死亡时——硬杀、OOM——wait 会将其读取为 DEAD)
cursor
waiter 的内部记录——自动维护,不要修改

Gotchas

常见陷阱

  • Stop is completion; SessionEnd is bookkeeping — SessionEnd may or may not fire for any given death: a clean
    /exit
    writes one,
    tmux kill-session
    sometimes does too, a hard kill never. Only its presence means anything — never wait on it, never read anything into its absence. wait.sh encodes all of this: claude gone with the pane's shell still alive reads DEAD — via SessionEnd when it fired, via the
    claude_exited
    sentinel when it didn't (SIGKILL, OOM) — never eternal IDLE.
  • First-run dialogs stall boot (spawn fails loud at the SessionStart step): a machine that has never accepted
    --dangerously-skip-permissions
    , or a cwd Claude doesn't trust yet, shows an interactive dialog first. Remedy: run
    claude
    once in that dir interactively, accept, respawn. Don't script past these — they're consent gates.
  • Non-bypass workers can stall on a permission dialog — permission and plan-approval prompts are not AskUserQuestion: no hook fires, no signal appears, the heartbeat just goes stale. On a non-bypass worker, IDLE + old heartbeat + no pending question → peek the pane; answer the dialog with send-keys, or respawn under bypass.
  • Vocabularies live in claude, not here. Effort levels and permission modes are passed through verbatim and never validated —
    claude --help
    is the source of truth.
  • No ghost text in a worker's input box. The TUI's suggested next prompt — dim text after each turn, which a pane capture prints as if typed — is switched off in every worker's injected settings (
    promptSuggestionEnabled: false
    ), so text sitting in a worker's input box is real: a lagged paste or a queued poke.
  • tmux
    -t
    prefix-matches once no exact name survives
    — a dead worker's id could then resolve to a similarly named session. Defense in depth: ids are fixed-width, spawn rejects names that embed an id-shaped run (so one id can never nest another — don't pass an old worker's id as a new worker's name), and the teardown one-liner pins with
    =
    . Pane-target commands take the same pinning as
    -t '=<id>:'
    if you want it for send-keys/paste/capture.
  • Hooks gate on
    $SUMMON_ID
    and are injected per worker — no session you didn't summon loads or writes anything. Every event line twice? A second copy of the hooks is registered in a settings file claude loads for that cwd — find it with
    grep -l on_session_start.sh <cwd>/.claude/settings*.json ~/.claude/settings.json
    and remove it; the signals tolerate the duplicates meanwhile.
  • Workers get the tmux server's environment, not yours — the server keeps the environment of whoever started it, so a variable exported before spawn never reaches the pane. Per-worker knobs go on the launch line (
    SUMMON_CLAUDE_ARGS
    ) or into the injected settings, which is how spawn passes its own.
  • Don't extend the scripts with GNU-isms (
    date -d
    ,
    sed -i
    ,
    flock
    ,
    timeout
    ) — epoch-seconds arithmetic and plain POSIX tools are what keep macOS supported.
  • Stop 表示完成;SessionEnd 仅用于记录——对于任意一种死亡方式,SessionEnd 可能触发, 也可能不触发:正常执行
    /exit
    会写入一条,
    tmux kill-session
    有时也会写入, 硬杀则永远不会。只有它是否出现才有意义——绝不要等待它,也不要因它缺失而推断 任何情况。wait.sh 对此进行了完整编码:当 claude 已消失但窗格中的 shell 仍存活时, 会读取为 DEAD——若 SessionEnd 已触发则使用它,否则使用
    claude_exited
    哨兵 (SIGKILL、OOM)——绝不会永远停留在 IDLE。
  • 首次运行对话框会阻塞启动(spawn 会在 SessionStart 步骤明确报错):如果机器尚未接受过
    --dangerously-skip-permissions
    ,或者 Claude 尚未信任某个 cwd,就会先显示交互式对话框。 修复方法:在该目录中交互式运行一次
    claude
    ,接受相关设置,然后重新 spawn。 不要用脚本绕过这些对话框——它们是同意机制。
  • 非 bypass worker 可能会卡在权限对话框上——权限和计划审批提示不是 AskUserQuestion:不会触发 hook,也不会产生信号,只会显示 heartbeat 逐渐过期。 对于非 bypass worker,如果出现 IDLE + heartbeat 过旧 + 没有待处理问题,请查看窗格: 使用 send-keys 回答对话框,或在 bypass 模式下重新 spawn。
  • 词汇表由 claude 定义,而不是这里。 Effort 级别和 permission mode 会被原样透传, 此处绝不校验——
    claude --help
    是唯一真源。
  • worker 输入框中不会出现幽灵文本。 TUI 在每轮结束后显示的下一条建议 prompt (窗格捕获时看起来像已经输入的暗色文本)会通过每个 worker 注入的 settings 被关闭 (
    promptSuggestionEnabled: false
    ),因此输入框中的内容都是真实内容:可能是粘贴延迟, 也可能是排队的 poke。
  • 当精确名称消失后,tmux
    -t
    会进行前缀匹配
    ——已死亡 worker 的 id 可能会解析到 名称相似的其他会话。纵深防御措施:id 采用固定宽度,spawn 会拒绝名称中嵌入 id 形状 字符串的请求(因此一个 id 绝不可能嵌套另一个 id——不要把旧 worker 的 id 作为新 worker 的 name),并且清理命令会使用
    =
    锁定精确目标。如果希望 send-keys、 paste 或 capture 也使用这种锁定方式,可将 pane 目标写为
    -t '=<id>:'
    。
  • Hooks 根据
    $SUMMON_ID
    进行门控,并按 worker 注入
    ——任何未由你 summon 的会话 都不会加载或写入任何内容。每个事件行出现两次?说明第二份 hooks 被注册在 claude 为 该 cwd 加载的 settings 文件中——使用以下命令找到并移除它:
    grep -l on_session_start.sh <cwd>/.claude/settings*.json ~/.claude/settings.json
    在此期间,信号机制可以容忍重复项。
  • worker 会获得 tmux server 的环境,而不是你的环境——server 会保留最初启动它的 进程的 environment,因此在 spawn 前导出的变量绝不会到达窗格中。每个 worker 的调整项 应放在启动命令上(
    SUMMON_CLAUDE_ARGS
    )或注入的 settings 中,spawn 正是通过这种方式 传递自身的配置。
  • 不要用 GNU 专属语法扩展脚本(
    date -d
    、
    sed -i
    、
    flock
    、
    timeout
    )—— epoch 秒算术和普通 POSIX 工具才是保持 macOS 兼容的关键。