About autonomous-dispatcher
autonomous-dispatcher coordinates development and review work from labeled GitHub issues, for teams running an autonomous development pipeline. It scans issue states and starts configured local or remote task wrappers without modifying project source itself.
Key features
- 01Scans issues by workflow label
- 02Checks concurrency and retry limits
- 03Dispatches development and review tasks
- 04Supports local and AWS SSM backends
- 05Handles multiple projects per tick
Use cases
- 01Dispatch work from labeled GitHub issues
- 02Run periodic pipeline checks for one repository
- 03Coordinate tasks across multiple projects
- 04Route tasks to a remote development host
Install
Copy the command and run it in a terminal.
npx skills add https:/ / github.com/ zxkane/ autonomous-dev-team --skill autonomous-dispatcherAfter installing, type/autonomous-dispatcherin Claude Code to confirm it loaded.
Bundled files
References and scripts installed along with SKILL.md.
scripts/adt-gc.shagent-progress-snapshot-remote-aws-ssm.shautonomous-dev.shautonomous-review.shautonomous.conf.examplecheck-provider-cutover.shcheck-shell-idioms.shcheck-spec-drift.shdispatch-local.shdispatch-remote-aws-ssm.shdispatcher-multi-tick.shdispatcher-tick.shdispatcher.conf.exampledistill-knowledge.shgen-state-machine.shgh-app-token.shgh-token-refresh-daemon.shgh-with-token-refresh.shinstall-gc-timer.shlib-accounting.shlib-agent-smoke.shlib-agent.shlib-auth.shlib-code-host.shscripts/adapters/agy.shclaude.shcodex.shgemini.shkiro.shopencode.shDetails
From the project READMEAutonomous Dev Team Dispatcher
Scan GitHub issues and dispatch dev/review tasks. One cron tick is one invocation of dispatcher-tick.sh (single-project) or dispatcher-multi-tick.sh (multi-project). The full state machine, per-step semantics, and invariants live at docs/pipeline/ in the source repo — that's the spec; this file is the agent's invocation contract.
Prerequisites
ghandjqonPATH.- For the local backend:
$PROJECT_DIRset, pointing at the project root. Per-projectautonomous.conf(seescripts/autonomous.conf.example). - For multi-project / remote backends:
dispatcher.confdeclaringPROJECTS=()(seescripts/dispatcher.conf.example). autonomous-dev.shandautonomous-review.shneed the execute bit (mode100755) — restored upstream and self-healed bydispatcher-tick.shon every tick (#97).
Security note: This dispatcher processes GitHub issue content as input. In public repositories, issue content is untrusted — anyone can create issues. Ensure the
autonomouslabel can only be applied by trusted maintainers (use GitHub branch rulesets or organizational policies). The dispatcher only reads labels/comments and spawns local processes via the helper script — it does NOT modify source code or push to branches.
What to do
When the cron fires (default: every 5 min), run one of:
Single-project deployment (one repo per dispatcher):
bash "$PROJECT_DIR/scripts/dispatcher-tick.sh"
Multi-project deployment (one cron, many repos — closes #62):
DISPATCHER_CONF="$HOME/.autonomous/dispatcher.conf" \
bash "$PROJECT_DIR/scripts/dispatcher-multi-tick.sh"
The multi-tick wrapper iterates PROJECTS=() from dispatcher.conf and runs one dispatcher-tick.sh per project. Per-project failures are logged to stderr but do not stall other projects. See scripts/dispatcher.conf.example for the schema.
Each PROJECTS[] entry is one of two shapes:
- Local project (file path): a path to a per-project
autonomous.confon this dispatcher box. The dispatcher and the project source live on the same machine. The wrapper sources the conf via theAUTONOMOUS_CONFpriority-1 path (PR-4 / [INV-14]). - Remote project (inline metadata block): used when the project source lives on a remote dev box reached via AWS SSM. The dispatcher box does NOT have the project's
autonomous.conf— everything the dispatcher needs to scan issues + dispatch is declared inline indispatcher.conf.EXECUTION_BACKEND=remote-aws-ssmandSSM_INSTANCE_IDroute the actualdispatch-local.shinvocation to the remote box viaaws ssm send-command. (#62)
Either form runs the same 5-step tick:
- Concurrency gate — abort if
count(in-progress + reviewing) >= MAX_CONCURRENT. - scan-new — find
autonomous-only issues, check dependencies, dispatch dev-new. - scan-pending-review — find
pending-reviewissues, dispatch review. - scan-pending-dev — find
pending-devissues, retry-counter check, dispatch dev-resume (or mark stalled if exhausted). - stale detection — for
in-progress/reviewingissues, probe wrapper PID, branch on alive/dead and on PR/CI/idle gates.
The logic is in scripts/dispatcher-tick.sh and the helpers in scripts/lib-dispatch.sh. For the spec each step is implementing, see docs/pipeline/dispatcher-flow.md in the source repo.
GitHub Authentication — App token, not user token
All gh calls inside the dispatcher use a GitHub App token, not the default user token. The wrappers (autonomous-dev.sh, autonomous-review.sh) handle their own auth via lib-auth.sh; the dispatcher tick handles its own.
When GH_AUTH_MODE=app, dispatcher-tick.sh automatically calls gh-app-token.sh::get_gh_app_token after upfront validation and exports GH_TOKEN before any gh call (#91). Required vars in the project's autonomous.conf or the inline metadata block:
GH_AUTH_MODE=app
DISPATCHER_APP_ID=<numeric app id>
DISPATCHER_APP_PEM=<absolute path to PEM>
If any of those are missing, or if token generation fails, the tick exits 1 with a FATAL message — there is no silent fallback to user auth.
The token is valid for 1 hour and scoped to the target repo only. dispatcher-tick.sh typically completes in well under a minute, so a single token covers the whole tick. When GH_AUTH_MODE=token (default) or unset, the dispatcher uses whatever token the caller provides through the code-host's standard channel — on GitHub, GH_TOKEN or a gh auth login session; on GitLab (CODE_HOST=gitlab), GITLAB_TOKEN or a glab auth login session. The chp_*/itp_* provider seams route through whichever is present.
Dispatch Helpers
dispatcher-tick.sh calls a dispatch() helper for each task type, which routes to the configured execution backend. Do NOT spawn agent processes any other way. Each backend handles nohup, input validation, log-file mode 0600, and stale-wrapper kill (INV-09).
Backends today:
EXECUTION_BACKEND | Driver | When to use |
|---|---|---|
local (default, also when unset) | scripts/dispatch-local.sh | Wrapper runs on the same box as the dispatcher. |
remote-aws-ssm | scripts/dispatch-remote-aws-ssm.sh | Wrapper runs on a remote dev EC2 reached via AWS Systems Manager. The dispatcher sends aws ssm send-command to invoke dispatch-local.sh on the remote box. |
Per-task command shapes (passed through both backends identically):
| Type | Command |
|---|---|
| New dev task | dispatch dev-new ISSUE_NUM |
| Review task | dispatch review ISSUE_NUM |
| Resume dev task | dispatch dev-resume ISSUE_NUM SESSION_ID |
Lane GC Enforcement
adt-gc.sh periodically reclaims residue from dead lanes under INV-117's
registry-driven safety predicate. The built-in default remains --dry-run
on all platforms. P8 safety improvements may merge while the default flip and
production enforcement remain gated by issue #384's at-least-two-week clean
soak. Explicit ADT_GC_ENFORCE=1 or --kill requires authorized enforcement.
Inspect the effective mode and host prerequisites:
bash "$PROJECT_DIR/scripts/adt-gc.sh" --doctor
For an immediate box-wide rollback shared by the periodic timer and every
project's opportunistic --quick invocation:
source "$PROJECT_DIR/scripts/lib-lane.sh"
mkdir -p "$ADT_STATE_ROOT"
printf 'ADT_GC_ENFORCE=0\n' > "$ADT_STATE_ROOT/adt-gc.conf"
Remove the file to restore environment selection or the built-in dry-run default. A present
rollback file vetoes ADT_GC_ENFORCE=1 from the environment; explicit
--dry-run/--kill overrides both without inspecting them. Precedence is CLI
mode > persistent config veto > environment > built-in. Selected invalid values
warn and fail toward dry-run. The config is parsed as data and must contain
exactly one rollback assignment plus optional blank lines or comments.
When ADT_STATE_ROOT is unset, lib-lane.sh resolves the mode-600 host
pointer at $HOME/.local/state/adt-state-root, written atomically by
install-gc-timer.sh; this keeps the rollback on the same custom root used by
the timer and opportunistic collectors. The same lib-state-root.sh resolver
is used by local dispatcher marker reads and embedded into remote SSM liveness
checks.
What the dispatcher MUST NOT do
The dispatcher is a label-and-spawn coordinator, not a code-changer:
- MUST NOT commit or push to the target repository.
- MUST NOT modify source files in
$PROJECT_DIR. - MUST ONLY read issue labels/comments via the GitHub API, update labels via the GitHub API, and dispatch wrapper processes via the configured backend (
dispatch-local.shordispatch-remote-aws-ssm.sh).
Any code changes happen via the wrapper-spawned dev / review agents.
Environment Variables
Loaded from scripts/autonomous.conf (sourced by dispatcher-tick.sh before lib-dispatch.sh):
REPO: GitHub repo inowner/repoformat (e.g.,myorg/myproject)REPO_OWNER,REPO_NAME: split form of REPO (used for App token scoping)PROJECT_ID: project identifier for log/PID files (default:project)PROJECT_DIR: absolute path to the project root on the local machineMAX_CONCURRENT: max parallel tasks (default:5)MAX_RETRIES: max dev retry attempts before marking issue asstalled(default:3)DISPATCHER_APP_ID: GitHub App ID for the dispatcher botDISPATCHER_APP_PEM: path to the GitHub App private key PEM file
Cron Configuration (OpenClaw)
openclaw cron add \
--name "Autonomous Dispatcher" \
--cron "*/5 * * * *" \
--session isolated \
--message "Run the autonomous-dispatcher skill. Check GitHub issues and dispatch tasks." \
--announce
Label Definitions
| Label | Color | Description |
|---|---|---|
autonomous | #0E8A16 | Issue should be processed by autonomous pipeline |
in-progress | #FBCA04 | Agent is actively developing |
pending-review | #1D76DB | Development complete, awaiting review |
reviewing | #5319E7 | Agent is actively reviewing |
pending-dev | #E99695 | Review failed, needs more development |
approved | #0E8A16 | Review passed. PR merged (or awaiting manual merge if no-auto-close present) |
no-auto-close | #d4c5f9 | Used with autonomous — skip auto-merge after review passes, requires manual approval |
stalled | #B60205 | Issue exceeded max retry attempts; requires manual investigation |
For the full state machine, see docs/pipeline/state-machine.md in the source repo.
Model Strategy
| Task | Model | Rationale |
|---|---|---|
Development (autonomous-dev.sh) | Opus (default) | Complex coding, architecture decisions |
Review (autonomous-review.sh) | Sonnet (--model sonnet) | Checklist verification, avoids Opus quota contention |