Skip to content
Back to skills

Dispatcher Resume

ASecurity

/dispatcher-handover で書き出した handover ファイルを読み込み、 ディスパッチャーを新しい session で復帰させる。/clear 直後の最初のターンで使う。 state.db の dispatcher_pane_id / dispatcher_peer_id を atomic に更新し、 worker monitoring の /loop 3m を再開する。 「ディスパッチャー復帰」「resume」「引き継ぎから再開」と secretary から指示された ときに使う。/org-start ではない(ワーカー・窓口・キュレーターは生きている前提)。

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

Works with

  • claude code
  • cursor
  • 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 dispatcher-resume --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Dispatcher Resume?

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

Security grade badge for Dispatcher Resume
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/suisya-systems-dispatcher-resume/badge)](https://www.skillsdirectory.com/skills/suisya-systems-dispatcher-resume)

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: dispatcher-resume
owner: dispatcher
description: >
  /dispatcher-handover で書き出した handover ファイルを読み込み、
  ディスパッチャーを新しい session で復帰させる。/clear 直後の最初のターンで使う。
  state.db の dispatcher_pane_id / dispatcher_peer_id を atomic に更新し、
  worker monitoring の /loop 3m を再開する。
  「ディスパッチャー復帰」「resume」「引き継ぎから再開」と secretary から指示された
  ときに使う。/org-start ではない(ワーカー・窓口・キュレーターは生きている前提)。
effort: low
allowed-tools:
  - Read
  - Bash(echo:*)
  - Bash(py -3 ../tools/journal_append.py:*)
  - Bash(bash ../tools/journal_append.sh:*)
  - Bash(py -3 ../tools/capability_gate.py:*)
  - Bash(python3 ../tools/capability_gate.py:*)
  - Bash(python3 -c:*)
  - Bash(py -3 -c:*)
  - Bash(ls:*)
  - Bash(mv:*)
  - mcp__org-broker__set_summary
  - mcp__org-broker__list_panes
  - mcp__org-broker__set_pane_identity
  - mcp__org-broker__list_peers
  - mcp__org-broker__check_messages
  - mcp__org-broker__send_message
---

# dispatcher-resume: ディスパッチャーの復帰

`/dispatcher-handover` で書き出した `.state/dispatcher-handover.md` を読み込み、
ディスパッチャーとして最低限の自覚(組織員としての立ち位置・進行中の派遣・監視
対象ワーカー)を復元し、`/loop 3m` の worker monitoring を再開する。

> **前提**:
> - ワーカー / 窓口 / キュレーターのペインは前 session から生きたまま残っている。
>   新たに spawn しない(/org-start ではない)。
> - 自ペイン(name=`dispatcher`)も生きている。secretary が `send_keys` で
>   `/clear` → `/dispatcher-resume` を打鍵した直後の状態。pane_id / peer_id は
>   変わっていないはずだが、必ず観測して state.db を **atomic 更新する**。
> - state DB (`.state/state.db`) はそのまま使う。
> - 監視 gap を埋める内部状態ファイル(`.state/dispatcher-event-cursor.txt`(backend
>   session に束縛された poll_events cursor) /
>   `.state/dispatcher/worker-idle-state.json` / `.state/dispatcher/curate-inflight.json`(存在時) /
>   `.state/pending_decisions.json`)は
>   前 session から残っている。新規作成・初期化しない(既存値からそのまま継続)。
> - handover ファイルが存在しないか古すぎる場合は、`/org-start` を案内して停止する。

> **輸送層(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)。)

## Step 0 の前: 残存 loop 予約 / stale 再発火ガード(重複起動の早期遮断)

`/dispatcher-resume` は **handover を 1 回だけ消費する one-shot** である(Step 7 で
`.state/dispatcher-handover.md` を `.consumed.md` に rename する)。だが Step 5 で起動する
`/loop 3m` の予約が、過去のバグや手動操作で `/dispatcher-resume` 自身を反復対象に握って
いると、監視サイクルのたびに本 skill が再発火する。この **stale 再発火 / 重複起動** を
Step 0 に入る前に遮断する(恒久対策の経緯は
[`knowledge/raw/2026-06-19-dispatcher-resume-loop-recursion.md`](../../../knowledge/raw/2026-06-19-dispatcher-resume-loop-recursion.md))。

1. live / consumed の handover 状態を **存在ベース**で判定する(`now` との時刻比較に
   依存しない。cold-start vs resume の分岐が live `.md` のみを対象とする
   `.dispatcher/CLAUDE.md` の規約と揃える):
   ```bash
   python3 -c "
   import os
   md = '../.state/dispatcher-handover.md'
   cm = '../.state/dispatcher-handover.consumed.md'
   if os.path.exists(md):
       print('resume')            # 正規の resume: live handover あり
   elif os.path.exists(cm):
       print('already_consumed')  # 既に消費済み: 残存 loop / 重複起動の疑い
   else:
       print('no_handover')       # handover が一度も無い: 真の cold-start 候補
   "
   ```
2. **`already_consumed`(live `.md` なし・`.consumed.md` あり)**: live handover が無い
   resume 起動だが `.consumed.md` が残っている。これは「(i) 直前に成功した resume の残存
   loop 予約による再発火 / 重複起動(組織は健全・監視ループ稼働中)」か「(ii) 組織が止まって
   いて本来 cold-start が要る状況での誤起動」のいずれか。**存在判定だけで no-op に倒すと (ii)
   の cold-start 案内を恒久的に隠す**ので、監視対象が実際に live かを観測して分岐する:
   - `mcp__org-broker__list_panes` / `mcp__org-broker__list_peers` で **監視対象が live か**を確認する: active な
     worker ペイン(`role == "worker"`)が 1 つ以上ある、または
     `.state/dispatcher/curate-inflight.json` が存在する(= 監視ループが回っているべき状態)。
     **`list_peers` の直前に
     [`.claude/skills/org-delegate/references/capability-first-drive-operational-gate.md`](../org-delegate/references/capability-first-drive-operational-gate.md)
     を Read し、`monitoring-read-only` の分岐を適用する**(同 reference §6 の表 #5)。capability 形
     かつ未承認なら列挙を live 判定に使わず破棄し、`list_panes` の `role == "worker"` と
     `curate-inflight.json` の有無だけで分岐する(停止しない・待ち時間 0 分)。
     **承認済み(同 reference §2 の `first_drive` が `recorded`)のときは §1-2 の三値判定を通し、
     「在」と出たワーカーだけを live として数える** — 他タブの別 org のワーカーを live と誤認すると
     (ii) の cold-start 経路が抑止される。**「unknown」は live に数えないが、(ii) にも倒さない**:
     判定不能を「監視対象なし」と読み替えると誤った `/org-start` 案内で健全な組織を壊しうる
     (不可逆側)。**終端処理は (i) と同じ**(早期 exit + Step 5 の monitoring ディレクティブで
     `/loop` を再 arm し、残存予約を空のまま放置しない)とし、secretary には
     `DISPATCHER_RESUME_INDETERMINATE: 監視対象の同タブ性を確定できず(unknown N 件)。
     live 判定を保留して監視ループのみ再開しました。` を送って判断を仰ぐ。
     **unknown のワーカーは監視対象・active のまま残す**(`.state/workers/worker-*.md` を
     `Status: pane_closed` にせず `WORKER_PANE_EXITED` も送らず
     `.state/dispatcher/worker-idle-state.json` の record も消さない)。再評価は再 arm した
     `/loop 3m` の通常サイクルが行う。
     **どの分岐にも落ちないまま skill を終えない**(一度きりの skill が loop に再発火され続ける)。
   - **(i) 監視対象が live = stale 再発火 / 重複起動**: 組織は健全で cold-start 不要。
     `DISPATCHER_RESUME_FAILED` を **送らず**(誤った `/org-start` 案内を出さない)、
     Step 1 以降に進まず早期 exit する。**残存予約を空のまま放置しない**: 監視 `/loop` が
     `/dispatcher-resume` を反復対象に握っている兆候があれば、Step 5 の monitoring 専用
     ディレクティブ(INVARIANT(loop-prompt) 準拠の `/loop 3m ...`)で loop を明示的に再 arm
     して予約を上書きする(重複起動はしない=単一 loop に収束)。既に monitoring ディレクティブ
     で回っていれば当該サイクルは **no-op heartbeat**(何も出力しない)。どちらの分岐でも予約を
     skill 自身に握らせたまま放置しない。再発火の事実だけ journal に残す:
     ```bash
     bash ../tools/journal_append.sh anomaly_observed \
         source=dispatcher_resume worker=dispatcher kind=stale_loop_refire confidence=n/a \
         note=handover_already_consumed
     ```
   - **(ii) 監視対象が無い = 本来 cold-start が要る誤起動**: stale 再発火ではない。no-op に
     倒さず **Step 1 の「handover ファイルなし」分岐へフォールスルーし `DISPATCHER_RESUME_FAILED`
     → cold-start 案内を出す**(cold-start 案内を隠さない)。
3. **`resume`(live `.md` あり)**: 通常どおり Step 0 へ進む。
4. **`no_handover`(live `.md` も `.consumed.md` も無し)**: 通常どおり Step 0 → Step 1 へ
   進み、Step 1 の「handover ファイルなし」分岐で `DISPATCHER_RESUME_FAILED` → cold-start
   案内に落ちる。

## Step 0: 自分の identity を確認する

1. `mcp__org-broker__set_summary` で「Dispatcher: 監視(resumed)」をセット
2. **caller pane id を確定する**(`focused: true` は「そのタブで今フォーカスされているペイン」で
   あって caller 自身ではない。人間がフォーカスを移した瞬間、別ペインを `dispatcher` に改名し、
   その id を `dispatcher_pane_id` として DB に焼き付けてしまう): 環境変数 `RENGA_PANE_ID`
   (`echo "${RENGA_PANE_ID:-unset}"`)を起点に、`mcp__org-broker__list_panes` へ**同一 id のレコードが
   在ることを照合**する。ここで確定した id が Step 2 で書く `dispatcher_pane_id` であり、以後の
   routing はこれを掴む。規則の正本は
   [`docs/contracts/backend-interface-contract.md`](../../../docs/contracts/backend-interface-contract.md)
   T-§4.2 の caller pane id 取得規則
   - **broker(`ORG_TRANSPORT=broker`)では `RENGA_PANE_ID` を読まず、最初から `caller-id 未確定` に
     倒す**: broker には `RENGA_PANE_ID` に相当する caller pane id を out of band で供給する surface が
     無いので、この取得規則は broker では常に未確定に落ちる。`list_panes` に数値 id のエントリが在って
     手元で掴めていても例外にならない(数値 id は MUST の**片方**=相対セレクタでないことしか満たさず、
     **もう片方**=自タブと確立済みの列挙から採った id でありその対象が caller であること、を満たさない)。
     したがって broker では下の `caller-id 未確定` 分岐へそのまま進み、`dispatcher_pane_id` を渡さない
3. 照合できたレコードの name/role を確認:
   - 期待値: `name == "dispatcher"` かつ `role == "dispatcher"`
   - 不一致なら `mcp__org-broker__set_pane_identity(target="<RENGA_PANE_ID の値>", name="dispatcher", role="dispatcher")` で修復(相対セレクタでは撃たない)
   - **`caller-id 未確定`**(`RENGA_PANE_ID` 未設定 / `list_panes` に同一 id 無し / broker 等で同等の
     caller-id を入手できない): 自己修復を実行せず、**`dispatcher_pane_id` も渡さない**まま
     (Step 2 の `update_session()` は未指定を「保持」と解釈するので既存値が温存され、これ自体が
     fail-closed になる)Step 1 へ進み、identity 未確認であることを Step 6 のブリーフィングで
     secretary に明示して続行判断を仰ぐ(`target="focused"` や裸 `name` へフォールバックしない)
4. `mcp__org-broker__list_peers` で `name == "dispatcher"` の `peer_id` を取得。
   **呼び出しの直前に
   [`.claude/skills/org-delegate/references/capability-first-drive-operational-gate.md`](../org-delegate/references/capability-first-drive-operational-gate.md)
   を Read し、`monitoring-read-only` の分岐を適用する**(同 reference §6 の表 #4)。capability 形
   かつ未承認なら列挙から `peer_id` を採らず破棄し、2 で確定した caller pane id で identity を
   確定して進む(停止しない・待ち時間 0 分)。
   **このとき Step 2 の DB 書き込みでは `dispatcher_peer_id` を渡さない**(`update_session()` は
   None / 未指定を「保持」と解釈する契約なので、**既存値がそのまま残る**)。placeholder や
   観測できなかった値を書くと、以後の routing が壊れた id を掴む。`dispatcher_pane_id` は
   2 で確定した caller pane id をそのまま書く(`caller-id 未確定` のときは渡さない)。Step 6 のブリーフィングでは
   `peer=<未取得: capability gate 縮退中>` と明示して secretary に伝える。

   **capability 形かつ承認済み(同 reference §2 の `first_drive` が `recorded`)のときも、自分の
   `peer_id` は名前引きで確定しない**(§1-2-d)。`mcp__org-broker__list_peers` は caller を除外するので、
   `name == "dispatcher"` に一致したレコードは**同タブに 1 件だけ在っても自分ではありえず**、
   他タブのみ / 同タブ性 unknown / `same_tab=True` が複数の各分岐と合わせて、**名前引きの結果が
   何であれ `peer_id` は未確定のまま**先へ進む。**したがって Step 2 の DB 書き込みでは
   `dispatcher_peer_id` を渡さない**(`update_session()` の「未指定 = 保持」で既存値を温存する。
   生きた同一ペインを引き継ぐ resume では保持が正しく、渡さないこと自体が fail-closed になる)。
   更新するのは 2 で確定した caller pane id(`dispatcher_pane_id`)だけである。Step 6 の
   ブリーフィングでは `peer=<未取得: 名前引きでは自 peer を同定できない>` と明示して secretary に伝える。

## Step 1: handover ファイルを読み込む

ディスパッチャーの cwd は `.dispatcher/` なので、リポジトリ root のパスを 1 階層上に解決する。

1. `.state/dispatcher-handover.md` が存在するか確認:
   ```bash
   ls -la ../.state/dispatcher-handover.md 2>&1
   ```
   - 存在しない → secretary に通知して停止:
     ```
     DISPATCHER_RESUME_FAILED: handover ファイルがありません。
     /org-start でディスパッチャーを cold start してください。
     ```
2. フロントマター `created_at` を見て鮮度を判定する。`created_at` は決定的 UTC で書かれている
   ([`/dispatcher-handover`](../dispatcher-handover/SKILL.md)) ので、**比較用の `now` も決定的 UTC で取得する**
   (`date -u +%Y-%m-%dT%H:%M:%SZ`、PowerShell 環境は
   `(Get-Date).ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ")`)。local(JST)時刻で `now` を
   取ると `now - created_at` が約 9 時間ぶれ、24h / 7d 境界付近で鮮度を誤判定するため:
   - 24 時間以内 → そのまま採用
   - 24 時間超〜7 日以内 → secretary に警告(「handover が古い、続行する旨」)
   - 7 日超 → 採用せず `/org-start` への切り替えを推奨し停止
3. ファイル本文を Read で取り込む。書かれている内容は次 session の自分にとっての
   「事実」として扱う(後の Step 3 で state.db と照合する)。

## Step 2: state.db の dispatcher identity を atomic 更新する

Step 0 で観測した `pane_id` / `peer_id` を `StateWriter.transaction()` 経由で
**1 トランザクションで** 書く(post-commit hook が `.state/org-state.md` を再生成
してくれる)。これが acceptance の「state.db identity が atomic 更新」要件の本体。

```bash
python3 -c "
import sys, os
sys.path.insert(0, os.path.abspath('..'))
from pathlib import Path
from tools.state_db import connect
from tools.state_db.writer import StateWriter
conn = connect('../.state/state.db')
with StateWriter(conn, claude_org_root=Path('..')).transaction() as w:
    w.update_session(
        dispatcher_pane_id='<observed_pane_id>',
        dispatcher_peer_id='<observed_peer_id>',
    )
"
```

**Step 0 で `peer_id` が未確定だった場合(capability gate の縮退中、または承認済み路で名前引きから
自 peer を同定できなかった場合)は、`dispatcher_peer_id` を渡さない形で呼ぶ**。上の例をそのまま
使うと観測していない値を書き込むことになる:

```bash
python3 -c "
import sys, os
sys.path.insert(0, os.path.abspath('..'))
from pathlib import Path
from tools.state_db import connect
from tools.state_db.writer import StateWriter
conn = connect('../.state/state.db')
with StateWriter(conn, claude_org_root=Path('..')).transaction() as w:
    w.update_session(
        dispatcher_pane_id='<observed_pane_id>',
    )
"
```

**Step 0 が `caller-id 未確定` に落ちていた場合は、同じ理由で `dispatcher_pane_id` も渡さない**
(両方とも未確定で `update_session()` に渡す引数が残らないなら、この Step 2 自体を skip する)。
`focused` から採った id で埋めてはならない — 人間のフォーカス位置に依存した別ペインの id を、
以後の routing の宛先として恒久的に焼き付けることになる。

`update_session()` は未指定を「保持」と解釈する契約なので、既存値がそのまま残る(生きた同一ペインを
引き継ぐ resume ではこれが正しい。詳細は Step 0 手順 3 / 手順 4 と共有 reference §1-2-d)。

- `dispatcher_pane_id` / `dispatcher_peer_id` は **文字列**で書く(schema は TEXT、
  既存 `/org-start` も文字列を入れているので型を揃える)
- `transaction()` 内なので片方だけ書いて失敗しても DB は半端な状態にならない
- 観測値が handover frontmatter と異なっても、現観測(list_panes / list_peers)を
  真値として優先する。差分があった旨を secretary 宛 message に含める

## Step 3: state.db で現状を再取得し handover と照合する

```bash
python3 -c "
import sys, os
sys.path.insert(0, os.path.abspath('..'))
from tools.state_db import connect
from tools.state_db.queries import get_org_state_summary
import json
conn = connect('../.state/state.db')
print(json.dumps(get_org_state_summary(conn), ensure_ascii=False, indent=2, default=str))
"
```

確認項目:
- `active_runs[]` が handover の「監視対象のワーカー」セクションと整合するか
- `active_worker_dirs[]` のワーカーディレクトリ存在

handover には書かれていないが state.db に `active` で残っている worker、または
handover には書かれているが state.db / list_panes で消えている worker があれば、
secretary に **報告して** 判断を仰ぐ(勝手に再 spawn / status 変更しない)。

## Step 4: ペイン生存確認

`mcp__org-broker__list_peers` を再度呼び、handover に記載のワーカー名が現存するか
確認する。消えていれば secretary に `WORKER_PANE_EXITED: worker-{task_id} (resume
時点で不存在)` を通知(reconcile は窓口の責務)。

**`list_peers` の直前に
[`.claude/skills/org-delegate/references/capability-first-drive-operational-gate.md`](../org-delegate/references/capability-first-drive-operational-gate.md)
を Read し、`monitoring-read-only` の分岐を適用する**(同 reference §6 の表 #6)。capability 形かつ
未承認なら列挙を不存在の根拠にせず破棄し、`mcp__org-broker__list_panes` と events テーブルの報告痕跡で判定する。
**確定できなければ `WORKER_PANE_EXITED` を送らない**(縮退中の「列挙に居ない」は死亡の証拠ではなく、
別 org の同名ピアと取り違えうる。同 reference §3-B-1)。停止しない・待ち時間 0 分。

**capability 形かつ承認済み(同 reference §2 の `first_drive` が `recorded`)のときは、§1-2 の三値判定を
handover 記載の各ワーカー名に適用する。「在」は生存として扱う。「不在」だけでは `WORKER_PANE_EXITED` を
送らず、送信可否は §1-2-c の pane 面裏取りが取れたかに従う。「unknown」でも送らず、indeterminate として
secretary に報告する。**(判定条件・三値の定義・評価順の正本は §1-2。ここに重ねて書かない)
**unknown / indeterminate のワーカーは監視対象・active のまま残し**、Step 5 で再開する `/loop 3m` の
通常サイクルが再評価する。

## Step 5: 監視ループの再開

以下を**この順序で**評価する:

1. **inflight 再生成(先に実行)**: Step 4 の `list_peers` / `list_panes` に
   `name == "curator"` のペインが生きているのに `.state/dispatcher/curate-inflight.json`
   が無い場合(前 session が spawn 直後の inflight 書き込み前に途切れた等)、untracked
   curator を放置しないよう `started_at = <決定的 UTC>` / `pane_id = <下記の pane id(backend が返した型のまま)>` /
   `reasons: []` / `extended: false` / `curate_result: null` / `last_inspect_hash: null` /
   `last_inspect_ts: null` で
   inflight を**再生成する**(フィールドの正本は
   [`.dispatcher/references/pane-close.md` 5-3](../../../.dispatcher/references/pane-close.md) の
   JSON。`trigger_task_id` は inflight を失った前 session の CLOSE_PANE 対象を指すフィールドで
   resume 側に値の出所が無いため書かない — Step 5.3 の完了受領 / timeout 判定はこの
   フィールドを読まない。`curate_result` は受領した `CURATE_*` を次サイクルへ持ち越す
   フィールドで、resume 時点では何も受領していないので**初期値 `null` を明示して書く** —
   Step 5.3 (a) が close できないまま inflight を保持するときだけ `"done"` / `"skipped"` /
   `"error"` に更新される)。
   `started_at` は **`date -u +%Y-%m-%dT%H:%M:%SZ`**(PowerShell 環境は
   `(Get-Date).ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ")`)の出力をそのまま埋め、
   **手書きの local(JST)時刻を `Z` で書かない**(再生成 started_at が未来時刻になると Step 5.3 の
   `now - started_at` が負値となり curator が永久に孤立する。決定的取得の正本は同 5-3)。
   `pane_id` は**生存確認に使った `mcp__org-broker__list_panes` の列挙で `name == "curator"` かつ
   `role == "curator"` を指しているレコードの `id`** を、**`mcp__org-broker__list_panes` が返した JSON の型を
   変えずに**書く(列挙が手元にあるのでその場で採れる)。tmux backend は `"%3"` のような**文字列**、
   WezTerm backend は**整数**を返すので、**引用符の有無を型に合わせる** — 文字列を引用符無しで
   書くと JSON が構文非妥当になり、Step 5.3 も次の resume もこのファイルを読めず curator の
   追跡が失われる(正本は同 5-3 の注記)。**`pane_id` を欠いた形で再生成しない** — Step 5.3 (a)/(b) の
   curator close はこの `pane_id` を identity 照合の起点にするので、欠けると schema 追加前の
   旧形式ファイルと同じ扱いになり、close が列挙引き直しの fallback 経路に落ちる
   ([`.dispatcher/references/worker-monitoring.md` Step 5.3](../../../.dispatcher/references/worker-monitoring.md))。
   `list_peers` にのみ `curator` 名があり `mcp__org-broker__list_panes` の列挙に該当レコードが無い場合は
   **再生成せず** secretary に informational として報告する(数値 pane id を採れないうえ、
   messaging 面の名前一致は同タブの pane 生存の根拠にならない。Step 4 と同じ規律で、
   契約 T-§4.2 が pane 制御可能性の推論を禁じている)。
   以降の判定はこの再生成後の状態で行う(= このケースは必ず 2 の再開条件を満たす)
2. **`/loop 3m` 再開条件**: handover の `active_worker_count > 0`、state.db の
   active worker dirs が非空、**または `curate-inflight.json` が存在する**
   (1 の再生成分を含む。オンデマンド curate の完了監視が引き継ぎ対象。
   `.dispatcher/references/worker-monitoring.md` Step 5.3)のいずれかを満たせば
   下記の monitoring 専用ディレクティブを渡して `/loop 3m` で worker monitoring を
   再開する(**prompt を省略しない**)。この再開は「/loop で監視を再開します」と
   **宣言するだけでは武装されない** — 宣言はターン終了と同時に失効するため、必ず
   本ターン内で `/loop` を実 invoke すること(窓口側も `inspect_pane` で実行痕跡を
   裏取りし、無ければ `send_keys` で再点火する運用である):

<!--
INVARIANT(loop-prompt): `/loop` の prompt 引数に skill 自身(`/dispatcher-resume`)や
他のスラッシュコマンドを渡さない。skill 実行ターン内で prompt を省略して `/loop` を
起動すると、反復対象としてアクティブな slash command が捕捉され、本 skill が 3 分ごとに
自己再帰する(2026-06-19 incident。詳細は
knowledge/raw/2026-06-19-dispatcher-resume-loop-recursion.md)。必ず monitoring 専用の
自然文ディレクティブを明示的に渡すこと。tests/test_dispatcher_resume_loop_invariant.py が
この不変条件を pin する。
-->

```
/loop 3m references/worker-monitoring.md(ディスパッチャー cwd .dispatcher/ 基準)の「監視ループ 1 サイクル」を 1 回だけ実行する(poll_events → check_messages → list_panes → inspect_pane → stall / relay-gap / pane_output 評価)。anomaly / stall / relay-gap / pane_exited を検出したサイクルのみ secretary へ通知し、検出が無ければ何も出力しない(毎サイクルの状況サマリや自然言語の状況描写を書かない)。cadence は 3 分以上を保ち短縮しない。スラッシュコマンドや本 skill を反復対象にしない。
```

- 監視ループの 1 サイクル目で `mcp__org-broker__poll_events` は
  `.state/dispatcher-event-cursor.txt` の前 cursor(前 session 終了時点)から resume
  する。これで「pane が閉じている間に来た pane_exited は次回 poll で必ず拾える」
  semantics が維持される(cursor 仕様)。**ただし resume するのは cursor に記録された
  `session_key` が現 backend session と一致したときだけ**で、一致しなければ 1 サイクル目が
  cursor を破棄して「今以降」から張り直し、窓口へ `EVENT_CURSOR_RESET` を 1 行報告する
  (`/clear` を挟む間に renga / broker daemon が再起動していた場合がこれ。判定と手当ての
  正本は [`.dispatcher/references/worker-monitoring.md`](../../../.dispatcher/references/worker-monitoring.md)
  「監視ループ 1 サイクル」Step 1)。**本 skill は cursor ファイルを読まず・書かず・消さない**
  — 束縛の照合は監視ループ側の責務で、resume 側で先回りして初期化してはならない
- `mcp__org-broker__check_messages` の 1 サイクル目で前 session 中にキューに溜まった
  worker → dispatcher peer message を drain する(broker = `mcp__org-broker__check_messages`。
  broker は push 一次に再設計済だが、`/clear` 中に channel sidecar 経由で注入できなかった
  滞留分はキューに残るため、resume 1 サイクル目の `check_messages` フォールバック drain で
  取りこぼさず回収する=ツール名のみ変わり drain 論理は同型。§9.6 / §9.3 lease-reap)
- `.state/dispatcher/worker-idle-state.json` は前 session の `idle_streak_cycles` を
  保持しているので stall 検出の連続性も維持される

監視対象が 0 件(active worker dir も 0、active_runs も 0、`curate-inflight.json` も
無し、かつ 1 の評価後なので `list_panes` に curator ペインも不在)の場合のみ `/loop` を
始動せず、idle 状態を secretary に通知して待機する:

```
DISPATCHER_RESUMED_IDLE: 監視対象なしで resume 完了。DELEGATE 待機。
```

## Step 6: secretary にブリーフィングを返す

handover + state.db の現状を統合した上で、secretary に簡潔に報告:

```
DISPATCHER_RESUMED: ディスパッチャー復帰完了。
- pane=<observed_pane_id> / peer=<observed_peer_id> (state.db 更新済)
- 監視対象ワーカー: <task_id list>
- pending decisions: <count>
- handover との差分: <あれば 1 行、無ければ「なし」>
- 監視ループ: /loop 3m 再開 (or idle)
```

- Step 0 が `caller-id 未確定` に落ちていた場合は `pane=<未確定: caller pane id を同定できない>` と
  書き、`(state.db 更新済)` は付けない(前値保持のままであることを secretary に伝える)

## Step 7: handover ファイルを consumed 状態に切り替える

resume が成功したら、`.state/dispatcher-handover.md` を `.state/dispatcher-handover.consumed.md` に **rename する**。これにより:

- `.dispatcher/CLAUDE.md` 起動時の自動分岐(handover ファイルが直近 7 日以内に存在すれば resume)が「次の `/org-start` cold-start 時にも誤って resume に分岐する」事故を防ぐ
- 直近 1 件のみ参照用に `.consumed.md` 形式で残る(次の `/dispatcher-handover` が新たに `.md` を書いた時点で `.prev.md` バックアップに置き換わる、または上書きされる)

```bash
mv ../.state/dispatcher-handover.md ../.state/dispatcher-handover.consumed.md
```

- 既に `.consumed.md` があれば上書きで構わない(直近 1 件のみ保持)
- `.state/dispatcher-handover.prev.md` は前回の `/dispatcher-handover` で書かれたバックアップ。読み込み済みでも消さない

## イベント記録

ディスパッチャーの cwd は `.dispatcher/` なので 1 階層上を呼ぶ:

```bash
bash ../tools/journal_append.sh dispatcher_resumed \
    pane_id=<observed_pane_id> peer_id=<observed_peer_id> \
    active_workers=<count> note=resumed_from_handover
```

## やってはいけないこと

- 新規にディスパッチャー / キュレーターを spawn する(既に生きている)
- ワーカーに勝手に SUSPEND / SHUTDOWN を送る
- `.state/dispatcher-event-cursor.txt` / `worker-idle-state.json` /
  `curate-inflight.json` / `pending_decisions.json` を初期化 / 削除する(前 session からの監視連続性が壊れる)
- handover の内容と state.db の現状が食い違うときに、勝手にどちらかへ寄せる
  (必ず secretary に報告して判断を仰ぐ)
- atomic 更新を分割して書く(必ず `StateWriter.transaction()` 1 ブロックで完結させる)
- Step 5 の `/loop 3m` を prompt 省略で起動する / `/dispatcher-resume`(本 skill 自身)や
  他のスラッシュコマンドを `/loop` の反復対象に渡す(skill が 3 分ごとに自己再帰する。
  必ず monitoring 専用ディレクティブを明示的に渡す。Step 5 の INVARIANT(loop-prompt) 参照)

Files in this skill

  • SKILL.md33.8 KB
  • SKILL.md.in30.3 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.