Inputs
- Env: optional
RELAY_SKILL_ROOTdefaults toskills; role overrides useRELAY_ORCHESTRATOR/RELAY_REVIEWER. - Files: task/issue text, optional sprint file,
/tmp/dispatch-<N>.md, and/tmp/rubric-<N>.yaml. - Sibling scripts:
${RELAY_SKILL_ROOT:-skills}/relay/scripts/run-preflight.js,${RELAY_SKILL_ROOT:-skills}/relay-dispatch/scripts/dispatch.js,${RELAY_SKILL_ROOT:-skills}/relay-review/scripts/review-runner.js.
Dev Relay
Execute the plan -> dispatch -> review cycle. GitHub delivery stops at ready_to_merge unless the user explicitly asks to merge; local delivery runs canonical recovery and stops at terminal reviewed_result_ready.
Role Defaults
- Orchestrator: Codex unless
RELAY_ORCHESTRATORis set at dispatch. - Executor: Codex unless dispatch
--executorselects another adapter. - Reviewer: Codex unless
RELAY_REVIEWERis set at dispatch. The binding is immutable; review--reviewermust equal it and is not an override.
Assigned run.json roles stay immutable; acting reviewer data is recorded separately.
Step 1: Source and Re-Anchor
Classify the repository before any fetch, forge lookup, worktree, run-directory,
or executor effect. Relay requires Git and never runs git init for you. Run
the route preflight first; it performs the read-only source gate and then
route-specific in-flight checks (the existing duplicate-PR guard on GitHub and
local-only run facts without a remote):
PREFLIGHT=$(node "${RELAY_SKILL_ROOT:-skills}/relay/scripts/run-preflight.js" \
--stage route --repo . --issue-number "$ISSUE_NUMBER" --branch "$BRANCH" --json)
If source.route is local-reviewed-result, use local task text or the user
description and do not run git fetch, gh issue view, or any PR lookup. The
run continues through Git verification, independent review, and canonical
Reviewed Result closure without a forge. If source.route is github, use
the reported source.remote_name for the existing re-anchor fetch, then use
gh issue view <N> when issue text is needed. That route retains its current
GitHub deduplication and requires authenticated GitHub access plus the selected
executor/reviewer's ordinary ambient CLI authentication and network
availability. GitLab and
other forges are unsupported; configure GitHub, remove all remotes for local
delivery, or use direct delegate. A SOURCE_NOT_GIT error recommends
explicit git init or direct delegate.
Task evidence follows the selected route after that gate: GitHub issue text
when the GitHub route has an issue number, otherwise local task text or the
user description. Use its track: or component: value as the sprint
ownership handle. If no issue number, use a descriptive branch name and skip
issue-close in merge.
Sprint tracking is optional; when in use, resolve ownership per sprint-integration.md.
Follow inflight.instruction whenever inflight.route != "continue"; that dedup guard is binding and the route table lives in preflight-guards.md. On continue, judge readiness yourself using the checklist in ../relay-ready/SKILL.md; no script scores it. When that judgment is needs_split, route through proposal-first relay-ready shaping, and the accepted handoff becomes the relay-plan source of truth before any dispatch.
Fast path: skip relay-ready only for one ready leaf with a stable review anchor and no clarification or decomposition needed. Otherwise run relay-ready; its handoff brief becomes the downstream source of truth.
Step 2: Plan
Always build a rubric. Follow relay-plan's process: read task, recover Done Criteria, build rubric, emit handoff artifacts. Do NOT dispatch from relay-plan; Step 3 handles dispatch. Write the dispatch prompt and rubric YAML to temp files such as /tmp/dispatch-<N>.md and /tmp/rubric-<N>.yaml.
Step 3: Dispatch (relay-dispatch)
relay owns lifecycle orchestration; relay-dispatch owns dispatch CLI semantics. Pass a fixed executor or model explicitly with --executor and --model; no catalog, preset, or model-hint fallback participates in selection.
For actor+model wording such as "opencode glm-5.2", pass both values explicitly. For model-only wording such as "glm-5.2", do not guess an actor; ask for actor context.
node "${RELAY_SKILL_ROOT:-skills}/relay-dispatch/scripts/dispatch.js" . \
-b issue-<N> --prompt-file /tmp/dispatch-<N>.md --rubric-file /tmp/rubric-<N>.yaml \
--done-criteria-file <done-criteria-path> --timeout 3600 --detach --json
# To pin dispatch selection, append: --executor <name> --model <provider/model>
If the rubric itself is the frozen Done Criteria, omit --done-criteria-file. --detach returns a snake_case launch receipt containing status, run_id, run_dir, worktree, attempt_id, host_handle, dispatcher_pid, and inspection; attempt log paths are durable facts. The dispatcher continues if the calling shell dies. Poll read-only inspection while the recommendation is wait:
node "${RELAY_SKILL_ROOT:-skills}/relay/scripts/relay-recover.js" inspect --repo . --run-id "$RUN_ID" --json
node "${RELAY_SKILL_ROOT:-skills}/relay/scripts/run-preflight.js" --stage review --repo . --run-id "$RUN_ID" --json
Follow inspection.recommended_action exactly:
recover→ run canonicalrelay-recover recoverwith the returned action key; it alone commits, pushes, and records or creates the exact PR.review→ proceed to Step 4.redispatch→ call dispatch with the immutablerun_id; all other resume attempts fail before writing.wait→ keep polling;operator_attentionornone→ stop and resolve the blocker.
After dispatch, the default conveyor for repeated wait/recover polling is the
caller-only advance driver. It re-inspects before every action and invokes only
canonical recovery, then prints the typed stop and exact next operator command:
node "${RELAY_SKILL_ROOT:-skills}/relay/scripts/relay-advance.js" \
--run-dir "$RUN_DIR" --json
Advance stops for review, merge, redispatch, missing verification evidence,
operator attention, or its step budget; it never dispatches, reviews, merges,
or acquires a lifecycle lock itself. Supply --verification-file when the
recommended recovery requires immutable verification evidence.
Use relay-status.js --all for the offline cross-run cockpit. Add --gc for a
retained-worktree dry run and --apply only for explicit reclamation.
Capture run_id, run_dir, and the current action key. The immutable record is ~/.relay/runs/<repo-slug>/<run-id>/run.json; lifecycle state is folded from events.jsonl plus live observations, never stored as mutable lifecycle state. For in-flight writes, resolve ownership per the same sprint-integration.md contract.
Step 4: Review (relay-review)
MANDATORY. Do NOT skip this step.
Verify before review. Before invoking relay-review, reconcile every declared verification against the retained worktree and state its result. Accept only actually executed or observed results as evidence. Run checks reported unavailable or proposed before review; a failed or still-unavailable check blocks review.
Invoke relay-review only when inspection recommends review:
node "${RELAY_SKILL_ROOT:-skills}/relay-review/scripts/review-runner.js" \
--repo . --run-id "$RUN_ID" --json
Invoke relay-review in an isolated context. It records immutable review evidence and facts while keeping frozen Done Criteria as the review anchor. A requested change remains blocking until the corrected HEAD receives a passing primary review. Do NOT review inline.
The runner returns verdict and a fresh recommended_action. On the GitHub route, lgtm must recommend merge before Step 5. On the local route, lgtm must recommend recover; run canonical recovery with that action key to append the terminal reviewed_result_ready result. changes_requested must recommend redispatch; send a new prompt through dispatch --run-id, follow any recovery needed to publish the corrected GitHub revision or retain the corrected local commit, then review again. A runtime invocation failure may recommend one explicit review retry bound to that failure; a second failure, model-returned escalated, operator_attention, or a mismatched action stops for investigation.
Step 5: Finish the Selected Route
For source.route=github, LGTM must derive merge/ready_to_merge; stop there until the user explicitly authorizes relay-merge. For source.route=local-reviewed-result, LGTM must derive recover/reviewed_result_ready; run that exact recovery action and stop only after inspection reports the terminal Reviewed Result. Create follow-up issues if discovered during review.
Batch Mode
When multiple independent tasks are ready, prepare a relay-fleet batch but preserve /relay's ready_to_merge stop until the user explicitly authorizes landing it; after authorization, relay-fleet is the default parallel batch drive. See references/batch-mode.md for the remaining conflict-recovery note and the "when in doubt, run sequentially" principle.
Summary Checklist
Verify Done Criteria fully implemented, relay-review LGTM/audit evidence, and the selected route's exact finish state: ready_to_merge for GitHub or terminal reviewed_result_ready for local delivery. Then apply any sprint/follow-up updates per sprint-integration.md.