| name | dispatcher-resume |
| description | Read the handover file written by /dispatcher-handover and bring the Dispatcher back in a fresh session. Use it on the very first turn after /clear. Atomically update `dispatcher_pane_id` / `dispatcher_peer_id` in state.db, and resume the `/loop 3m` worker monitoring loop. Use when the Secretary instructs "resume the Dispatcher" / "resume" / "pick up from the handover". This is not /org-start (the Worker / Secretary / Curator are assumed to still be alive).
|
| effort | low |
| allowed-tools | ["Read","Bash(py -3 ../tools/journal_append.py:*)","Bash(bash ../tools/journal_append.sh:*)","Bash(python3 -c:*)","Bash(py -3 -c:*)","Bash(ls:*)","Bash(mv:*)","mcp__renga-peers__set_summary","mcp__renga-peers__list_panes","mcp__renga-peers__set_pane_identity","mcp__renga-peers__list_peers","mcp__renga-peers__check_messages","mcp__renga-peers__send_message"] |
dispatcher-resume: bring the Dispatcher back
Read .state/dispatcher-handover.md written by /dispatcher-handover and
restore the Dispatcher's minimum self-awareness (its standing as an org
member, in-flight dispatches, workers under monitoring), then resume the
/loop 3m worker monitoring loop.
Preconditions:
- The Worker / Secretary / Curator panes are still alive from the previous
session. Do not spawn new ones (this is not
/org-start).
- Your own pane (name=
dispatcher) is alive too. You are right after the
Secretary issued /clear → /dispatcher-resume via send_keys. The
pane_id / peer_id should not have changed, but you must observe them
and atomically update state.db.
- The state DB (
.state/state.db) is used as-is.
- The internal state files that bridge the monitoring gap
(
.state/dispatcher-event-cursor.txt /
.state/dispatcher/worker-idle-state.json /
.state/dispatcher/curate-inflight.json (when present) /
.state/pending_decisions.json) survive from the previous session.
Do not re-create or re-initialize (continue from existing values).
- If the handover file does not exist or is too old, point at
/org-start
and stop.
Transport layer (transport) both systems — default renga / opt-in broker: this skill's mcp__renga-peers__* calls are written for default renga (ORG_TRANSPORT unset) and can be followed as-is (default behavior unchanged). Under ORG_TRANSPORT=broker (opt-in, revertible) the MCP server name becomes org-broker, and tools' fully qualified names get machine-substituted from mcp__renga-peers__* → mcp__org-broker__* (argument shape and semantics are identical). Only the transport-dependent points are noted in broker form:
- Receive model (push-primary =
claude/channel / pull fallback): under renga, worker → dispatcher peer messages are pushed in-band. Broker has been redesigned to push-primary (runtime push-first 0.1.24+, transport-lab docs/design/broker-native-roles.md §9): the per-pane channel sidecar (server:org-broker-channel) injects the body into the idle session via notifications/claude/channel. Pull is the fallback layer: when the sidecar is absent / unhealthy / channel-incapable, the Dispatcher actively check_messages (broker: mcp__org-broker__check_messages) each /loop 3m cycle (the per-role dispatcher cadence in the §9.6 reading-substitution table; Step 5's drain of stranded messages from the previous session follows the same logic. A nudge can be a trigger, but it does not wake an idle session, so an active poll is the canonical reception path. The existing "see the nudge → check_messages" prose is not retracted and is read as this fallback cadence — §9.6). The /loop 3m monitoring loop itself resumes even under push-primary — it depends on poll_events (lifecycle cursor; broker: mcp__org-broker__poll_events), and Step 2's check_messages functions as a fallback drain when push fails.
- Spawn rite (folder-trust approval + dev-channel sidecar approval re-introduced): resume does not spawn, so approvals are unused; but on broker, the spawn-time flow (in org-start / org-delegate) machine-approves the
--mcp-config <broker> folder-trust prompt in addition to the dev-channel approval for the --dangerously-load-development-channels server:org-broker-channel channel sidecar (the re-introduced spawn-flow 3-3b) for push-primary (additive over ratified §5/§8.5; design broker-native-roles.md §9.5).
- Error branching (broker additional codes): on top of the renga codes, broker may return
[token_invalid] / [session_invalid] / [tool_not_authorized] / [no_backend] (= adapter_unavailable) / [nudge_failed] / [peer_not_found] / [name_taken] (unknown codes hit the default branch). See the broker section in .claude/skills/org-delegate/references/renga-error-codes.md.
new_tab / focus_pane are absent from the broker surface (intentional exclusion). The canonical contract is docs/contracts/backend-interface-contract.md Surface 8 (ratified 2026-06-14; the push-primary additive amendment S3 is ratified 2026-06-15, with existing ratified text unchanged); the design SoT is transport-lab docs/design/broker-native-roles.md §9 (push-primary redesign) / docs/design/ja-migration-plan.md §5.2(ii) / §8. Broker real-run (dogfood) is scoped to Epic #6 Issue G and is not this skill's default path.
Step 0: confirm your identity
- Use
mcp__renga-peers__set_summary to set "Dispatcher: monitoring (resumed)".
- Use
mcp__renga-peers__list_panes to check the focused pane's name/role:
- Expected:
name == "dispatcher" and role == "dispatcher"
- If mismatched, repair with
mcp__renga-peers__set_pane_identity(target="focused", name="dispatcher", role="dispatcher")
- Get your own
pane_id from list_panes (the id where focused: true).
- Get the
peer_id for name == "dispatcher" from
mcp__renga-peers__list_peers.
Step 1: read the handover file
The Dispatcher's cwd is .dispatcher/, so resolve repo-root paths one
level up.
- Check that
.state/dispatcher-handover.md exists:
ls -la ../.state/dispatcher-handover.md 2>&1
- Look at frontmatter
created_at to judge freshness:
- Within 24h → adopt as-is
- 24h < … ≤ 7d → warn the Secretary ("handover is stale; continuing anyway")
- More than 7d → do not adopt; recommend switching to
/org-start and stop
- Read the body via Read. Treat its contents as "fact" for your next-session
self (Step 3 reconciles against state.db).
Step 2: atomically update Dispatcher identity in state.db
Write the pane_id / peer_id observed in Step 0 through
StateWriter.transaction() in a single transaction (the post-commit
hook will regenerate .state/org-state.md). This is the substance of the
acceptance requirement that "state.db identity is updated atomically".
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>',
)
"
- Write
dispatcher_pane_id / dispatcher_peer_id as strings (the
schema is TEXT and existing /org-start writes strings; keep types aligned)
- Inside
transaction(), even if one half fails the DB will not be left in
a half-written state
- If the observed values differ from the handover frontmatter, treat the
current observation (
list_panes / list_peers) as ground truth and
prefer it. Include the diff in the message you send to the Secretary.
Step 3: re-fetch the current state from state.db and reconcile with the handover
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))
"
Items to check:
- Whether
active_runs[] lines up with the handover's "Workers under monitoring" section
- Whether the worker directories in
active_worker_dirs[] exist
If a worker is active in state.db but not in the handover, or is in the
handover but missing from state.db / list_panes, report to the
Secretary for a decision (do not respawn or change status on your own).
Step 4: pane liveness check
Call mcp__renga-peers__list_peers again and confirm that the worker
names recorded in the handover still exist. If any are gone, notify the
Secretary with WORKER_PANE_EXITED: worker-{task_id} (missing at resume time). Reconciliation is the Secretary's responsibility.
Step 5: resume the monitoring loop
Evaluate the following in this order:
- Inflight regeneration (do this first): if Step 4's
list_peers /
list_panes shows a live pane with name == "curator" but
.state/dispatcher/curate-inflight.json is absent (e.g., the previous
session was cut off right after spawn, before the inflight write), do
not leave the curator untracked: regenerate the inflight record with
started_at = now / reasons: [] / extended: false /
last_inspect_hash: null / last_inspect_ts: null. All subsequent
judgments use this post-regeneration state (= this case always satisfies
the resume condition in 2).
/loop 3m resume condition: if the handover's
active_worker_count > 0, the active worker dirs in state.db are
non-empty, or curate-inflight.json exists (including one
regenerated in 1; completion monitoring of an on-demand curate is part
of the handover — .dispatcher/references/worker-monitoring.md
Step 5.3), resume worker monitoring with the monitoring-only
directive below via /loop 3m (do not omit the prompt). This resume is
not armed by merely declaring "I'll resume monitoring with /loop" — the
declaration expires the moment the turn ends, so you must actually invoke
/loop within this turn (on the Lead side too, the operation is to verify the
invocation trace via inspect_pane and re-ignite via send_keys if it is missing):
/loop 3m Run one cycle of the "monitoring loop" in references/worker-monitoring.md (relative to the Dispatcher cwd .dispatcher/), exactly once (poll_events → check_messages → list_panes → inspect_pane → stall / relay-gap / pane_output evaluation). Notify the secretary only on a cycle where an anomaly / stall / relay-gap / pane_exited is detected; if nothing is detected, output nothing (do not write a per-cycle status summary or natural-language status narration). Keep the cadence at 3 minutes or more and do not shorten it. Do not use a slash command or this skill as the repeat target.
- On the first cycle of the monitoring loop,
mcp__renga-peers__poll_events
resumes from the previous cursor (the moment the previous session ended)
in .state/dispatcher-event-cursor.txt. This preserves the semantics that
"any pane_exited that arrived while the pane was closed is still
guaranteed to be picked up at the next poll" (renga 0.5.7+ cursor spec).
- On the first cycle,
mcp__renga-peers__check_messages drains any
worker → dispatcher peer messages queued during the previous session
(broker = mcp__org-broker__check_messages. Broker has been redesigned
to push-primary, but any messages that piled up during /clear, when the
channel sidecar could not inject them, remain in the queue, so the resume
first-cycle check_messages fallback drain still recovers them without
loss — only the tool name changes; the drain logic is identical. §9.6 /
§9.3 lease-reap).
.state/dispatcher/worker-idle-state.json retains the previous session's
idle_streak_cycles, so stall-detection continuity is preserved as well.
Only if there is nothing to monitor (active worker dirs is 0,
active_runs is 0, no curate-inflight.json, and — since this comes
after evaluating 1 — no curator pane in list_panes either), do not start
/loop; notify the Secretary that you are idle and wait:
DISPATCHER_RESUMED_IDLE: resume complete with no monitoring targets. Awaiting DELEGATE.
Step 6: brief the Secretary
Combine the handover + current state.db and report concisely to the
Secretary:
DISPATCHER_RESUMED: Dispatcher resume complete.
- pane=<observed_pane_id> / peer=<observed_peer_id> (state.db updated)
- workers under monitoring: <task_id list>
- pending decisions: <count>
- diff vs. handover: <one line if any, else "none">
- monitoring loop: /loop 3m resumed (or idle)
Step 7: switch the handover file to consumed state
Once resume succeeds, rename .state/dispatcher-handover.md to
.state/dispatcher-handover.consumed.md. This:
- Prevents the auto-branch in
.dispatcher/CLAUDE.md at startup
(resume if a handover file is present within the last 7 days) from
mistakenly branching to resume on the next /org-start cold-start after
the resume has been consumed once
- Keeps the most recent one for reference in
.consumed.md form (when the
next /dispatcher-handover writes a new .md, the prior one is rotated
to .prev.md backup or overwritten)
mv ../.state/dispatcher-handover.md ../.state/dispatcher-handover.consumed.md
- If
.consumed.md already exists, overwrite is fine (keep only the most
recent one)
.state/dispatcher-handover.prev.md is the backup written by the previous
/dispatcher-handover. Even if it has been read, do not delete it.
Event recording
The Dispatcher cwd is .dispatcher/, so call one level up:
bash ../tools/journal_append.sh dispatcher_resumed \
pane_id=<observed_pane_id> peer_id=<observed_peer_id> \
active_workers=<count> note=resumed_from_handover
What you must not do
- Spawn a new Dispatcher / Curator (they are already alive)
- Send SUSPEND / SHUTDOWN to Workers on your own
- Initialize / delete
.state/dispatcher-event-cursor.txt /
worker-idle-state.json / curate-inflight.json /
pending_decisions.json (monitoring continuity from the previous
session breaks)
- When the handover content disagrees with the current state.db, take it
upon yourself to favor one side (always report to the Secretary for a
decision)
- Split the atomic update across multiple writes (it must always complete
within a single
StateWriter.transaction() block)
- Start Step 5's
/loop 3m with the prompt omitted, or pass
/dispatcher-resume (this skill itself) or any other slash command as the
/loop repeat target (the skill self-recurses every 3 minutes; always pass
an explicit monitoring-only directive — see Step 5's INVARIANT(loop-prompt))