The assignment-to-PR workflow is specified in DESIGN.md §19.7. The coordinator connects the existing session store, task spine, GitHub executor, and bounded model lanes on the execution device. It never merges.
| Piece | Status |
|---|---|
Configuration schema (coordinator_config.py, $AGENT_HOME/coordinator.json) |
Implemented. Absent/empty config enables no worker. |
Runtime coordinator.tick / coordinator_runtime (+ coordinator_*.py helpers) |
Implemented in this repository. |
| CLI / daemon | agent coordinate --session ID advances one worker; --follow is the script loop. The daemon starts explicitly configured workers on startup. Changes to existing workers are read each tick; changes to the daemon worker set require restart. Legacy assignment dispatch and supervise refuse these sessions. |
Operator accounts, roles, check_argv, readiness_argv, workspace roots |
Never installed automatically. Operators add them explicitly. |
| End-to-end deployment on a named host | Not claimed. Deployment hostnames stay out of this public repository. |
| Universal sandbox / forced model isolation | Not claimed. Grok implementer argv denies Bash/subagents/web-search; that is process argv hardening only. |
Distinguish a requirement (DESIGN §19.7), an implemented module, and a verified deployment. This document does not invent evidence that a device is running the coordinator.
$AGENT_HOME/coordinator.json starts absent. A missing file, {}, or
null/empty workers enables no worker. Operator-supplied worker keys are
existing session IDs, bound explicitly in github-accounts.json and
ai-accounts.json; the coordinator does not select accounts or roles for them.
Each configured worker explicitly names review_session, workspace_root,
repositories, reply_logins, poll_seconds, lane_timeout, and
check_timeout. Each repository entry names its base, publication_repo,
check_argv, and readiness_argv. The two argv arrays belong to trusted
device configuration, never issue text or model output. They run the target
repository's required tests and additional readiness validation;
repository-specific policy stays outside the core. The publication repository
can equal the target repository; a different repository requires operator
authorization for that publication route.
Example device configuration (illustrative values, not installation defaults):
{
"workers": {
"worker-session": {
"review_session": "review-session",
"workspace_root": "/absolute/operator/worktrees",
"repositories": {
"example/project": {
"base": "develop",
"publication_repo": "example/project",
"check_argv": ["/absolute/operator/full-checks"],
"readiness_argv": ["/absolute/operator/readiness"]
}
},
"reply_logins": ["AuthorizedHuman"],
"poll_seconds": 30,
"lane_timeout": 1800,
"check_timeout": 600
}
}
}Both sessions must already exist with the required skills and explicit account bindings. The accepting script verifies the actual human assignment event before using it as the spine's human specification evidence. Missing evidence blocks implementation. No model makes that acceptance decision or posts its confirmation.
Selected execution profiles, repository route and check commands are pinned to the task. Changing them blocks that task instead of silently changing its execution identity. Adding unrelated profiles does not invalidate the binding.
from agent_cli.coordinator import tick
from agent_cli.coordinator_config import load_coordinator_config
workers = load_coordinator_config(store.home)
for worker in workers.values():
observations = tick(store, worker, runner=run_argv, lane_runner=None)tick(store, worker, *, runner=run_argv, lane_runner=None) -> list[str]performs one bounded, resumable advancement. It is not a monitoring loop. The outer CLI owns polling (poll_seconds) and invokes workers.- A Postgres session advisory lock (
coordinator-worker:<session_id>) is held for the whole tick and released on success and on error, so concurrent same-session ticks across processes are excluded. Device-wide source admission forrepo#issueusescoordinator-source:<repo>:<number>so two workers cannot open duplicate tasks/PRs for the same issue. - Before accepting work,
tickpreflights: worker session locally owned/active with skillsspine,review-loop,pr-review; formalreview_sessionowned/active withpr-review; all required AI lane slots present for the worker session; worker and review GitHub accounts configured, authenticated, and bound to different logins; worker account has git identity for signed commits. Missing or mismatched profiles start no provider. - Required AI slots:
grok:implementer,grok:reviewer,grok:pr-reviewer-quality,grok:pr-reviewer-logic,codex:pr-reviewer-quality,codex:pr-reviewer-logic. - Checkpoints live in
task.payload['coordinator']and activityresultfields. There is no second hub state machine and no new store table. runnerexecutesgh/gittrusted calls and returnsCompleted(returncode, stdout, stderr). GitHub-scoped calls go throughAccount.runner(explicitGH_CONFIG_DIR), never an ambient login.lane_runner(argv, stdin)is optional. When omitted, lanes and trusted argv lists run via a Python bounded subprocess (process-group kill on timeout), preserving stdin and cwd. Externaltimeout(1)is not used (absent on stock macOS). Tests inject fakes. Grok implementer argv is hardened with--deny Bash,--no-subagents, and--disable-web-search. This is process argv hardening, not universal sandbox enforcement.- Environment context for trusted
check_argv/readiness_argv(set in the child environment, with cwd = worktree):AGENT_COORDINATOR_HEAD,AGENT_COORDINATOR_BASE,AGENT_COORDINATOR_REPO,AGENT_COORDINATOR_PR,AGENT_COORDINATOR_SESSION,AGENT_COORDINATOR_WORKTREE. Those argv arrays never come from model or repo content. - The default process runner removes ambient GitHub tokens and uses an empty
temporary
GH_CONFIG_DIR. Script operations that need GitHub select their configured account throughAccount.runner; trusted check/readiness scripts must do the same (see github-accounts.md). - Models only edit/review/read. They never Git, GitHub, test, monitor, or merge.
Reviewer approval is only
STATUS: completeplusRESULT: approved. - Coordinator control/spec/log files live under
workspace_root/.coordinator-control/<task-id>/, outside the model worktree, so internal prompts are never staged as a patch. - Signed commits use explicit
git commit -Swith the configured Git identity.git verify-commitmust succeed cryptographically. SSH verification requires trusted allowed-signers in the Git account executor environment/config; signature text alone is never treated as proof. - Target repository is
source.repofor all PR API/gate calls.publication_repois the branch push location only. Base is fetched/pinned fromorigin(target), never from a stale fork develop. When publication differs, PR head ispublicationOwner:branch.
- Discover configured repositories for open issues assigned to the
configured GitHub login (
gh api --paginate --slurp). Initial scan includes current assignments (no silent first-run ignore). Idempotent source key:repo + issue numberdevice-wide — first session owns; one task/PR until terminal. Failed tasks are not auto-reopened merely because the issue is still assigned, and this coordinator does not implement automatic recovery ofstate=failedtasks on reply alone. Assignment evidence is verified on GitHub; forged model activity payloads are not trusted. Issue bodies are redacted/bounded before persistence.updated_atis not treated asassigned_at. - Accept with a deterministic issue comment (fixed wording + idempotency
marker) via
comment.post/scan_githubbefore any model start. A failed comment never starts a model. Effects are discovered on retry. - Checkout under
workspace_root/<task-id>with named remotesorigin/publication, ownership marker, pinned base revision from origin, and a deterministic feature branch. Clone, fetch, push, and signed commits use the explicit GitHub account runner. Never push a protected branch, never force-push, never reuse an arbitrary dirty/wrong directory as a fresh checkout. Interrupted clones are refused without deleting unrelated content. - Implement / inner review via
lane.launchbuilders with explicit session and config home. No round cap. Rejection routes findings to a fresh implementer. Outcome distinction (review-loop preserved, not waived):RESULT: ask→ publish a question checkpoint, keep the task non-failed, wait for an authorized reply, then resumeimplement.RESULT: blocked→ publish a blocker, settask.state=failed, clearresume_phase, and stop. Later ticks do not advance that task; discovery only notes that the failed task remains. Automatic reply-recovery of failed tasks is not implemented.- Incomplete implementer status / invalid RESULT, missing required
SUMMARY_EN/SUMMARY_DEafterdone/no-change, accept-time unassignment, and external blockers (CIaction_required, inaccessible CI logs, incomplete inner/PR reviews) keep a non-failed task, setresume_phaseto the exact safe phase (implement,accept,ci, …), and pinquestion_activity_idon the published checkpoint so an authorized reply can resume that phase — never a blind implementer start for CI authorization or acceptance.publish_blockeralso derives checkpoint eligibility fromresume_phase+ non-failed / non-done / non-uncertain state so a missed boolean cannot wedge another recoverable path; status re-publishes keep the existing checkpoint. Uncertain lane outcomes refuse a second model start (a human reply must not silently duplicate an uncertain process) and publish a GitHub-visible blocker.
- Draft as soon as the first signed task commit exists (
pr.openon the target repo), before full tests/reviews. Each new signed head is pushed to the existing PR before later stages. No empty fake PR when there is no patch and no existing PR. Crash after commit/push before draft reconciles without starting another implementer.task.refholds the PR number only (never the issue number). - Tests run only via script
check_argvon the exact clean signed head (cwd = worktree). Failure routes bounded output to the implementer. Stale passes from another head are not reused. - PR gates: Grok quality+logic in parallel (fresh independent
invocations; agent rows prepared on the main thread, subprocesses in
threads, results persisted on the main thread), then Codex quality+logic the
same way only after both Grok dimensions are approved on that head. Author
session does not sit those reviews. Incomplete/unavailable vendor output is a
GitHub-visible blocker with
resume_phaseofpr_gates_grok/pr_gates_codexand a pinnedquestion_activity_id— not a rejected complete gate and not an implementer fix loop. Rejections publishreview.postCOMMENT (notREQUEST_CHANGES) and invalidate head-specific evidence. - CI: exact-head PR check rollup and paginated head workflow inventory
(path+event+attempt), classified by one shared observer used by both
ordinary
phase_ciand Ready-side fresh rechecks. Onlysuccesscounts. An absentstatusCheckRollupis normalized to[]and inventory is still inspected —action_required(and actual failures) may be present only in inventory; absent rollup prevents green but must not hide those facts.action_requiredis an external authorization blocker (not routed to the implementer;resume_phasestaysci;question_activity_idis pinned so an authorized reply resumes CI observation). Hard protocol faults (malformed rollup, unexpected inventory shape, missingworkflow_runs, pagination truncation — typedCiObservationProtocolError/CiInventoryProtocolError) enter recoverable blocked +resume_phase=ci+ checkpoint; an authorized reply resumes CI once valid evidence is restored. Transient observation transport (PR view or inventorygh_json/ typedCiObservationTransportError) and pending / absent-yet evidence stay on staticphase=ciand retry without an idle model, blind implement, or reply gate. Missing / pending / failure / cancelled / skipped / neutral are not green. This core observes cumulative GitHub CI only; target- repository policy / A38 live join belongs to configuredreadiness_argv. Failures fetch plain-text logs viagh run view <id> --repo <target> --log-failed --attempt <n>(never ZIP/logsarchive bytes). Inaccessible logs, including successful fetches with empty or whitespace-only output, are a reply-recoverable blocker. Transient pending returns without an idle model. - Ready: run
readiness_argv(cwd = worktree, ambient GitHub tokens cleared). Stdout must be the fixed JSON readiness contract below (trusted operator script output — not model/repo input). Re-verify clean signed head after the command, re-observe CI fresh (no staleci_green), unchanged PR head, author/base/mergeability, tests, and all four same-head gates. Fresh CI rechecks on readiness / formal_approve / leave-draft (including after the Ready evidence comment) treat pending / absent-yet evidence and transient observation or PR-metadata transport as same-phase retry: no new authorized reply, no idle model, no premature APPROVE/Ready. Hard protocol faults stay fail-closed blockers. Observed actual failed CI raises a typedCiObservedFailureErrorhandled byadvance_one(not a Ready-phase reply gate): phase returns to scriptciso the next tick reuses existingphase_cilog fetch → implement routing; inaccessible logs keep the existing external blocker. No premature APPROVE/Ready and no model until actual logs exist. A known mismatched head/author/base is distinct from an unavailable fetch. Closecontributing_ok/ deviation checklist keys from that JSON viachain.close_allowedbefore Ready — never after human merge. Formalreview.postAPPROVE from the separate review account with an explicitly validated full-SHAcommit_idin the activity payload (executor discover-before-POST and POST both bind that head; verify state/head/login/id/url). Leave-draft re-runs readiness, then performs a fresh GET that must still show APPROVED on the exact head immediately before the Ready mutation (storedformal_headis not current proof; a dismissal during readiness or the evidence comment must block leave-draft). Only an observed same-marker revoked / non-APPROVED / misbound approval (or the matching typedreview.postexecutor error) clears stale formal evidence, pinsresume_phase=formal_approve, and requires an authorized new reply before another approval attempt — human dismissal is not silent override permission, and absent/unverified discovery alone is not treated as dismissal. Transient POST/transport or not-yet-visible discovery preserves the durable attempt/activity id and retries/reconciles through the existing executor without new human authorization; unknown delivery is never counted as approval. The resumed post-dismissal attempt uses a new durable activity occurrence (same attempt stays crash-idempotent; a dismissed same-marker APPROVE fails closed inreview.postand cannot be marked done). One evidence comment (must complete withexecution_status=done),allow pr-ready, then leave draft and verifyisDraft=false. Never merge. - Complete only after a verified human merge: GitHub merge actor type
must be exactly
User(missing type is not human; Bot is refused). Also require merge SHA, timestamp, and base/target. Then existingtask-donechecklist / summary guard (summaries must already describe the actual result — no boilerplate invented at merge), thenissue.assigned.ack. A Ready PR closed unmerged, or a non-human merge, is an intentionally non-recoverable user-facing blocker: stalequestion_activity_id/resume_phasecheckpoints are cleared, and later authorized comments must not consume replies or start an implementer. Reply resume requiresreply_checkpoint_eligible(saferesume_phase+ non-failed / non-done / non-uncertain); missingresume_phasenever defaults toimplement. Reassignment must not open a duplicate PR for a completed source. Revoked assignment stops new effects including formal approve / leave-draft;await_mergemay continue observation only.
Configured readiness_argv must print a single JSON object on stdout and exit
0. Installation defaults remain unconfigured (NULL); operators add the argv
explicitly. Required shape (exact HEAD + base binding):
{
"head": "<40-hex current clean signed HEAD>",
"base": "<40-hex pinned base_sha>",
"contributing_ok": true,
"deviation": { "declared": false }
}When a human-authorized exception exists (never inferred):
{
"head": "<40-hex>",
"base": "<40-hex pinned base_sha>",
"contributing_ok": true,
"deviation": {
"declared": true,
"granted": true,
"granted_by": "<login in worker.reply_logins>",
"evidence": "<explicit human grant provenance>"
}
}No automatic grants. deviation.declared=false closes deviation keys as n_a
with human source tied to the verified assignment mandate plus this trusted
script attestation. A declared exception without granted_by in reply_logins
fails closed.
execute_github(store, runner, *, activity_ids=(...)) requires the exact
intended activity id batch. The worker must not scan the whole device store or
publish unrelated pending intents from other sessions.
Implementer RESULT must be done|ask|blocked|no-change (empty / approved /
rejected fail closed). Reviewer RESULT must be approved|rejected; ask /
blocked are not code rejections. Completed lane outcomes are persisted before
signing/publishing so crash recovery applies the recorded result instead of
starting another model. Authorized replies (reply_logins only), listed with
gh api --paginate --slurp, resume the exact resume_phase after the pinned
question_activity_id checkpoint only when reply_checkpoint_eligible holds
(not blindly implement for CI authorization / incomplete review / checkout
blockers, and not at all for terminal failed / done / uncertain /
closed-unmerged / non-human-merge outcomes). Uncertain prior agents refuse a
second model start even when a human replies. Inner and PR reviewers receive a
script-generated base→head diff artifact outside the worktree.
Every lane prompt includes strict prohibitions and requires:
STATUS: complete|partial|timeout|unavailable
RESULT: done|blocked|ask|approved|rejected|no-change
A completed implementation additionally returns exactly one English and one
German change-summary sentence, each ending with a period, directly after
RESULT and before its body:
SUMMARY_EN: Describe the actual change here.
SUMMARY_DE: Die tatsächliche Änderung hier beschreiben.
The script records these semantic summaries; it does not invent them at merge. Missing summaries block progression. The remaining body is bounded. Empty, partial, timeout, or unavailable output is never zero findings and never approval. Nonzero process exits cannot approve, even when stdout claims completion. Model text cannot certify checks, CI, commits, or Ready. Recorded real assignment or an authorized human reply may evidence human spec input; the script never invents human grants.
Outputs stored or published are redacted and bounded. Profile credentials,
config directory paths, signing-key paths, and raw auth errors must not appear
in replicated rows or GitHub text. User-facing blockers and questions are
published on the source issue only when execution_status=done is verified;
otherwise they remain locally visible (CoordinatorError / StoreError
subclass SystemExit and must not be mistaken for success). Preflight
account/config failures can only report locally when GitHub is unavailable.
No silent failure.
| Module | Role |
|---|---|
coordinator.py |
Public tick + worker advisory lock |
coordinator_runtime.py |
Preflight, discovery, implement/inner/tests, advance_one |
coordinator_git.py |
Checkout, signed commits, push, draft |
coordinator_lanes.py |
Lane launch + parallel PR gate stages |
coordinator_github.py |
Comments, CI, readiness, formal approve, Ready, merge, replies |
coordinator_exec.py |
Bounded subprocess helper |
coordinator_common.py |
Shared helpers / constants |
coordinator_config.py |
Parent-owned configuration loaders |