Skip to content
Back to skills

Org Suspend

ASecurity

組織を中断し、全状態をディスクに保存する。「中断」「保存して終了」 「閉じたい」「一旦やめる」「今日は終わり」と言われたときに使う。

  • 5 stars
  • 0 votes
  • 0 copies
  • 2 views
  • Added October 5, 2026
ai-agentspythonrustgoshellbashsqlgitapibackend

Works with

  • claude code
  • cursor
  • terminal
  • cli
  • api
  • mcp

Security analysis

A100/100

Pro scans all 2 files and shows the line behind each finding

Scanned October 5, 2026

npx -y skills add suisya-systems/claude-org-ja --skill org-suspend --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Org Suspend?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Org Suspend
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/suisya-systems-org-suspend/badge)](https://www.skillsdirectory.com/skills/suisya-systems-org-suspend)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: org-suspend
description: >
  組織を中断し、全状態をディスクに保存する。「中断」「保存して終了」
  「閉じたい」「一旦やめる」「今日は終わり」と言われたときに使う。
effort: low
allowed-tools:
  - Read
  - Bash(bash tools/journal_append.sh:*)
  - Bash(py -3 tools/journal_append.py:*)
  - Bash(py -3 tools/capability_gate.py:*)
  - Bash(python3 tools/capability_gate.py:*)
  - Bash(python -c "from tools.state_db import connect;*)
  - Bash(python3 -c "from tools.state_db import connect;*)
  - Bash(python -m tools.state_db.importer:*)
  - Bash(python3 tools/secretary_queue_watcher.py:*)
  - Bash(py -3 tools/secretary_queue_watcher.py:*)
  - Bash(rm -f .state/attention_pane.json)
  - Bash(del .state\attention_pane.json)
  - mcp__org-broker__check_messages
  - mcp__org-broker__close_pane
  - mcp__org-broker__inspect_pane
  - mcp__org-broker__list_panes
  - mcp__org-broker__list_peers
  - mcp__org-broker__poll_events
  - mcp__org-broker__send_keys
  - mcp__org-broker__send_message
  - mcp__org-broker__set_pane_identity
  - mcp__org-broker__set_summary
  - mcp__org-broker__spawn_claude_pane
  - mcp__org-broker__spawn_pane
---

# 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-down/SKILL.md))**: /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/SKILL.md) の責務で、/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-in `renga`**: 本ファイル(および各スキル)の `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.md`](../../../docs/contracts/backend-interface-contract.md) Surface 8 + push-primary amendment(broker push 一次が **既定の契約**、pull は fallback として retain)。**opt-in `renga` は削除せず常時有効な切戻しの安全装置**として維持する。broker 実走(dogfood)は Epic #6 Issue G スコープで本ファイルの既定運用経路ではない(**二フレーム注記(Refs #604)**: ここの「既定 `broker`」は**コード既定**(`tools/transport.py: DEFAULT_TRANSPORT`、生成面はこれで render)。**運用既定**は broker dogfood が Epic #6 Issue G まで未活性のため `renga` で、両者は指す対象が異なり矛盾しない。総説は root [`CLAUDE.md`](../../../CLAUDE.md)。)

## Phase 1: ワーカー状態収集

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_peers` capability を広告する 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" と書いている
     > — renga `docs/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`](../../../.dispatcher/references/pane-close.md) の
     >   「照合の前に「その列挙が自タブのものか」を確立する」節で、両者は同一規則であり片方だけを
     >   緩めない):
     >   1. **backend が Group B を自身の単一タブモデル内で解決する** — `org-broker` は Group B も
     >      単一タブモデル内で解決するので、列挙は構成上つねに自タブのもの(契約 §8.1 / §8.10)
     >   2. **`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 として人間に報告する
     >   (相対セレクタへフォールバックしない)
     > - **かつて 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」であり、旧挙動での継続ではない)
   - > **フォーカス前提(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` / 欠落のレコードを除外する**
   - **突合(join)キーは `name` だけで、`role` は使わない**。`role` は非一意で(worker が複数居れば
     必ず衝突する)join キーとして成立しない。`name` も**全タブ列挙では一意ではない**ので
     (契約 T-§2.2: §1.8 の名前一意性は "this tab" 限定。`worker-{task_id}` 等の予約名は 2 org 並走で
     構造的に衝突する)、`name` join が効くのは **`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` は人間可読の補助ラベルに留める
2. 直後の「**宛先選定の真理値表(Phase 1 / Phase 4 共通)**」で broadcast 対象を決める
   (Phase 1 の対象 role 集合は `R = {worker, dispatcher}`)。**版判定が 2.0 系だった場合は、同節の
   operational gate に従って手順 3 の送信前に人間へ報告し、確認を得てから進む**(旧版 fallback では
   gate を発動せず、そのまま手順 3 へ進む)
3. 対象ピアの **`peer_id`** を `to_id` に渡して `mcp__org-broker__send_message` で以下を送信:
   ```
   SUSPEND: 現在の状態を報告してください。
   1. これまでに完了したこと
   2. 変更したファイル(コミット済み/未コミット)
   3. 次にやろうとしていたこと
   4. ブロッカーや未解決の問題
   ```
4. 30 秒間 `mcp__org-broker__check_messages` で応答を待つ(5 秒間隔でポーリング)
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`](../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/capability_gate.py) と
  `tools/journal_append.sh` で行う。以後の `/org-suspend`([`.claude/skills/org-down/SKILL.md`](../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`](../../../.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: 未応答ワーカーのスクレイプ

応答がなかったワーカーについて:

1. `.state/workers/` から該当ワーカーの状態ファイルを読み、Pane Name と Directory を取得
2. 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」に書き込むのを防ぐ)
3. 画面内容スクレイプで最新のコンソール出力を読む:
   ```
   mcp__org-broker__inspect_pane(target="<pane_id>", format="text")
   ```
   `target="worker-{task_id}"` の**名前指定はしない** — 2.0 系では名前解決が呼び出し側のタブ内に
   限定され、同名ペインが別タブに存在しうるため。画面表示だけでは不十分な場合は、次の Step 4 の
   git 情報で補完する
4. ワーカーの作業ディレクトリで以下を実行:
   - `git status`
   - `git diff --stat`
   - `git log --oneline -5`
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`
> で再構築する。

1. 既存の `org-state.md` を `org-state.prev.md` にコピー(バックアップ)
2. **DB に Status / Suspended を書く** (`StateWriter.transaction()` 経由。post-commit hook が `.state/org-state.md` を自動再生成、regen 失敗時も DB は確定済みで stderr 警告のみ):

   ```bash
   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 に書く
3. 各 Work Item の状態を更新する場合は `upsert_run(task_id=..., status=...)` を `transaction()` 内で呼ぶ
4. 各ワーカーの `.state/workers/worker-{id}.md` を更新:
   - Current State at Suspend セクションを追加/更新
   - Progress Log に中断時の状態を追記
5. suspend イベントを DB に追記 (`tools/journal_append.py` は M4 で DB-only ルーティング。`ts` は自動付与):
   ```bash
   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`](../../../docs/journal-events.md) を参照。

## Phase 3.5: ダッシュボードサーバー停止

```bash
kill $(cat .state/dashboard.pid 2>/dev/null) 2>/dev/null || true
```

> **注**: この blind kill は「/org-start で再開する前提の中断」なので簡素なまま残す。daemon ごと
> 落とす [`/org-down`](../org-down/SKILL.md) では、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**:
```bash
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 別手順):
```powershell
$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`](../org-attention-stop/SKILL.md) と同じ **identity 照合**を使う
(sidecar の pane_id を無検証で `close_pane` しない。pane_id が別ペインへ再割当てされていると
無関係なペインを kill する — Issue #468):

1. `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 の部分中断報告に載せる)
2. `.state/attention_pane.json` を `Read` で開けたら `pane_id` を読む(= sidecar pane_id)。無ければ
   「sidecar 無し」
3. 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 しない
4. 確認済み集合の各ペインを **`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 制御で分岐する**」を参照)
5. sidecar があれば **分類によらず必ず削除**する:
   ```bash
   rm -f .state/attention_pane.json     # Windows native は del .state\attention_pane.json
   ```
6. journal に 1 行追記する。**実際に close したペインがある場合のみ** pane_id を載せ、recycled / gone で
   close を 1 つも行わなかった場合は `reason=stale_sidecar` にする(無関係なペインを停めた誤記録を防ぐ):
   ```bash
   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`](../org-attention-stop/SKILL.md) を参照(本 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`](../org-delegate/references/renga-error-codes.md)。

- **messaging 文脈**(`mcp__org-broker__send_message`。**broker では `[peer_not_found]`** が同じ意味で返る):
  受けても**即「ワーカー閉鎖」と断定しない**。2.0 系では名前解決が送信者タブ内に限定され、また
  宛先台帳が古く id が再割当て / 消滅していると、生存中のペインでもこのコードが返るため、次の順で
  切り分ける:
  1. `mcp__org-broker__list_peers` を取り直す(2.0 系は全タブ列挙なので他タブのワーカーもここで拾える)
  2. **宛先の選び直しは名前で行わない**。全タブ列挙では `name` が一意でなく(契約 T-§2.2)、
     同名レコードが複数返りうるので、名前一致で拾うと**別 org のピアへ SUSPEND / SHUTDOWN を
     送りうる**。次の優先順で 1 件に絞る([`.claude/skills/org-delegate/references/renga-error-codes.md`](../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 確認(次項)へ進む
  3. 絞れた **`peer_id`** で **1 回だけ**再送する(ループにしない — 2 回目以降も結果は同じで、
     本当に閉じていた場合の検知が遅れるだけ)
  4. **`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 に記録して人間に報告する
  4. 消滅が確定したら pending 集合から外す。**worker 状態ファイルの `Status: pane_closed` は窓口が
     書かない** — この terminal transition の書き手はディスパッチャーである
     ([`docs/contracts/state-semantics-contract.md`](../../../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`**
    でのみアドレスする

### 停止手順

1. `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 の**部分中断**
     報告に載せる(相対セレクタへフォールバックしない)
2. **ワーカーを先に停止**: Phase 1 の**宛先選定の真理値表**を `R = {worker}` で適用し、選ばれた
   ワーカーの **`peer_id`** を `to_id` に渡して `mcp__org-broker__send_message` で終了を指示:
   「SHUTDOWN: 作業を終了してください。」
3. **ワーカーペインが閉じたことを確認** — 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 が期待どおりの
     ペインをなお指していることを毎回確かめる):
     1. `mcp__org-broker__list_panes` を実行する
     2. 控えた `pane_id` のレコードが `name == "worker-{task_id}"` かつ `role == "worker"` を**なお
        指している** → その数値 id で close する
     3. レコードはあるが `name` / `role` が一致しない(別ペインへ再割当て済み)→ **close しない**
        (相対セレクタへフォールバックしない)。`pending` に残したまま次の再確認へ送る
     4. レコードが列挙に無い → 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`](../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 に記録して人間に報告する(強制終了は現状未サポート。「停止できなかったワーカーが居る」ことを報告に明記し、全ペイン停止を偽って完了報告しない)

4. **ディスパッチャーを停止**: 真理値表を `R = {dispatcher}` で適用し、選ばれたディスパッチャーの
   **`peer_id`** を `to_id` に渡して `mcp__org-broker__send_message` で終了を指示:
   「SHUTDOWN: 作業を終了してください。」
5. **キュレーターを停止(存在する場合のみ)**: 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 設計のため途中停止でも
   破壊的な中間状態は残らない)
6. ディスパッチャー(および存在した場合のみキュレーター)も (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) と同じく
     残存として人間に報告する

**最後のペイン (窓口) の扱い**: ディスパッチャー(と存在した場合のキュレーター)を閉じた時点で、
窓口ペインが窓口タブに残る最後のペインになる。**窓口は自分自身を `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`](../org-delegate/references/renga-error-codes.md)
のとおり「**唯一のタブの**唯一のペイン」であり、他のタブが存在する状況で窓口タブの最後のペインが
保護されるかは 2.0 系では未確認である(保護されない読みが素直だが、いずれにせよ当てにしない)。
自己 close をしない根拠は上の 2 つだけである。

7. 人間に報告。**`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 件でも残っていれば「組織を中断しました」の文面は使わない**
     (全ペイン停止を偽って報告しないこと自体が本フェーズの目的)

Files in this skill

  • SKILL.md73.7 KB
  • SKILL.md.in69.5 KB

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.