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-resumeviasend_keys. Thepane_id/peer_idshould 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-startand stop.
Transport layer (transport) both systems — default
renga/ opt-inbroker: this skill'smcp__renga-peers__*calls are written for defaultrenga(ORG_TRANSPORTunset) and can be followed as-is (default behavior unchanged). UnderORG_TRANSPORT=broker(opt-in, revertible) the MCP server name becomesorg-broker, and tools' fully qualified names get machine-substituted frommcp__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-labdocs/design/broker-native-roles.md§9): the per-pane channel sidecar (server:org-broker-channel) injects the body into the idle session vianotifications/claude/channel. Pull is the fallback layer: when the sidecar is absent / unhealthy / channel-incapable, the Dispatcher activelycheck_messages(broker:mcp__org-broker__check_messages) each/loop 3mcycle (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 3mmonitoring loop itself resumes even under push-primary — it depends onpoll_events(lifecycle cursor; broker:mcp__org-broker__poll_events), and Step 2'scheck_messagesfunctions 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-channelchannel 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_paneare absent from the broker surface (intentional exclusion). The canonical contract isdocs/contracts/backend-interface-contract.mdSurface 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-labdocs/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_summaryto set "Dispatcher: monitoring (resumed)". - Use
mcp__renga-peers__list_panesto check the focused pane's name/role:- Expected:
name == "dispatcher"androle == "dispatcher" - If mismatched, repair with
mcp__renga-peers__set_pane_identity(target="focused", name="dispatcher", role="dispatcher")
- Expected:
- Get your own
pane_idfromlist_panes(the id wherefocused: true). - Get the
peer_idforname == "dispatcher"frommcp__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.mdexists:ls -la ../.state/dispatcher-handover.md 2>&1- Missing → notify the Secretary and stop:
DISPATCHER_RESUME_FAILED: handover file not found. Cold-start the Dispatcher with /org-start.
- Missing → notify the Secretary and stop:
- Look at frontmatter
created_atto 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-startand 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_idas strings (the schema is TEXT and existing/org-startwrites 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_panesshows a live pane withname == "curator"but.state/dispatcher/curate-inflight.jsonis 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 withstarted_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 3mresume condition: if the handover'sactive_worker_count > 0, the active worker dirs in state.db are non-empty, orcurate-inflight.jsonexists (including one regenerated in 1; completion monitoring of an on-demand curate is part of the handover —.dispatcher/references/worker-monitoring.mdStep 5.3), resume worker monitoring with the monitoring-only directive below via/loop 3m(do not omit the prompt):
/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_eventsresumes from the previous cursor (the moment the previous session ended) in.state/dispatcher-event-cursor.txt. This preserves the semantics that "anypane_exitedthat 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_messagesdrains 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-cyclecheck_messagesfallback 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.jsonretains the previous session'sidle_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.mdat startup (resume if a handover file is present within the last 7 days) from mistakenly branching to resume on the next/org-startcold-start after the resume has been consumed once - Keeps the most recent one for reference in
.consumed.mdform (when the next/dispatcher-handoverwrites a new.md, the prior one is rotated to.prev.mdbackup or overwritten)
mv ../.state/dispatcher-handover.md ../.state/dispatcher-handover.consumed.md
- If
.consumed.mdalready exists, overwrite is fine (keep only the most recent one) .state/dispatcher-handover.prev.mdis 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 3mwith the prompt omitted, or pass/dispatcher-resume(this skill itself) or any other slash command as the/looprepeat target (the skill self-recurses every 3 minutes; always pass an explicit monitoring-only directive — see Step 5's INVARIANT(loop-prompt))