dispatcher-resume

star 0

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).

suisya-systems By suisya-systems schedule Updated 6/6/2026

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

  1. Use mcp__renga-peers__set_summary to set "Dispatcher: monitoring (resumed)".
  2. 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")
  3. Get your own pane_id from list_panes (the id where focused: true).
  4. 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.

  1. Check that .state/dispatcher-handover.md exists:
    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.
      
  2. 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
  3. 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:

  1. 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).
  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):
/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))
Install via CLI
npx skills add https://github.com/suisya-systems/claude-org --skill dispatcher-resume
Repository Details
star Stars 0
call_split Forks 0
navigation Branch main
article Path SKILL.md
More from Creator
suisya-systems
suisya-systems Explore all skills →