ディスパッチャーのコンテキストを圧迫したまま session を続けるのを避けるため、 monitoring 状態(active workers / 直近 polling cursor / pending escalations)を handover ファイルに書き出し、secretary の指示で /clear → /dispatcher-resume の 流れで新しいディスパッチャー session を開始する準備をする。 Secretary から DISPATCHER_HANDOVER peer message を受領したとき、または ディスパッチャー自身が context が長くなったと判断したときに使う。
org-suspend
組織を中断し、全状態をディスクに保存する。「中断」「保存して終了」 「閉じたい」「一旦やめる」「今日は終わり」と言われたときに使う。
含まれるファイル(2)
- SKILL.md73.7 KB
- SKILL.md.in69.5 KB
SKILL.md(原文)
インストールする前に、エージェントに与えられる指示の中身を確認できます。
org-suspend: 組織の中断
全ワーカーの状態を収集し、ディスクに保存し、全ペインを停止する。
curator 不在は正常系(オンデマンド化): キュレーターは常駐しない。state.db の
curator_pane_id/curator_peer_idは null が正常で、list_panes/list_peersに curator が見えないことは異常ではない。curator ペインが存在するのは「worker クローズ起点の オンデマンド curate が実行中に suspend が重なった」一時的なケースのみで、その場合だけ Phase 4 の停止対象に含める。
責務境界(/org-suspend と
/org-down): /org-suspend は 「状態保存 + ja 管理下の補助プロセス(dashboard / secretary_queue_watcher / attention watcher)とペインの停止」までを担い、claude-org-runtime org down(broker daemon の停止)は 呼ばない。suspend 単体は「また/org-startで再開する」前提の中断であり、broker daemon は 走らせたままにする(端末を閉じても daemon はすぐ再開できるよう生存する)。daemon ごと完全に 落とすのは/org-downの責務で、/org-down が suspend の成功を確認した 後にのみorg downを実行する。
ペイン操作は mcp__org-broker__* MCP ツール経由で行う。pane_exited
相当の lifecycle イベントは mcp__org-broker__poll_events で long-poll、画面スクレイプ
は mcp__org-broker__inspect_pane で取得、raw キー入力は mcp__org-broker__send_keys。
輸送層(transport)両系 — 既定
broker/ opt-inrenga: 本ファイル(および各スキル)のmcp__org-broker__*呼び出しは 既定broker(ORG_TRANSPORT無設定)で書いてあり、そのまま従えばよい(既定挙動)。ORG_TRANSPORT=renga(opt-in・切戻し可)では MCP サーバー名がrenga-peersになり、ツールの 完全修飾名がmcp__org-broker__*→mcp__renga-peers__*に機械置換される(引数形・セマンティクスは同一なので手順の論理は変わらない)。輸送依存で手順が変わる点だけ renga 併記する:
- 受信モデル: 既定 broker は push 一次(各ペイン同居の channel sidecar
server:org-broker-channelが broker キューを ~1 秒間隔で claim→notifications/claude/channelで idle セッションへ本文注入。pull = ナッジ +check_messagesは sidecar 不在 / unhealthy / channel 非対応ペイン(codex pull-peer)/ claude.ai login 不在時のフォールバック層)。ORG_TRANSPORT=renga時は dispatcher / worker メッセージが<channel source="renga-peers" …>として in-band で push される。- spawn 儀式: 既定 broker は
--mcp-config <broker>注入による Claude Code folder-trust プロンプトの画面判定による機械承認に加え、push 一次のため channel sidecar を--dangerously-load-development-channels server:org-broker-channelで load し dev-channel 承認プロンプトを画面判定で機械承認する(2 段承認)。ORG_TRANSPORT=renga時は--dangerously-load-development-channels server:renga-peersの「Load development channel?」を画面判定で承認する 1 段。 画面判定 =inspect_pane→claude-org-runtime dispatcher spawn-prompt-stepが返した 1 手だけをsend_keysする反復(runtime 0.1.43+)。盲目 Enter は禁止(folder-trust の初期カーソルは「No, exit」で、Enter 一発はワーカーを終了させる)。手順は spawn-flow 3-3b。- エラー分岐: 既定 broker は shared codes(
pane_not_found/last_pane/invalid-params)に加え broker 固有[token_invalid]/[session_invalid]/[tool_not_authorized]/[no_backend](= adapter_unavailable)/[nudge_failed]/[peer_not_found]/[name_taken]を返しうる(未知コードは default-branch で escalate)。ORG_TRANSPORT=renga時は broker 固有コードは発生しない。
new_tab/focus_paneは broker surface に無い(意図的除外)。契約面の正本はdocs/contracts/backend-interface-contract.mdSurface 8 + push-primary amendment(broker push 一次が 既定の契約、pull は fallback として retain)。opt-inrengaは削除せず常時有効な切戻しの安全装置として維持する。broker 実走(dogfood)は Epic #6 Issue G スコープで本ファイルの既定運用経路ではない(二フレーム注記(Refs #604): ここの「既定broker」はコード既定(tools/transport.py: DEFAULT_TRANSPORT、生成面はこれで render)。運用既定は broker dogfood が Epic #6 Issue G まで未活性のためrengaで、両者は指す対象が異なり矛盾しない。総説は rootCLAUDE.md。)
Phase 1: ワーカー状態収集
- id 台帳を作る。messaging とペイン制御は別々の面なので、id もそれぞれの面から取る
(契約 Surface 4 の批准済み amendment (2026-08-09) T-§4.2 は「到達性は面ごとに別々に確立する」ことを MUST に
している。
list_peersにレコードがあることは、そのピアがペイン制御で addressable であることの 証拠にならない):-
mcp__org-broker__list_peers— messaging 用の peer id。各レコードのid/name/role/cwdと、 マルチタブ対応 backend が返す optional なsame_tab/tab/tab_nameを控える(この 3 つはcross_tab_peerscapability を広告する backend でのみ載る。非広告 backend では欠落する)。list_peersのタブ範囲を決めるのはcross_tab_peersであってcaller_scopeではない (契約 T-§cap は capability を互いに独立と規定する。caller_scopeが変えるのはlist_panesと Group A の pane 制御呼び出しだけで、list_peersには影響しない。close_pane/set_pane_identityはcaller_scopeの管轄外=別 token — 下記 (G) を参照):cross_tab_peersを広告する backend:list_peersは全タブを列挙し、same_tabは 呼び出し側から見た値として返る(契約 T-§2.2 / T-§2.2-fields)。フォーカスに依存しないので フォーカスの確認は不要cross_tab_peersを広告しない backend(旧版・renga 1.x・現行org-broker、およびcaller_scopeだけを広告する backend): ratified の messaging 規則どおり フォーカス中のタブに追随する。marker 全欠落が保証するのは「その列挙が単一タブに閉じて いる」ことだけで(契約 T-§cap の conformance MUST)、それが窓口のタブであることまでは 保証しない。/org-suspendを走らせる前にフォーカスが窓口ペインのタブにあることを確認する。 列挙されたタブを呼び出し側に紐付けられない場合は broadcast しない(別 org のタブへ SUSPEND / SHUTDOWN を撃つ経路になる)。紐付けの確認は、list_panesに窓口ペイン自身が 居ること、あるいは列挙に自組織の既知 identity(state.db のdispatcher_peer_id等)が 含まれることで足りる
-
mcp__org-broker__list_panes— ペイン制御用の pane id。各レコードのid/name/roleを控える。 どのタブが返るかは capability による(契約 T-§cap はcaller_scopeとcross_tab_peersを 独立と規定する):caller_scopeを広告する backend では caller のタブが返る(list_panesは Group A なのでこの token だけで足りる)。広告しない backend(旧版、およびcross_tab_peersだけを広告する backend)では ratified §4.3 の visibility consequence どおりフォーカス中のタブが返るので、/org-suspendを走らせる窓口ペインの タブがフォーカスされていることが前提になる。フォーカスが別タブにあると別タブのペイン id を 台帳に取り込み、後段で無関係なペインをinspect_pane/close_paneしうる。前提が成り立つか 確認できない場合は pane 制御を行わず、pending として人間に報告する -
pane 制御の 2 グループ(G)— 以降この記号で参照する: 契約 T-§4.2 は pane 制御ツールを 管轄 capability の異なる 2 群に分ける。分ける理由はリリース時期ではなくサーバー世代の スキューである(4 トークンは 1 リリースで出ており renga
src/ipc/mod.rs:123-128 のSERVER_CAPABILITIESは 4 定数を並べる。それでもトークンが別なのは、先行 3 トークンを 広告する#290世代の server が未知のfrom_paneを落としてclose/set_pane_identityをユーザーが見ているタブに解決し不可逆にペインを閉じるからで、 上流はcaller_scope_close_identityを "a token of its own" と書いている — rengadocs/api-surface-v1.0.md:576-582)。契約 T-§cap "Independence" はcaller_scopeからcaller_scope_close_identityを推論することを MUST NOT としている。「caller_scopeがあるからあらゆる pane 制御がフォーカス非依存」 という推論をしてはならない:- Group A —
caller_scope管轄(suisya-systems/renga#288 の 7 ツール):list_panes/spawn_pane/spawn_claude_pane/spawn_codex_pane/focus_pane/inspect_pane/send_keys。この token を広告する backend では相対セレクタ (リテラル"focused"/ 裸のname)が caller のタブに解決され、フォーカス非依存になる - Group B —
caller_scope_close_identity管轄(suisya-systems/renga#296):close_pane/set_pane_identityの 2 本。caller_scopeはこの 2 本を覆わない。caller_scope(さらにcross_tab_peers/spawn_tab)を広告する backend であっても、 この token が無ければ"focused"は user が見ているタブに解決され、裸のnameは アクティブタブを最初に探し、そこで見つからなかったときだけ他タブを index 順に走査して 最初の一致を採る(無差別の全タブ検索ではない。危険なのはこのフォールスルーと、優先される タブが caller ではなく user のタブである点。契約 T-§4.2 "Pane-control scope, Group B") - (G-MUST)
caller_scope_close_identityが確立していない間は、close_pane/set_pane_identityに相対セレクタ(リテラル"focused"/ 裸のname)を使ってはならない (MUST NOT)(契約 T-§4.2 "Fail-safe consequence for Group B (MUST)")。自タブのものだと 独立に確立済みの列挙から採った数値 pane id でのみアドレスする(MUST)。誤爆した場合、close_paneは不可逆で(人間が入力中のペインを落とす / 並走する別 org の同名worker-*を 閉じる)、しかもエラーを返さない。契約はこの MUST に carve-out を 1 つだけ置いているが (stale-binding cleanup = 再 spawn が[name_taken]で弾かれるのに当の name がlist_panesに 出ない状態)、本スキルはその条件に当たらないので取らない — /org-suspend が閉じるのは列挙に 居るペインだけで、[name_taken]起点の再 spawn 経路を持たないため、数値 pane_id は常に列挙から 採れる - (G-OWNTAB) 上の MUST が言う「自タブのものだと独立に確立済みの列挙」は、
mcp__org-broker__list_panesの列挙を指す。name/roleの一致が見るのは「その id が期待どおりのペインか」だけで、 その列挙が自分のタブのものかは見ていないので、確立は照合の前段に置く。確立手段は次の 2 つ だけで、いずれか 1 つが成立したときにその列挙を自タブと確立済みとみなす(規範の出所は 契約 T-§4.2 の MUST。同じ規則を harness 側で書き下したものが.dispatcher/references/pane-close.mdの 「照合の前に「その列挙が自タブのものか」を確立する」節で、両者は同一規則であり片方だけを 緩めない):- backend が Group B を自身の単一タブモデル内で解決する —
org-brokerは Group B も 単一タブモデル内で解決するので、列挙は構成上つねに自タブのもの(契約 §8.1 / §8.10) caller_scopeを確立できている —list_panesが caller のタブにフォーカス非依存で 解決する(契約 T-§cap。caller_scope_close_identityとは別トークンで、一方から他方を 導出しない)。確立できたときだけ数える: 広告を確かめられない / 確定できないときは T-§cap の fail-safe default で不成立に倒れる(version 文字列やサーバー名から推定しない)
same_tabは確立手段に数えない。same_tab/tabは契約 T-§2.2-fields がlist_peersのレコードに足すフィールドで、pane 制御の起点であるlist_panesのレコードには 載らない。list_peers側のsame_tabを根拠に pane 制御を許すのは、契約が MUST で禁じる 「messaging 到達性から pane 制御到達性を推論する」ことそのものである(同契約 「The two scopes are not interchangeable (MUST)」)。same_tabが使えるのは否定方向の 絞り込みだけで、same_tab == Falseのレコードにpane_idを割り当てないという狭める 使い方は正しく、「自タブと確立できた」と広げる使い方は license されない。 2 つのいずれも成立しなければ Group B を撃たず、pending として人間に報告する (相対セレクタへフォールバックしない) - backend が Group B を自身の単一タブモデル内で解決する —
- かつて SHOULD だった経緯(2026-08-07 の人間判断、ja#823 — 記録として保持、discharge 済み):
当時この規則は SHOULD として書かれていた。危険は実在するが本 amendment が持ち込んだもの
ではなく以前から存在しており、かつ org 自身の正準手順が Group B を相対セレクタで撃つ箇所を
抱えていた(当時の実測は契約 T-§4.2 の台帳のとおり 8 ファイル 11 呼び出し)。不可逆操作の
手順を未検証のまま変える方がリスクが高いと人間が判断し、コード既定の輸送層
org-brokerには この失敗モードが無い(capability を広告せず単一タブ規約で解決する)ことがその判断を可逆に していた。その呼び出し箇所の移行は 2026-08-09 に完了し、契約 T-§4.2 は同日 MUST へ in-place で 引き上げられた(契約 T-§4.2 "Why SHOULD and not MUST" は当時の決定の記録として過去形で保持され、 台帳の全 row が discharge 済み)。したがって本スキルが従うのは上の (G-MUST) であって、この段落は 当時の状態を説明する記録である - capability gate 実装済みクライアントでは、Group B の呼び出しが token 欠落時に
[server_too_old]で fail-closed する。これは黙って握り潰さず、未実行として pending に残し人間へ報告する(契約 T-§6 clause (b)。renga では回復手順は「daemon を 2.0.0 以降へ更新して再起動しserver_infoで再 probe」であり、旧挙動での継続ではない)
- Group A —
-
フォーカス前提(F)— 以降この記号で参照する:
caller_scopeを広告しない backend ではlist_panesと pane 制御呼び出しはフォーカス中のタブに向く。したがって (F-1)list_panesにピアが居ないことは、それ単独では消滅の証拠にならない (フォーカスが別タブへ移っただけで、生存中の同タブピアが「不在」になる)。 (F-2) pane 制御呼び出しが返す[pane_not_found]も、同じ理由で単独では閉鎖の証拠にならない。 消滅を確定してよいのは (i)pane_exitedを観測した、または (ii) 列挙が窓口のタブのものだと確認できているときだけで、どちらも取れなければ indeterminate として残す。F の解除も (G) のグループ別である:caller_scope広告 backend では Group A(list_panes/inspect_pane/send_keys等)がフォーカス非依存になるので F-1 は効かない。一方 F-2 はclose_paneについて残る —caller_scope_close_identityが未確立ならclose_paneの[pane_not_found]は依然フォーカス(user 可視タブ)由来でありうる。 契約 T-§4.3 の visibility supersession が完全になるのは 2 token 揃ったときだけで、caller_scope単独では §4.3 の文言が Group B に対して verbatim で立つ。 -
2 つを突合して 1 エントリ =
{peer_id, pane_id(割り当てられない場合は未設定), name, role, cwd, same_tab, tab}の台帳にする。to_idには必ずpeer_id、targetには必ずpane_idを渡す。2 つのidが同じ値空間だとは 仮定しない(backend 定義。契約 §4.1 は id を "opaque" と規定しており、面をまたいだ流用は契約が 保証していない) -
pane_idの出所は必ずmcp__org-broker__list_panes(pane 制御面)である。list_peersの台帳は messaging(to_id)用に保ち、そこから pane 制御用の id を導かない — 契約が MUST で禁じる 「messaging 到達性から pane 制御到達性を推論する」ことになるため(「The two scopes are not interchangeable (MUST)」)。割り当ては列挙面のゲート → レコード面の絞り込みの 2 段で決める:- (i) 列挙面のゲート((G-OWNTAB)):
mcp__org-broker__list_panesの列挙を自タブと確立できないなら、 どのエントリにもpane_idを割り当てない。確立手段は単一タブ backend /caller_scopeの 2 つだけで、same_tabはここに数えない。確立できないまま台帳に id を載せると、後段の identity 照合が「期待どおりのペインか」しか見ないため、別タブのペインを照合済みとしてclose_paneしうる - (ii) レコード面の絞り込み(
same_tabは否定方向にだけ使う): (i) が成立した列挙の中でlist_peersのレコードと突合するとき、same_tab == False/ 欠落のレコードを除外する
- (i) 列挙面のゲート((G-OWNTAB)):
-
突合(join)キーは
nameだけで、roleは使わない。roleは非一意で(worker が複数居れば 必ず衝突する)join キーとして成立しない。nameも全タブ列挙では一意ではないので (契約 T-§2.2: §1.8 の名前一意性は "this tab" 限定。worker-{task_id}等の予約名は 2 org 並走で 構造的に衝突する)、namejoin が効くのはlist_panes側が単一タブに閉じているからであって、nameが無条件に一意だからではない。2.0 系のlist_peersは全タブを返す一方list_panesは 単一タブ分しか返さない(契約 §1.5 は本 amendment で非 supersede。どのタブかは上記のとおり capability 依存)ため、(ii) を誤ると別タブのピアのレコードに自タブの同名 ペインのpane_idが付き、Phase 2 が別ワーカーのコンソールをスクレイプし Phase 4 の lifecycle 追跡が壊れる:same_tab == Falseのレコード →pane_idを絶対に割り当てない(pane 制御面では到達できない。 契約 T-§4.2)- 2.0 系だが
same_tab欠落(‡ のケース)→ 「判定できない場合は他タブ扱いに倒す」規則どおりpane_idを割り当てない - 上の 2 つで除外されなかったレコード(
same_tab == Trueが確定した 2.0 系のレコード、および 旧版 fallback の列挙 = 全レコードでsame_tab/tabが両方欠落)→ (i) が成立している列挙に 同名レコードが在れば、そのlist_panesレコードのidをpane_idとして割り当てる。same_tab == Trueはこの割り当てを license する根拠ではない — license するのは (i) であり、same_tabはそこから他タブのピアを外す negative filter として働いているだけである pane_idが**未設定のエントリは「pane 制御では触れない」**ことを意味する((i) が不成立で 台帳全体に id を載せなかった場合を含む)。Phase 2 ではスクレイプを 諦めて git 情報に倒し、Phase 4 ではclose_paneを撃たずに pending に残す(各 Phase の該当箇所と 同じ結論)
-
この台帳を Phase 2 / Phase 4 まで引き回す。宛先解決・pending 集合はすべて台帳エントリを primary key にし、
nameは人間可読の補助ラベルに留める
-
- 直後の「宛先選定の真理値表(Phase 1 / Phase 4 共通)」で broadcast 対象を決める
(Phase 1 の対象 role 集合は
R = {worker, dispatcher})。版判定が 2.0 系だった場合は、同節の operational gate に従って手順 3 の送信前に人間へ報告し、確認を得てから進む(旧版 fallback では gate を発動せず、そのまま手順 3 へ進む) - 対象ピアの
peer_idをto_idに渡してmcp__org-broker__send_messageで以下を送信:SUSPEND: 現在の状態を報告してください。 1. これまでに完了したこと 2. 変更したファイル(コミット済み/未コミット) 3. 次にやろうとしていたこと 4. ブロッカーや未解決の問題 - 30 秒間
mcp__org-broker__check_messagesで応答を待つ(5 秒間隔でポーリング) - 応答があったワーカーの報告を、送信先の台帳エントリに紐付けて記録する(受信メッセージの
from_idをpeer_idと突合し、name一致は補助にする)
宛先選定の真理値表(Phase 1 / Phase 4 共通)
list_peers の各ピアレコードについて、same_tab / tab / role の 3 つで broadcast 可否と
宛先指定の形を決める。Phase 1(SUSPEND)と Phase 4(SHUTDOWN)はこの 1 つの表を共有し、
対象 role 集合 R だけをフェーズごとに差し替える:
- Phase 1(状態報告の依頼):
R = {worker, dispatcher}。curator には状態報告を求めないので含めない - Phase 4(停止指示): 停止順序どおりステップごとに
R = {worker}→R = {dispatcher}→R = {curator}(curator ペインが存在する場合のみ)
role が欠落 / null / 空文字列 / 未知の文字列のピア、および role == "secretary" のピアは
どの R にも入らない。窓口自身が列挙に含まれるかは backend 次第だが(契約 §2.2 は caller を除外すると
書く一方、除外しない実装もありうる)、自己除外は role gate が担うので列挙の挙動に依存しない。
他タブに別 org の secretary が居ても同じく R 外で除外される。
role gate で外した同タブピアは「黙って落とす」のではなく
unresolvedとして追跡する: 契約 §2.2 はroleを optional と規定しているので、実体はワーカーなのにroleが欠落 /nullの レコードを適合 backend が返しうる。これを broadcast 対象から外すのは安全側で正しいが、そのまま 忘れると Phase 4 が「組織を中断しました」と報告する裏でそのワーカーが走り続ける。 したがって、同タブでありながらR外で除外したピア(role 欠落 /null/ 空文字列 / 未知)は 台帳にunresolvedとして残し、Phase 4 の最終報告で「role が判定できず停止操作の対象外にしたピア」 としてpeer_id/name/cwd付きで人間に提示する。role == "secretary"で外したものは 窓口自身なのでunresolvedに入れない。他タブの除外ピアは † の他タブ報告に含める。
版判定は列挙 1 回につき 1 度だけ決める(レコード単位で切り替えない)。list_peers の結果全体を見て、
same_tab / tab のどちらか一方でも載っているレコードが 1 件でもあれば、その列挙全体を 2.0 系として
扱う。全レコードが両方欠落しているときだけ旧版 fallback に落とす(契約 T-§2.2-fields の検出規則は
「backend についての証拠」であり、混在列挙で一部のピアだけ旧版規則を当てるのは禁止)。下表の「版判定」
列は、こうして決まった版のもとで各レコードがどの行に当たるかを読む。
| # | same_tab | tab | role | 版判定 | broadcast | 理由 |
|---|---|---|---|---|---|---|
| 1 | True | 値あり | ∈ R | 2.0 系 | する(peer_id 宛・† 同タブ = 所属 signal) | 自タブの対象役割。tab 情報が揃っており同タブが確定している |
| 2 | True | 値あり | ∉ R | 2.0 系 | しない | 役割が対象外 / 不明。SUSPEND・SHUTDOWN は org の役割にだけ意味を持つ |
| 3 | True | None | ∈ R | 2.0 系(片方欠落) | する(peer_id 宛・† 同タブ = 所属 signal) | same_tab=True だけで同タブが確定する。tab 欠落は旧版の徴候ではない |
| 4 | True | None | ∉ R | 2.0 系(片方欠落) | しない | 役割で除外(版判定より役割判定が優先) |
| 5 | False | 値あり | ∈ R | 2.0 系 | しない(† 他タブは対象外・人間に報告) | 他タブのピアは、この org が spawn したものではない(T-§4.2 retained の同一タブ launch 規則の対偶)。所属を証明する signal も無いので送らない |
| 6 | False | 値あり | ∉ R | 2.0 系 | しない | 他タブの無関係ペインである可能性が最も高い |
| 7 | False | None | ∈ R | 2.0 系(片方欠落) | しない(† 他タブは対象外・人間に報告) | same_tab=False だけで他タブが確定する。行 5 と同じ理由 |
| 8 | False | None | ∉ R | 2.0 系(片方欠落) | しない | 役割で除外 |
| 9 | None | 値あり | ∈ R | 2.0 系(片方欠落) | 原則 しない(行 5 と同じ)。tab が caller 自身のタブ値と一致すると確認できたときだけ行 1 と同じ扱い(‡) | tab が載る時点で 2.0 系。same_tab 欠落は unknown であり、name 一致で同タブと結論してはならない(‡) |
| 10 | None | 値あり | ∉ R | 2.0 系(片方欠落) | しない | 役割で除外 |
| 11 | None | None | ∈ R | 旧版 fallback(cross_tab_peers 非広告。renga 1.4 系および現行 org-broker はここ) | する(peer_id 宛・† 単一タブ = 所属 signal。フォーカス前提の確認が済んでいること) | tab 情報が無い=列挙が単一タブに閉じている(契約 T-§cap の conformance MUST)ので、従来どおり role gate だけで決める(現行挙動と同一)。ただしその単一タブが窓口のタブであることは手順 1 のフォーカス確認で担保する |
| 12 | None | None | ∉ R | 旧版 fallback | しない | 旧版経路でも未知 role は対象に含めない。役割が判定できないペインへの broadcast は org 外プロセスへの誤爆になる |
旧版 fallback の発動条件は 1 つだけ: 列挙内の全レコードで same_tab と tab が 両方 None
のときに限る(行 #11 / #12)。片方でも値が載っていれば 2.0 系として扱う。特に same_tab=False は
「他タブに居ることが分かっている」という 2.0 系の積極的な情報であり、旧版扱いして role 判定だけに
戻してはならない(他タブのピアを名前宛で取りこぼす)。
2.0 系と判定したら、broadcast の前に人間へ報告して確認を取る(operational gate): ゲート本文は
list_peers を呼ぶ全経路の共有 reference に切り出してある。list_peers の直前に
.claude/skills/org-delegate/references/capability-first-drive-operational-gate.md
を Read し、interactive-action の分岐を適用する(/org-suspend は人間が起動する flow なので
人間が画面の前に居り、送信前に停止して確認を取れる。同 reference §6 の表 #1)。
同 reference が定めることのうち、/org-suspend にとって要点だけ再掲する(正本は reference 側。
ここに手順を複製しない):
- 版判定が capability 形なら、それを旧版形として読み替えない(reference §1-1)。「未承認だから 非広告経路として解釈する」は契約が名指しで禁じている誤り。承認前に控えるのは cross-tab addressing に 依拠した行動であって、解釈ではない
- 報告に必ず含める: 見えているタブ数 / 列挙されたピア総数 / うち他タブ判定の件数、および broadcast
対象に選んだピアの
name/peer_id/cwdの一覧(人間が誤爆リスク=別 org を巻き込まないかを 判断できる材料にする) - 人間の確認を得てから先へ進む。確認前は 1 通も出さない
- 他タブピアは † のとおりそもそも broadcast 対象に入らない。gate の役割は「他タブに何が見えているか」を 人間に見せて、停止対象外にした集合が妥当か判断してもらうことにある
- gate は初回駆動に対して 1 度だけで、
/org-suspendの実行ごとではない。照会と記録は reference §2 / §3-A のとおりtools/capability_gate.pyとtools/journal_append.shで行う。以後の/org-suspend(.claude/skills/org-down/SKILL.md経由の suspend を含む)はこの記録に従う - 記録がある場合でも、他タブ判定のピアが前回より増えていたときはその差分を報告する(gate の 再発動ではなく、停止対象外にした集合の変化を人間に見せるため)
- production-activation gate(reference §4)は
/org-suspendを停止させない。未記録の間は他タブ ピアを対象外にしたまま同タブ集合で続行する(下の真理値表の行 5〜10 がまさにその形)。止まるのは first drive gate だけである - 旧版 fallback(行 #11 / #12。現在配備されている全 backend —
org-brokerを含む — はここを通る)では この gate を発動しない。従来どおり無停止で broadcast に進む(既存運用は一切変わらない)
宛先は必ず peer_id: broadcast 対象に決まったピアへは send_message(to_id="<peer_id>") で送る。
to_id に name を渡すと 2.0 系では名前解決が送信者タブ内に限定されるため、他タブのピアは
[pane_not_found] になる(list_peers 由来の id 宛はタブ横断で解決される)。旧版 fallback の行でも
peer_id 宛は同じく有効なので、経路を 1 本に揃えるため版によらず常に peer_id を使う。
†(broadcast 対象の所属判定 — 同タブのみが自組織): SUSPEND / SHUTDOWN は同タブのピアにだけ送る。 他タブのピアには送らず、人間に報告する。根拠は identity 照合ではなく構造である:
- 同タブのピア(
same_tab=True、および旧版 fallback の列挙全体)は自組織のもの。caller のタブの ペイン群は、この org が組み上げたレイアウトである(旧版 fallback では、その単一タブが窓口のタブで あることを手順 1 のフォーカス前提の確認で担保したうえで成り立つ)。契約 T-§4.2 は ratified §4.2 の 「harnesses MUST launch every orchestrator-spawned pane in the same tab」を retained(非 supersede) として維持しており、この org が spawn したペインは全て同一タブに居ることが契約上保証されている。 したがってここは role gate だけで判定し、追加の identity 一致を要求しない(要求すると、identity を state.db に記録しない役割 — 後述の on-demand curator — を永久に取りこぼし、現在配備の backend の挙動も 変わってしまう) - 他タブのピアは、同じ契約規則の対偶により、この org が spawn したものではない。よって
broadcast 対象から外す。
roleが対象役割でも送らない
なぜ identity 照合で他タブピアを「自組織」と認定しないのか: 他タブピアの所属を証明できる signal が 現状存在しないためである。
worker-{task_id}の task_id は人間が付ける slug で、別 checkout の org が 独立に同じ値を使いうる(runs.task_idの UNIQUE 制約はその DB の中だけの話で、org を跨いで一意で あることは何も保証しない)。cwdも証明にならない — ja root 配下には人間が開いたペインや pr-watch ペインが生えるし、別 checkout の org が似た階層構造を持つこともある。runs.worker_dir_idは nullable (tools/state_db/schema.sql)なので cwd 照合を必須にもできない。証明できない以上、送らないのが唯一の 安全な選択であり、「たぶん自組織だろう」で SUSPEND / SHUTDOWN を撃つと別 org を止める。将来この規則を再訪する条件: worker を意図的に別タブへ配置する運用(マルチタブ配置ポリシー)を 導入するときは、本規則と併せて session スコープの identity をピアに刻む仕組みが必要になる。 それが無いまま他タブへ broadcast を許すと、上の証明不能性がそのまま誤爆になる。
他タブピアの扱い(役割共通):
- broadcast しない。SUSPEND も SHUTDOWN も送らない
close_paneも撃たない(そもそも pane 制御面では到達できない。契約 T-§4.2 と下記 §)- 人間に報告する: 「他タブに
roleが {worker,dispatcher,curator} のピアが N 件見えているが、 自組織のものと確認できないため停止操作の対象外にした」ことを、name/peer_id/cwd付きで 提示する。人間がそれを自組織のものだと判断した場合の対応(手動停止など)は人間の判断に委ねる - curator の補足: on-demand curator の identity は設計上 state.db に書かれない
(
org-startはcurator_pane_id/curator_peer_idをStateWriter.CLEARで明示クリアし、.dispatcher/CLAUDE.mdは「state.db に書かない(null が正常系。 生存確認はlist_panesのみ)」と規定する)。同タブの curator はタブ所属で自組織と判定できるので 従来どおり graceful に SHUTDOWN を受け取る(オンデマンド curate と suspend が重なったケースは この経路で停止する)
§(他タブピアの停止確認に関する注記): same_tab=False と判定されたピアは、messaging では到達
できてもペイン制御では到達できない(契約 T-§4.2: pane 制御は caller のタブ内に留まる)。Phase 4 で
このピアの close_pane が [pane_not_found] を返しても閉鎖の証拠にはならないので、Phase 4 の
「[pane_not_found] の扱い」節の他タブ例外に従う。
‡(same_tab 欠落時の扱い — 名前一致で「同タブ」と結論してはならない): 2.0 系と判定した列挙で
same_tab が載らないレコードは unknown であり、他タブ扱いに倒す(§ の制約と † の identity 確認を
両方適用する安全側)。
mcp__org-broker__list_panes に同じ name / role のペインが在ることを「同タブである」の根拠に使ってはならない。
name は全タブ列挙では一意でなく(契約 T-§2.2。予約名は 2 org 並走で構造的に衝突する)、role はさらに
非一意なので、他タブのピアが自タブのペインと name / role を共有していても矛盾しない。この一致を
同タブの証拠に使うと、他タブのピアが同タブと誤分類され、† の identity 確認を素通りして別 org へ
SUSPEND / SHUTDOWN が飛ぶ(本節が防ごうとしている事象そのもの)。
同タブと積極的に結論してよいのは、same_tab == True が載っているか、tab の値が caller 自身のタブの
値と一致することを確認できた場合だけである。どちらも取れなければ unknown のまま他タブ扱いにする。
Phase 2: 未応答ワーカーのスクレイプ
応答がなかったワーカーについて:
.state/workers/から該当ワーカーの状態ファイルを読み、Pane Name と Directory を取得- Pane Name を Phase 1 の台帳で
pane_idに解決する。inspect_paneはペイン制御面なので、 使うのはlist_panes由来のpane_idであってpeer_idではない:- 台帳に
pane_idが無いピア(list_panesに居ない、または Phase 1 の列挙面のゲート (i) が 不成立で台帳全体に id を載せなかった)は 3 通りに分かれる。いずれもスクレイプは 諦め、Step 4 の git 情報だけで状態を推定するが、記録する状態が違う:same_tab=False/ ‡ で他タブと導出されたピア → 「他タブに居るのでスクレイプ不能」。生存して いる可能性があるので消滅と読み替えない- それ以外で、F-1 の (i)
pane_exited観測 or (ii) 列挙が窓口タブのものと確認済み →「消滅済み」 - それ以外で F-1 のどちらも取れない → indeterminate。フォーカスが別タブへ移っただけで 生存中のピアが不在に見えている可能性があるので、消滅済みとして記録せず判定不能のまま残し、 Phase 4 の最終報告に載せる。列挙面のゲート (i) が不成立のケースはここに落ちる — 列挙を 自タブと確立できていない以上 F-1 の (ii) も同時に満たせないため
- Phase 1 から時間が経っているので、使う直前に
mcp__org-broker__list_panesを取り直し、そのpane_idのname/roleが台帳と一致することを確認してから叩く。一致しなければ pane_id が別ペインへ再割当てされているので、そのペインには触れずスクレイプを諦める (Phase 3.7 と同じ identity 照合の規律。無関係なペインの画面を当該ワーカーの 「Current State at Suspend」に書き込むのを防ぐ)
- 台帳に
- 画面内容スクレイプで最新のコンソール出力を読む:
mcp__org-broker__inspect_pane(target="<pane_id>", format="text")target="worker-{task_id}"の名前指定はしない — 2.0 系では名前解決が呼び出し側のタブ内に 限定され、同名ペインが別タブに存在しうるため。画面表示だけでは不十分な場合は、次の Step 4 の git 情報で補完する - ワーカーの作業ディレクトリで以下を実行:
git statusgit diff --statgit log --oneline -5
- これらの情報からワーカーの状態を推定する
Phase 3: 状態書き込み
state-db cutover (M4, Issue #267):
.state/state.dbが唯一の SoT。 構造化セクション (Status / Updated / Suspended / Dispatcher / Curator / Worker Directory Registry / Active Work Items / Resume Instructions) は 必ず StateWriter 経由で書く。transaction()の post-commit hook が.state/org-state.mdを DB から自動再生成する (markdown 直接編集禁止 — drift_check で検出される)。free-form な session notes / Pending Lead / 学び等はnotes/配下に保存する (notes/README.md参照)。.state/journal.jsonlは M4 で廃止 (events テーブルが SoT)。 DB が古い場合はpython -m tools.state_db.importer --db .state/state.db --rebuild --no-strictで再構築する。
-
既存の
org-state.mdをorg-state.prev.mdにコピー(バックアップ) -
DB に Status / Suspended を書く (
StateWriter.transaction()経由。post-commit hook が.state/org-state.mdを自動再生成、regen 失敗時も DB は確定済みで stderr 警告のみ):python -c " from datetime import datetime, timezone from pathlib import Path from tools.state_db import connect from tools.state_db.writer import StateWriter ts = datetime.now(timezone.utc).strftime('%Y-%m-%dT%H:%M:%fZ') conn = connect('.state/state.db') with StateWriter(conn, claude_org_root=Path('.')).transaction() as w: w.update_session(status='SUSPENDED', suspended_at=ts, updated_at=ts) "- bash / zsh / PowerShell すべて
"..."内の改行をそのまま透過するので multi-line でも cross-shell。Windows CMD は heredoc 不可なのでpy -3 -c "ts=...; conn=...; w=...; w.begin(); w.update_session(...); w.commit()"の単行 fallback を使う(その場合transaction()の rollback / regen 自動 swallow は失われるので追加で try/except を書く) - 同コマンドが
.state/org-state.mdの Status 行をSUSPENDEDに切り替える (DB 由来で再生成) - free-form な「Resume Instructions の補足説明」「Pending Lead」「学び」等は
notes/に保存する (notes/README.md参照)。markdown 直接編集は drift_check で検出される。update_session(resume_instructions=...)は構造化セクションとして DB に書く
- bash / zsh / PowerShell すべて
-
各 Work Item の状態を更新する場合は
upsert_run(task_id=..., status=...)をtransaction()内で呼ぶ -
各ワーカーの
.state/workers/worker-{id}.mdを更新:- Current State at Suspend セクションを追加/更新
- Progress Log に中断時の状態を追記
-
suspend イベントを DB に追記 (
tools/journal_append.pyは M4 で DB-only ルーティング。tsは自動付与):py -3 tools/journal_append.py suspend \ reason=user_requested \ --json '{"active_workers": ["worker-xxx"], "pending_items": ["blog-redesign"]}'event 名と payload key の規約は
docs/journal-events.mdを参照。
Phase 3.5: ダッシュボードサーバー停止
kill $(cat .state/dashboard.pid 2>/dev/null) 2>/dev/null || true
注: この blind kill は「/org-start で再開する前提の中断」なので簡素なまま残す。daemon ごと 落とす
/org-downでは、pid recycle による誤 kill を避ける stale-pid-safe な 停止(/proc/Get-CimInstanceの CommandLine 照合)に差し替える。
Phase 3.6: secretary_queue_watcher の停止(broker のみ)
broker 面(ORG_TRANSPORT=broker)で org-start Block C3 が run_in_background で
常駐させた滞留 watcher を停止する。renga では watcher が存在しない(queue.jsonl 非依存)ので、
transport が renga なら本 Phase はまるごと skipする。
watcher は起動時に .state/secretary_queue_watcher.json へ自分の pid / cwd / cmdline / started_at /
broker_state_dir を記録している。停止は pid 単独で kill せず、(a) 記録された broker_state_dir が
現在の ORG_BROKER_STATE_DIR と一致し(別 org / 別 broker の watcher 誤停止防止)、かつ (b) pid が
生存し live argv(Linux/WSL は /proc/<pid>/cmdline、macOS/BSD は ps -p <pid> -o args= フォールバック)が
本 watcher であることを照合できたときだけ SIGTERM する。照合が外れたら kill せず sidecar を stale として
削除する(誤 kill 防止)。この照合ロジックは helper に入っているので、POSIX では 1 行呼ぶだけでよい:
Mac / Linux / WSL:
python3 tools/secretary_queue_watcher.py --stop # Windows で console python を使う場合は py -3 ...
出力の 1 行(STOP: ...)で結果を確認する(SIGTERM を送信し停止 / stale sidecar を削除 /
既に停止済み)。exit 0 が正常系(停止・stale 掃除・既停止のいずれも 0)。macOS は ps フォールバックで
identity 照合できるので --stop がそのまま効く。exit 2(identity 未確認)は /proc も ps も無い環境
(Windows native)でのみ出るシグナルで、その場合は次の PowerShell 手順を使う。
Windows native(PowerShell) — /proc が無く helper の argv 照合が使えないので、
Get-CimInstance Win32_Process の CommandLine で identity を照合してから Stop-Process する
(kill -0 / kill -TERM の直訳ではなく Windows 別手順):
$pf = ".state\secretary_queue_watcher.json"
if (Test-Path $pf) {
$rec = Get-Content $pf -Raw | ConvertFrom-Json
$wpid = [int]$rec.pid
$ownOk = $false
try {
if ($env:ORG_BROKER_STATE_DIR) {
$ownOk = ((Resolve-Path $rec.broker_state_dir).Path -eq (Resolve-Path $env:ORG_BROKER_STATE_DIR).Path)
} else {
$ownOk = ((Resolve-Path $rec.cwd).Path -eq (Get-Location).Path)
}
} catch { $ownOk = $false }
$proc = Get-CimInstance Win32_Process -Filter "ProcessId=$wpid" -ErrorAction SilentlyContinue
$idOk = $proc -and ($proc.CommandLine -match 'secretary_queue_watcher\.py')
if ($ownOk -and $idOk) {
Stop-Process -Id $wpid -Force
Write-Output "secretary_queue_watcher (pid=$wpid) stopped"
} else {
Write-Output "watcher pid stale / different org / not running; not killing, removing stale sidecar"
}
Remove-Item $pf -ErrorAction SilentlyContinue
}
Phase 3.7: attention watcher の停止(ペイン teardown の前)
attention watcher は dispatcher ペインの右 split に常駐する CLI ペインなので、Phase 4 のペイン 一括 teardown より前に停止する(dispatcher を先に閉じると attention ペインが孤児化 / pane_id recycle され、後続の識別を壊すため)。attention watcher を起動していないセッションでは sidecar も live pane も無く、本 Phase は no-op。
停止は /org-attention-stop と同じ identity 照合を使う
(sidecar の pane_id を無検証で close_pane しない。pane_id が別ペインへ再割当てされていると
無関係なペインを kill する — Issue #468):
mcp__org-broker__list_panesでname="attention"またはrole="attention"の live pane を 全て集める(= 確認済み attention ペイン集合。各レコードのid(= pane 制御用の pane_id) を控える)。 この列挙も (G-OWNTAB) の 2 手段で自タブのものと確立してから使う — 確立できなければ close へ 進まない(相対セレクタへフォールバックしない)。以降の手順 5 / 6 は gone / recycled と同じ 「close しなかった」経路として扱い、attention watcher を停止できなかったことを人間への報告に 明記する(Phase 4 手順 7 の部分中断報告に載せる).state/attention_pane.jsonをReadで開けたらpane_idを読む(= sidecar pane_id)。無ければ 「sidecar 無し」- sidecar pane_id を list_panes が返す name/role で分類する(sidecar 記録の name は信用しない):
- verified(確認済み集合に含まれる)→ その pane はいまも本物の watcher。close 対象
- recycled(list_panes にあるが name/role が attention でない)→ pane_id が別ペインへ再割当て済み。 絶対に close しない
- gone(list_panes に無い)→ 既に消滅。close しない
- 確認済み集合の各ペインを
list_panes由来のpane_idでmcp__org-broker__close_pane(target="<pane_id>")する (target="attention"の name 指定はしない — role だけ持つ孤児に当たらないため)。 ここは pane 制御文脈で、かつ対象は自タブと確立済みのmcp__org-broker__list_panes(手順 1)由来= caller のタブに居ることが確定 しているので、[pane_not_found]/[pane_vanished]は既に閉じた扱いで skip してよい(close の 目的は消滅であり、既に消えていれば結果は同じ。他タブのペインに対する例外と messaging 文脈での 扱いは Phase 4 の「[pane_not_found]の扱い — messaging と pane 制御で分岐する」を参照) - sidecar があれば 分類によらず必ず削除する:
rm -f .state/attention_pane.json # Windows native は del .state\attention_pane.json - journal に 1 行追記する。実際に close したペインがある場合のみ pane_id を載せ、recycled / gone で
close を 1 つも行わなかった場合は
reason=stale_sidecarにする(無関係なペインを停めた誤記録を防ぐ):bash tools/journal_append.sh attention_watch_stopped pane_id=<N> # close した場合 bash tools/journal_append.sh attention_watch_stopped reason=stale_sidecar # close しなかった場合
分類別の詳細な挙動と報告文は /org-attention-stop を参照(本 Phase は
その要点を suspend フローに埋め込んだもの)。
Phase 4: 全ペイン停止
停止順序が重要。ワーカー → ディスパッチャー → キュレーターの順で停止する。
このフェーズは全ステップを Phase 1 の台帳の id で回す: mcp__org-broker__send_message の to_id は
peer_id、mcp__org-broker__close_pane の target は pane_id、pending 集合と poll_events の突合は台帳
エントリを主キーにする。name は人間可読のラベルとしてのみ扱い、宛先解決には使わない — 2.0 系では
名前解決が呼び出し側のタブ内に限定され、同名ペインが別タブに存在しうるため。
[pane_not_found] の扱い — messaging と pane 制御で分岐する
本フェーズ(および Phase 1 / Phase 2)の送信・ペイン制御すべてに適用する分岐。分岐の第一軸は
呼び出し文脈、第二軸は同タブ / 他タブである(世代では分けない)。正本は
.claude/skills/org-delegate/references/renga-error-codes.md。
- messaging 文脈(
mcp__org-broker__send_message。broker では[peer_not_found]が同じ意味で返る): 受けても即「ワーカー閉鎖」と断定しない。2.0 系では名前解決が送信者タブ内に限定され、また 宛先台帳が古く id が再割当て / 消滅していると、生存中のペインでもこのコードが返るため、次の順で 切り分ける:mcp__org-broker__list_peersを取り直す(2.0 系は全タブ列挙なので他タブのワーカーもここで拾える)- 宛先の選び直しは名前で行わない。全タブ列挙では
nameが一意でなく(契約 T-§2.2)、 同名レコードが複数返りうるので、名前一致で拾うと別 org のピアへ SUSPEND / SHUTDOWN を 送りうる。次の優先順で 1 件に絞る(.claude/skills/org-delegate/references/renga-error-codes.mdの messaging 分岐と同一規則):- a. 台帳に控えてある
peer_idがまだ列挙に在れば、その id をそのまま使う - b. その
peer_idが消えている場合(再接続で id が振り直された等)、名前一致レコードは 候補にすぎない。採用してよいのは名前が一意に定まる範囲に閉じていると分かっている ときだけで、次のどちらかを満たす場合:same_tab: trueを確認できたレコード(同一タブ内でのみ名前は一意 — 契約 §1.8)- 旧版 fallback の列挙(全レコードで marker 欠落)で、その名前のレコードが 1 件だけの場合。
marker 無し列挙は caller のタブに閉じていることが契約 T-§cap の conformance MUST で保証され、
そのタブが窓口のタブであることは手順 1 のフォーカス前提で確認済みなので、名前は一意に働く。
現在配備の backend(
org-broker/ renga 1.x)はここを通る — この枝が無いと、再接続で id が変わった生存ワーカーを retarget できず indeterminate に落として取り逃がす
- c. 上のどちらも満たさない(他タブの同名しか無い / 同名が複数残る)→ 再送しない。 lifecycle 確認(次項)へ進む
- a. 台帳に控えてある
- 絞れた
peer_idで 1 回だけ再送する(ループにしない — 2 回目以降も結果は同じで、 本当に閉じていた場合の検知が遅れるだけ) peer_id宛でも失敗した / 2-c で再送できなかったときに初めて lifecycle 確認へ進む。ここで確認を省いて いきなり閉鎖確定にしない(契約 T-§2.1 の復旧順は確認段を含む):- 同タブのピア:
pane_exitedの観測をもって消滅を確定する。mcp__org-broker__list_panesの不在を根拠に するのは F-1 の (ii) を満たすときだけで、満たせなければ indeterminate として残す - 他タブのピア(
same_tab=False/ ‡ で他タブと導出):list_panesには原理的に出ないので 不在は証拠にならない。pane_exitedの観測か、後続mcp__org-broker__list_peersからの消失で確定する - どちらも取れなければ indeterminate。閉鎖に倒さず journal に記録して人間に報告する
- 同タブのピア:
- 消滅が確定したら pending 集合から外す。worker 状態ファイルの
Status: pane_closedは窓口が 書かない — この terminal transition の書き手はディスパッチャーである (docs/contracts/state-semantics-contract.md)。 窓口は Progress Log への追記と journal 記録に留める
- pane 制御文脈(
mcp__org-broker__close_pane/mcp__org-broker__inspect_pane/mcp__org-broker__list_panesの target):- 同タブのペインに
pane_id宛で[pane_not_found]/[pane_vanished]が返ったら、従来どおり 「既に閉じた」扱いで skip してよい(close は消滅させるのが目的なので、既に消えていれば結果は同じ) - 他タブのペインは例外 — 閉鎖と読んではならない。契約 T-§4.2 はタブ横断の pane-addressed
control が
pane_not_foundを返すと規定しているので、生存中でも同じコードが返る。 このコードを「閉じた」と読むと、稼働中のワーカーを残したまま「全ペイン停止」を完了報告する。 該当ピアは pending に残し、journal に記録して人間に報告する(停止できない残存) - 名前宛で受けた場合はこの結論を出さず、
pane_idに解決し直してから判断する close_pane/set_pane_identityはそもそも名前宛・"focused"宛で撃たない((G-MUST))。 この 2 本は Group B なので、caller_scopeが広告されていてもcaller_scope_close_identityが確立していなければ相対セレクタが user 可視タブ / 全タブに 解決されうる。(G-OWNTAB) の 2 手段で自タブと確立したmcp__org-broker__list_panes由来の数値pane_idでのみアドレスする
- 同タブのペインに
停止手順
-
mcp__org-broker__list_peersとmcp__org-broker__list_panesを取り直し、Phase 1 の台帳を最新化する(Phase 1 から 時間が経っているため、peer_id/pane_idの両方を取り直す)。再構築でも Phase 1 手順 1 の join 規則をそのまま適用する(pane_idの出所はmcp__org-broker__list_panesだけ / 列挙を自タブと 確立できなければどのエントリにもpane_idを割り当てない((G-OWNTAB)) /roleは join キーに しない / 他タブ・same_tab欠落のエントリにはpane_idを割り当てない)。他タブ判定のピアが Phase 1 から増えていた場合は、真理値表の operational gate に従ってその差分を人間に報告し、 確認を得てから停止指示へ進む- この取り直しが (G-OWNTAB) の確立段でもある: 以降の
mcp__org-broker__close_paneはすべてこの列挙から 採った数値pane_idで撃つので、列挙を自タブと確立できたかをここで判定して以降の全 close に 引き回す(単一タブ backend =org-broker/caller_scopeの 2 手段だけ。list_peersのsame_tabは確立手段に数えない)。いずれも成立しなければ close を 1 つも撃たない — 台帳の 全エントリが「pane_idを持たないエントリ」と同じ扱いになり、pending のまま手順 7 の部分中断 報告に載せる(相対セレクタへフォールバックしない)
- この取り直しが (G-OWNTAB) の確立段でもある: 以降の
-
ワーカーを先に停止: Phase 1 の宛先選定の真理値表を
R = {worker}で適用し、選ばれた ワーカーのpeer_idをto_idに渡してmcp__org-broker__send_messageで終了を指示: 「SHUTDOWN: 作業を終了してください。」 -
ワーカーペインが閉じたことを確認 — 2-pass 構造で実施:
Pass 1 (polite shutdown の観察、最大 10 秒):
mcp__org-broker__poll_eventsでpane_exitedを long-poll する。types=["pane_exited"]フィルタで他 type を除外しつつ、deadline 内でループして待機対象が全て閉じたら break:pending = {SHUTDOWN を送った全ワーカーの台帳エントリ set} cursor = None # 初回は since 省略 deadline = now + 10 秒 while pending not empty and now < deadline: remaining_ms = (deadline - now) ミリ秒 result = mcp__org-broker__poll_events( since=cursor, timeout_ms=min(remaining_ms, 10000), types=["pane_exited"] ) cursor = result.next_since for ev in result.events: # 突合は id で行う。契約 T-§3.1 が canonical 名を固定している ev_pane = ev.id ?? ev.pane_id # canonical: ev.id (list_panes と同じ id 空間) ev_peer = ev.peer_id ?? ev.agent_id # canonical: ev.peer_id (list_peers と同じ id 空間) # id 突合は「両辺が非 null」を前提にする。name のみのイベントでは ev_pane / ev_peer が # どちらも null になり、pane_id 未設定 (他タブ) のエントリと null == null で誤一致して # 無関係な生存ワーカーを pending から external してしまう hit = pending の中で (ev_pane is not None かつ entry.pane_id == ev_pane) または (ev_peer is not None かつ entry.peer_id == ev_peer) if hit is None and ev.name is not None: # name フォールバックは「同タブ確定エントリ」に限定する(下記) hit = pending の中で entry.name == ev.name かつ entry が同タブ確定 if hit: pending.remove(hit) # deadline 到達 or pending が空で抜ける- 突合キーは id。ratified §3.1 は per-event フィールド名を規定していないが、批准済み amendment (2026-08-09) の
T-§3.1 が canonical 名を
id(pane 側)/peer_id(peer 側)に固定している。上の疑似コードがev.pane_id/ev.agent_idも見るのは、canonical 名を出さない backend への**寛容(tolerance)**で あって、これらが正の名前だという意味ではない。pane 側 id と peer 側 id のどちらか一方でも 一致したら hit とする(他タブピアはlist_panesに出ないので pane 側だけでは join できない) name単独を突合キーにしてはならない。2.0 系の名前はタブ内一意でしかないので、別タブの 同名ペインのpane_exitedで自組織の生存ワーカーを誤って retire する。nameフォールバックはsame_tab=True/ 旧版 fallback で同タブと確定しているエントリに限って使う(他タブ対象が pending に居る間は name フォールバックを無効にしてもよい)- 初回
since省略で「今以降のイベントだけ」セマンティクス(過去の pane_exited を replay しない) types=["pane_exited"]filter は cursor を全 type で advance させるので重複 scan なし- filter 不一致イベント到着で long-poll が early return (
events:[]+ advanced cursor) するため、空応答時は deadline までループ継続 - 10 秒以内に閉じなかった残留ワーカーは Pass 2 へ
Pass 2 (残留ワーカーへのフォールバック + 再確認、最大 5 秒):
- close の前に (G-OWNTAB) が成立していることを確かめる(手順 1 で判定済み。成立していなければ
この Pass 2 では 1 つも close せず、
pendingをそのまま次の再確認へ送る) - close の直前に identity 照合する(Phase 3.7 の attention watcher 停止と同じ idiom。台帳を
取り直してから時間が経つと
pane_idが別ペインへ再割当てされうるため、控えた id が期待どおりの ペインをなお指していることを毎回確かめる):mcp__org-broker__list_panesを実行する- 控えた
pane_idのレコードがname == "worker-{task_id}"かつrole == "worker"をなお 指している → その数値 id で close する - レコードはあるが
name/roleが一致しない(別ペインへ再割当て済み)→ close しない (相対セレクタへフォールバックしない)。pendingに残したまま次の再確認へ送る - レコードが列挙に無い → close 対象を確認できないので close しない。消滅の確定は F-1 の (ii) を満たすときだけで、満たせなければ「判定不能」に倒す
pendingに残っているエントリのうち、pane_idを持ち、上の照合を通ったもの (= caller のタブに居ることが確定したペイン)それぞれに対して:
でペインを明示破棄する(mcp__org-broker__close_pane(target="<照合済みの数値 pane_id>")worker-{task_id}のような相対セレクタでは撃たない — (G-MUST))。成功時は"Closed pane id=N."テキストが返る。ここは pane 制御文脈なので[pane_not_found]/[pane_vanished]は既に閉じた扱いで skip してよい(.claude/skills/org-delegate/references/renga-error-codes.md参照)。[last_pane]はワーカー停止段階では通常発生しない(窓口/ディスパッチャー/キュレーターが残っているため)pane_idを持たないエントリ(他タブのワーカー、および (G-OWNTAB) 不成立で id を割り当てられ なかったエントリ)にはclose_paneを撃たない。タブ横断の pane 制御は原理的に不可で、返る[pane_not_found]は閉鎖の証拠にならない(契約 T-§4.2)。 このエントリは pending に残したまま次の再確認へ送る- その後、同じ
poll_eventsループをtimeout_ms=5000/ deadline 5 秒で再度回し、close_pane 由来のpane_exitedを消化する - Pass 2 後もまだ
pendingが空でなければmcp__org-broker__list_panes/mcp__org-broker__list_peersで生存確認する(id 突合。list_panesは 1 タブ分しか返さないため、他タブのワーカーはlist_peersからの消失で判定する。list_panesの不在を停止の証拠にするのは F-1 の (ii) を満たすときだけで、満たせなければ「判定不能」に倒す)。残存または判定不能なら、閉鎖扱いにせず journal に記録して人間に報告する(強制終了は現状未サポート。「停止できなかったワーカーが居る」ことを報告に明記し、全ペイン停止を偽って完了報告しない)
- 突合キーは id。ratified §3.1 は per-event フィールド名を規定していないが、批准済み amendment (2026-08-09) の
T-§3.1 が canonical 名を
-
ディスパッチャーを停止: 真理値表を
R = {dispatcher}で適用し、選ばれたディスパッチャーのpeer_idをto_idに渡してmcp__org-broker__send_messageで終了を指示: 「SHUTDOWN: 作業を終了してください。」 -
キュレーターを停止(存在する場合のみ): curator は常駐しないため、通常このステップは no-op。curator の存在確認は
mcp__org-broker__list_peersのrole == "curator"(name == "curator"は補助) で行い、peer_idを得る(list_panesは呼び出し側のタブしか返さないので、別タブで起動した curator を取りこぼす)。存在した場合(オンデマンド curate 実行中に suspend が重なったケース) のみ真理値表をR = {curator}で適用し、そのpeer_id宛にsend_messageで終了を指示: 「SHUTDOWN: 作業を終了してください。」(curate は move-then-mark 設計のため途中停止でも 破壊的な中間状態は残らない) -
ディスパッチャー(および存在した場合のみキュレーター)も (3) と同じ 2-pass 構造で確認する。 pending 集合は名前ではなく台帳エントリの集合にする(
pending = {dispatcher のエントリ}、 curator が存在した場合はそのエントリも追加)。pane_exitedの突合も (3) と同じく pane 側 id / peer 側 id のいずれか一致で行い、ev.role == "dispatcher"/"curator"とev.nameは同タブ確定エントリに対する補助に留める:- Pass 1:
poll_events(types=["pane_exited"], timeout_ms=10000)相当ループ - Pass 2: (3) と同じ前段((G-OWNTAB) の成立確認 → close 直前の identity 照合。期待する
name/roleはdispatcher/curatorに読み替える)を通したうえで、pendingに残った エントリのうちpane_idを持ち照合を通ったものにmcp__org-broker__close_pane(target="<照合済みの数値 pane_id>")を送り(相対セレクタでは撃たない)、poll_eventsループ (timeout_ms=5000) で再確認。pane_idを持たない(他タブの / (G-OWNTAB) 不成立の)エントリや 照合が外れたエントリには撃たず、(3) と同じく 残存として人間に報告する
- Pass 1:
最後のペイン (窓口) の扱い: ディスパッチャー(と存在した場合のキュレーター)を閉じた時点で、
窓口ペインが窓口タブに残る最後のペインになる。窓口は自分自身を mcp__org-broker__close_pane しない。
その根拠は次の 2 つで、どちらも「最後の 1 ペインだから backend が閉じさせてくれない」ことには
依存しない構造的な理由である:
- 窓口は人間との唯一の接点である: 自分を閉じると、この後の「人間に報告」ステップで suspend 完了を 渡す相手の画面が消える。報告前に自分を殺すと、人間から見て「中断できたのか分からないまま画面が 消えた」になる
- 自己終了は人間の責務境界にある: 窓口プロセスの終了は人間の
exit//exit/ 端末クローズに 委ねる。org-suspend が担うのは「状態保存 + ja 管理下の補助プロセスとペインの停止」までで、窓口 ペインを閉じる責任は負わない(冒頭の「責務境界(/org-suspend と /org-down)」で引いた線と同じ)
[last_pane] はエラーハンドリングとしてのみ残す: 何らかの経路で窓口ペインに close_pane が飛び
[last_pane] が返った場合は、強制再試行せずそのまま人間の exit に委ねる。ただしこのコードを
自己 close の安全網として当てにしてはならない — このコードの意味は
.claude/skills/org-delegate/references/renga-error-codes.md
のとおり「唯一のタブの唯一のペイン」であり、他のタブが存在する状況で窓口タブの最後のペインが
保護されるかは 2.0 系では未確認である(保護されない読みが素直だが、いずれにせよ当てにしない)。
自己 close をしない根拠は上の 2 つだけである。
-
人間に報告。
pendingとunresolvedが両方空のときだけ「中断しました」と言い切る。 どちらかに残りがあれば部分中断として報告し、完了を偽らない:全て停止できた場合:
組織を中断しました。 - 保存済み: {N}件の作業アイテム - 状態ファイル: .state/org-state.md /org-start で再開できます。停止できなかった / 判定できなかったピアが残る場合(
pendingが空でない、unresolvedが 空でない、他タブの対象 role ピアを検出した、Phase 3.7 で attention watcher を停止できなかった、 のいずれか):組織を部分的に中断しました(停止しきれていないペインがあります)。 - 保存済み: {N}件の作業アイテム - 状態ファイル: .state/org-state.md - 停止できなかったワーカー: {peer_id / name / cwd を列挙} - role が判定できず対象外にしたピア: {peer_id / name / cwd を列挙} - 他タブに見えたが自組織と確認できずに対象外にしたピア: {peer_id / name / cwd を列挙} - 停止できなかった attention watcher: {pane_id を列挙} これらは走り続けている可能性があります。手動での確認 / 停止をお願いします。 /org-start で再開できますが、上記のペインが残っている前提でご判断ください。- 該当が無いセクションは行ごと省く。1 件でも残っていれば「組織を中断しました」の文面は使わない (全ペイン停止を偽って報告しないこと自体が本フェーズの目的)
レビュー
まだレビューはありません。使ってみた感想をお寄せください。
同じリポジトリのスキル
概要と使いどころ
/dispatcher-handover で書き出した handover ファイルを読み込み、 ディスパッチャーを新しい session で復帰させる。/clear 直後の最初のターンで使う。 state.db の dispatcher_pane_id / dispatcher_peer_id を atomic に更新し、 worker monitoring の /loop 3m を再開する。 「ディスパッチャー復帰」「resume」「引き継ぎから再開」と secretary から指示された ときに使う。/org-start ではない(ワーカー・窓口・キュレーターは生きている前提)。
プロジェクトのゴール台帳(registry/goals/<owner>/<repo>.md)を人間と対話して新規作成・改訂する。 README と open Issue からゴール候補を推定して提示し、「何を目指すか」「何が起きていたら未達か」を 短く質問して条項を固め、書式検証(tools/work_discovery_goals.py validate)を通してから保存する。 「ゴール台帳を書きたい」「ゴールを設定して」「ゴール未設定と出た」「台帳エラーを直して」 「このプロジェクトの目標を決めたい」等や、/work-discovery の「ゴール未設定」「ゴール台帳エラー」案内を 受けて窓口が起動する。候補の提示そのものは /work-discovery の役目で本スキルではない。
組織の生きているペイン(secretary / dispatcher / worker)へ tmux で**直接入る** ための **コマンドを出力するだけ** の read-only スキル。`mcp__org-broker__list_panes`(論理ペイン) と `/usr/bin/tmux -L claude-org-broker list-panes -a`(pane_id ↔ session マッピング)を pane_id(%N)で突き合わせ、各ペインに role + name(task_id) ラベル付きの attach コマンドを 生成する(読取専用 `-r` / 書込は `-r` 無し / デタッチは `Ctrl-b d`)。 「ワーカーのペインを見たい」「ワーカーのペインに入りたい」「dispatcher の様子を直接見たい」 「ペインに入りたい」「あのペインを直接覗きたい」「attach コマンド教えて」 「tmux でつなぎたい」「worker の画面に入る」等で発動。 自分で attach はせず・ペインも一切変更しない(コマンド文字列とペイン一覧表を表示するのみ)。 作業委譲(org-delegate)・ダッシュボードで状況一覧(org-dashboard)・watcher 停止(org-attention-stop) には発動しない。
attention notification watcher(承認待ち / 判断待ち / CI 失敗等を OS 通知 + 音で能動通知)を dispatcher ペインの右側に split で常駐起動する。`.state/attention.json` が未配置なら ja 既定テンプレートを `tools/templates/attention.example.json` から自動コピーする。 起動後のペイン id は `.state/attention_pane.json` に記録し、`/org-attention-stop` から参照する。 「attention 起動」「通知監視を始めて」「watcher を立てて」等で発動。 `/org-start` からの auto-start はしない(明示起動推奨ポリシー)。
`/org-attention-start` で起動した attention watcher ペインを停止する。 `.state/attention_pane.json` に記録された pane_id を `list_panes` の name/role で identity 確認し、いまも attention watcher を指している場合だけ `close_pane`(pane 破棄)で 破棄する(pane_id が別ペインへ再割当て済みなら close せず stale sidecar として削除)。 「attention 止めて」「通知監視を停止」「watcher 落として」等で発動。