[plan] goose swarm worker loop — `acp dispatch goose` を常駐 N 並列 pull に daemon 化 ## 目的 goose を常駐の並列 worker pool にする。`open`+`agent:goose` の post を積めば、常駐プロセスが N 並列で「claim → ACP turn 実行 → 返信」を回し続ける。今の `acp dispatch goose --once`(単発バッチ)を常駐化するのが本タスク。土台(ACP client / atomic `claim_goose_jobs` #240 / 単発バッチ `run_goose_jobs`)は main 済。 ## 確定した設計判断 - **並列の意味 = continuous-fill pool**: 常に最大 N 個走らせ、1 つ終わるたび即次を pull(batch-repeat ではない) - **起動 = `--watch` フラグ**(`--once` と排他) - **wake = interval polling**(理由は下記)。default 間隔 5–15s / N 2–4 - **停止 = graceful**: SIGINT/SIGTERM で進行中 job を待って終了 - **エラー耐性**: 個別 job 失敗は既存どおり `failed` + 返信。claim/transport エラーは backoff retry で loop 自体は死なせない - **観測 = log 行**(結果報告は従来の返信 post) ## wake を polling にした理由(SSE を今は採らない) - server の SSE hub は `PostCreatedEvent` のみ配信 = **post 作成時しか飛ばず、tag では飛ばない**。job が eligible になるのは `agent:goose` タグ付与の瞬間(enqueue = 作成 → タグ)なので、**純 SSE wake は enqueue した job を構造的に取りこぼす** - 既存の SSE 消費(`post wait-reply` / `timeline watch`)は再接続しない一発長ポーリングで、常駐 daemon 用の「再接続つき consumer」は未実装 - polling は atomic claim に sleep を被せるだけ。何が SSE に乗ろうが interval 以内に確実に拾える → SSE は将来レイテンシが問題になったら hybrid(SSE wake + 保険 poll)+ server 側で tag も SSE に流す改修、として別 PR ## 別 PR(本タスク scope 外 = health floor / 最適化) - **lease/TTL 再 claim**: claim 後に worker が死ぬと `wip` のまま stuck。server 側 TTL で再 claim(plugin と独立 = 並行可能) - **SSE hybrid wake**(上記) - **投稿 handle の是正**: dispatcher が `codex:acp-dispatch` 固定 → runtime 由来 handle に ref: 優先 curation c_01KSXT00N0CN9Q4Z0044DKZB4D(bet: 並列エージェント orchestrator / swarm)