Skip to content
Development
Skill

/dispatcher

Use when you want the orchestrator to pick the next repo to work on across your whole portfolio — it enumerates candidate repos below the confinement root, resolves free/busy from each repo's session.lock lease, ranks the FREE ones by backlog priority × staleness × readiness,

From plugin
session-orchestrator
5144 skills14 agents26 commands10 hooks
+1
Install
$ npx -y skills add Kanevry/session-orchestrator --skill dispatcher --agent claude-code

How it fires

How this skill gets triggered: by you, by Claude, or both.

  • Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
  • You can call itInvoke it directly when you want it.
  • Slash command/dispatcher

Context preview

The summary Claude sees to decide when to auto-load this skill.

Use when you want the orchestrator to pick the next repo to work on across your whole portfolio — it enumerates candidate repos below the confinement root, resolves free/busy from each repo's session.lock lease, ranks the FREE ones by backlog priority × staleness × readiness,

SKILL.md

dispatcher.SKILL.md
name: dispatcher
description: >
  Use when you want the orchestrator to pick the next repo to work on across your whole portfolio — it
  enumerates candidate repos below the confinement root, resolves free/busy from each repo's session.lock
  lease, ranks the FREE ones by backlog priority × staleness × readiness, recommends the single most
  worthwhile one via AskUserQuestion, atomically claims it, and routes you to the chosen entry command.
  Triggers: "what should I work on next", "dispatch me to a repo", "pick the next project", "run
  /dispatcher". <example>Context: operator finished a session and wants the next-best repo across the
  portfolio. user: "/dispatcher" assistant: "Ranked 18 free repos — top recommendation: Pencil-Designs
  (score 4.50, 90d stale). Confirm via the picker, I'll claim its lease atomically, then route you to
  /session deep."</example>
model: sonnet

Dispatcher Skill

> Cross-repo autopilot front-door — enumerate → rank → owner-AUQ → atomic claim → route. Read-only until the operator confirms; the only mutating step is the atomic `session.lock` claim, and it happens BEFORE any launch.

Soul

The dispatcher answers one question: *"of all my repos, which is the most worthwhile to work on right now, and is it free?"* It scans the confinement-root children, resolves each repo's free/busy status from its `session.lock` v2 lease (same lease semantics as the vault-status board), ranks only the FREE ones by `priority × staleness × readiness`, and recommends the single best one. You confirm via a picker, it claims the lease atomically (winning the race or excluding-and-re-ranking on a loss), then routes you to the entry command for that repo. Busy repos are listed-as-such, never selected.

When to use

  • You just finished a session and want the orchestrator to pick the next-best repo across your whole portfolio.
  • You want a ranked, free/busy-aware view of every candidate repo before committing to one.
  • You want the atomic claim handled for you so two parallel sessions never both grab the same repo.

When NOT to use

  • Single-repo work where you already know the target — just run `/session`, `/plan`, or `/discovery` directly in that repo.
  • A cross-repo *read-only health dashboard* (open issues/MRs/CI per repo) — that is `/portfolio` (gitlab-portfolio), not the dispatcher.
  • Writing issues/MRs back to GitLab/GitHub — use `gitlab-ops`.
  • Inside a subagent — the dispatcher is coordinator-only because Phase 2 uses `AskUserQuestion` (unavailable in dispatched agents; see `.claude/rules/ask-via-tool.md` AUQ-004).

Phase 1: Enumerate + Rank

Run the read path (non-mutating). Either invoke the CLI directly or call `runDispatch` from the module:

node scripts/lib/dispatcher/cli.mjs --json

The JSON object has keys `{ candidates, free, ranked, warnings, recommended }`:

  • `candidates` — every repo found below the confinement root (busy ones LISTED, not dropped).
  • `free` — the subset with no live lease (`free === true`).
  • `ranked` — the free candidates sorted DESC by score; `ranked[0]` is the recommendation.
  • `recommended` — `ranked[0]` or `null` (no free candidates).
  • `warnings` — human-readable degradation notes (glab/gh missing, host probe failed). **Surface every warning to the operator** — they explain why a repo was ranked on partial signals.

Ranking combines three signals per repo (implementation: `scripts/lib/dispatcher/rank.mjs`): backlog **priority** (critical/high counts), **staleness** (days since the last completed session — older = more worthwhile, capped at 90d), and **readiness** (CI status × host resource verdict — only ever dampens). A null priority (glab/gh missing) is ranked on staleness × readiness alone with a warning; the dispatcher NEVER blocks on a missing CLI.

Phase 1.5: Verdict gate (autonomy-gated launch) — #682

> Decides ONE thing for the recommended repo **R** (`ranked[0]`): may the dispatcher launch WITHOUT per-selection confirmation, or must it inform-and-ask? This sits BETWEEN ranking (Phase 1) and the Owner-AUQ (Phase 2). It is a **pre-launch** decision, NOT a per-iteration kill-switch — the autopilot loop's 10 kill-switches are reused unchanged once a session is running (see `skills/autopilot/SKILL.md § Pre-Loop Verdict Gate`).

Compute the suitability verdict for **R** via the pure four-gate engine `computeSuitabilityVerdict(deps)` from `scripts/lib/autonomy/suitability.mjs`. The engine is pure + DI: the dispatcher gathers every signal and passes it in.

**Source each verdict input as follows** (the dispatcher already has most in hand from Phase 1):

| `deps` field | Source | Notes | |---|---|---| | `autonomy` | `resolveDispatcherAutonomy({ committed, env, ownerConfig })` from `scripts/lib/config/dispatcher-autonomy.mjs` | The effective dial. Defaults to `'off'` when unset (fail-closed). | | `confidenceFloor` | the `confidence-floor` from the parsed `dispatcher-autonomy:` block (default 0.5) | Same source object as `autonomy`. | | `confidence` | mode-selector `selectMode(signals).confidence` (0..1 float) for the recommended session-type | The same mode-selector the Phase-2 heuristic and autopilot use. | | `ci` | `checkCiStatus({ repoRoot: R })` → `{ status }` \| `null` | **CRITICAL (NICE-b):** Phase-1 `rank.mjs` exposes only the BARE status string (`readiness.ciStatus`). The engine's G3 gate expects an OBJECT `{ status }` — wrap it as `{ status: ciStatus }` when you HAVE a status; on a genuine ABSENCE (no CI configured) pass `ci = null`. **Since #1031 `checkCiStatus` no longer returns `null` on failure** — an unreadable state comes back as `{severity:'warn', ok:false, degraded:<reason>}`, and Phase-1 `rank.mjs` surfaces exactly that as `readiness.ciDegraded` with `readiness.ciStatus === 'unknown'`. Wrap that state as `{ status: 'unknown' }` (the engine warns `'CI signal unknown — treated as absent'` and G3 still passes) rather than flattening it to `null` — `null` claims a measured absence the pro

Read more
Ships withsession-orchestrator

Give your agents a working rhythm. You type three commands: /session reads your repository, your open issues and the last session, proposes what to work on, and waits for your correction.

Get the whole plugin

Other skills on session-orchestrator.