Completion workflow contract
Status: implementation contract for backlog item 2. The service never runs Git or accepts a caller-authored assertion that a required check passed. Every mutation uses an idempotency key and rechecks authority after obtaining SQLite’s writer lock. Replaying a durable result does not renew a lease or refresh any authority field in the saved response.
Policy and immutable submissions
GET /api/v1/projects/{project}/workflow-policy returns
{project_id, revision, canonical_repository_key, required_checks}. A required check is
{identity, version, environment}; all three strings are stable exact-match
identities. A human changes the roster with
PUT /api/v1/projects/{project}/workflow-policy and
{expected_revision, required_checks}. Normal setup derives the shared repository
identity from the project’s URL and reuses an existing saved identity. Public
GitHub HTTPS, [email protected]:owner/repo and ssh://[email protected]/owner/repo
forms share an identity, including optional .git suffixes and trailing slashes.
GitHub owner/repository case is ignored. Other URLs have distinct exact-URL
identities; custom SSH hosts are not guessed to be equivalent.
The optional canonical_repository_key is an advanced administrator alias
configuration. Existing clients may resend an unchanged saved key. An
administrator can assign the same key to a custom alias before it has workflow
evidence, after verifying both URLs refer to the same repository. An existing
binding cannot change after submissions, while its project has an integration
hold, or when changing it would split a binding shared by other projects.
Equivalent URLs with conflicting historical bindings fail closed and require
explicit reconciliation; saved submissions and holds are never rewritten.
Omitting the key on an existing project preserves its binding even if its URL
has changed. The current project API does not expose a repository-URL edit.
The target branch remains the project’s validated Git ref. The roster must
contain 1–100 unique, nonempty checks. The
workflow-policy revision is independent of the existing
project policy revision. A code submission pins both. There is no implicit empty
roster and an agent cannot create, weaken, or replace it.
POST /api/v1/projects/{project}/attempts/{attempt}/submissions accepts the
following common fields:
{
"generation": 1,
"task_revision": 2,
"project_policy_revision": 3,
"workflow_policy_revision": 4,
"kind": "code",
"summary": "bounded result summary",
"acceptance_evidence": [{"criterion": "exact criterion text", "evidence": "bounded evidence"}],
"handoff": "bounded handoff",
"repository": "canonical configured repository URL",
"base_revision": "full source revision",
"candidate_revision": "full candidate revision",
"candidate_tree": "full candidate tree",
"candidate_remote": "credential-free canonical configured repository identity",
"candidate_ref": "refs/agent-coordinator/candidates/attempt-id"
}
The native CLI derives candidate_remote from the saved credential-free
canonical_repository_key. It creates the candidate ref, unless the caller
selects one under refs/agent-coordinator/candidates/, by pushing the exact
clean worktree commit to the configured remote. Before the submission POST, it
fetches that ref into an isolated verification namespace and compares both the
fetched commit and tree. The ref is create-only: a prior different commit is
never overwritten. A failed push or verification leaves the attempt active and
unsubmitted; a remote candidate ref may remain if the push succeeded before a
later verification failed, but no workflow activity or publication hold is
created. That ref can be reused on retry only when it still names the same
exact commit.
Native review and integration claims fetch and verify the recorded candidate ref from the configured remote before claiming work. Integration preparation fetches and verifies it again before making the integration result or durable publication intent. These client checks provide source availability across workstations; the service records the credential-free remote identity and ref but never runs Git or claims to have independently verified the remote. MCP coordination can inspect records but native Git capability is required for code submission, review, and integration.
Rows created before candidate refs were required retain candidate_ref = null.
They remain visible as historical records, but are not treated as remotely
available. Their review and integration activities are blocked with an explicit
candidate_checkpoint_missing condition. An operator may reopen an eligible
pre-publication submission for a new revision, and under recovery_mode=agent any
agent may revise it with reason_code: "candidate_missing" (the service verifies the
missing ref itself); the next code submission must
create and verify a durable checkpoint. Never infer or synthesize a ref for a
legacy commit.
For kind: "general", omit repository/base/candidate fields and send a workflow
policy revision of 0; project policy and task revision are still pinned. Every
current acceptance criterion must appear exactly once. Submission requires the
current unexpired owner, a matching kind/revision/policy, and quiescent subject
work: no retained reservations and no nonterminal or uncertain jobs. It atomically
ends the implementation attempt, stores the immutable submission, records the
contributor session, blocks ordinary subject claims, and creates the applicable
review and integration activities. The response is
{submission, subject_task_id, activities, next_actions}.
A code submission creates review activities from the pinned review_mode,
plus one integration activity. The modes are agent, human, both, either,
and none. both requires an agent_review and a human_review approval.
either creates one shared either_review activity: an eligible independent
agent or a human may claim it, and one approval satisfies the review requirement.
Only one session can own that slot at a time. Requesting changes requires a new
submission; another reviewer cannot override the decision on the old candidate.
Agent independence is enforced both when claiming and when deciding this shared
review, including the existing opt-in rules for non-contributing subagents.
The existing agent-only, human-only and no-review modes retain their behavior.
A general submission
creates only its configured reviews; with no review it completes atomically.
Activities have stable IDs and linked internal task IDs. They are returned from
GET /api/v1/projects/{project}/tasks/{task}/workflow and
GET /api/v1/projects/{project}/workflow-activities/{activity}. Internal activity
tasks are excluded from ordinary candidate selection and must be claimed through
the workflow API.
Activity authority and review
POST /api/v1/projects/{project}/workflow-activities/{activity}/claim accepts
{expected_submission_id, expected_project_policy_revision, expected_workflow_policy_revision} and an optional review_independence. It returns {activity, attempt, lease_remaining_ms, renew_after_seconds, current_authority}. Review activity
claims enforce actor type. Agent review also rejects every principal/session
recorded as a contributor to any revision of the subject task; contributor history
survives releases, recoveries, changes requested, and later submissions. Integration
claims require all current approvals and, if automatic integration is disabled, a
current human authorization. Integration claims atomically acquire the exclusive
hold for the project’s canonical repository key plus target branch. The hold is
global across projects and survives attempt/session/credential expiry.
Expired or revoked activity ownership respects the project’s recovery mode.
In manual mode, agents cannot take over. A human inspects saved work and physical
resources. Before publication intent exists, a human may reopen an expired,
revoked, or requirements-stale candidate; under recovery_mode=agent an agent may
revise it with a reason code (see below). Either fences its linked attempts and
requires a new submission and new reviews. Live current owners must release first
(the integration owner revising its own candidate is the exception), and
unresolved jobs/resources always block reopening. After publication intent, use
publication reconciliation. Completed workflows cannot be reopened.
A submission pins a digest of the task’s judged fields (title, description,
acceptance criteria, kind), not whole task or policy revisions. Priority edits and
policy changes such as the lease length no longer make a candidate stale. A policy
update reconciles required reviews for subjects still in review: it adds reviews a
tightened review_mode now requires, cancels queued ones no longer required, and
advances subjects whose approvals already satisfy the mode. Approvals are matched
by approver class (a human approval satisfies any review kind, an agent approval
satisfies agent or either reviews, each approval counts once; both needs one of
each). Subjects already in integration keep the review set they were approved
under. The required-check roster is captured on the publication intent; the
integration result and finalization validate against that captured roster. The
reconcile records a workflow.reconciled event; at startup it runs only in normal
coordination with a trusted clock and only adds or cancels reviews. Agents holding
agent_rule_editing may edit the rules text but never review_mode,
recovery_mode or the permission switches, so an author cannot loosen review to
land its own candidate. An ac_amendment is refused when no review is required. The
submission reports its ac_amendment, and a review decision records its
amendment_decision and, when the poster states it, its review_independence
(distinct_launch, distinct_host or distinct_vendor).
distinct_launch is enforced (decision U5). A session’s launch is the root of
its parent_session_id chain as the service recorded it at registration: a
top-level session is its own launch, and a registered subagent shares its
parent’s. When a review claim or decision asserts distinct_launch, the service
also refuses (reviewer_shares_launch) a reviewer whose launch is the launch of
any recorded contributor session of the subject task or of the tasks its landing
range carries. When the integrator later records a landing range, an approval
recorded as distinct_launch whose reviewer shares a launch with a contributor of
a carried task is voided like a contributor’s approval. Without the assertion the
principal/session checks above apply unchanged, so a same-launch subagent review
permitted by allow_subagent_reviews still works but cannot be recorded as
distinct_launch. The supervisor’s reviewer asserts it at claim and decision.
The launch walk follows at most 64 parent hops; a reviewer or contributor chain
nested deeper cannot be resolved, so a distinct_launch assertion involving it
is refused (reviewer_shares_launch) rather than trusted. For an agent
previewing a review activity, the read-only preconditions result also reports
distinct_launch_eligible and distinct_launch_unmet_preconditions (the
reviewer_shares_launch blocker the assertion would add), leaving
eligible_to_claim and unmet_preconditions unchanged for clients that do not
assert it.
Launch lineage is only what sessions declared at registration; a shared credential
can always open a fresh top-level session, so this enforces the recorded launch
separation, not process isolation.
An authenticated agent may reconcile only when the exact current candidate and policy are unchanged, current scoped decisions are resolved, a fresh observation of the pinned repository branch exactly matches the immutable pre-publication base or intended result, the old publisher is confirmed stopped, the integration owner is no longer live, and no registered job is live or uncertain and no reservation remains held. Agent reconciliation cannot publish or create check receipts. A moved target, missing durable intent or local journal, conflicting result, or any ambiguous producer remains human-gated with the target hold preserved. The native CLI serializes its local journal and records its evidence; the service stores this as a client attestation and does not verify remote Git or process state itself. Both human and agent reconciliation retain the original record and create a fresh integration activity under the same target hold.
Activity attempts renew through the existing
POST /api/v1/projects/{project}/attempts/{attempt}/renew endpoint and use existing
checkout, reservation, and job endpoints. They release through
POST /api/v1/projects/{project}/workflow-activities/{activity}/release with
{generation, summary, blocked:false}; a release after publication intent is
refused. Inspect and reconcile that publication instead; reconciliation creates
a replacement activity and retains the historical result. Workflow state changes
always require the activity ID as an additional candidate/type guard.
POST /api/v1/projects/{project}/workflow-activities/{activity}/review accepts:
{
"generation": 1,
"submission_id": "uuid",
"decision": "approved",
"summary": "bounded review summary",
"findings": [{"severity": "required", "remedy": "bounded remedy", "evidence": "bounded evidence"}]
}
decision is approved or changes_requested; severity is required or
advisory. The current, unexpired linked attempt and exact current submission are
required. A decision is immutable. Changes requested completes that review
activity, cancels the other pending activities, makes the subject eligible for a
new revision attempt, and keeps all old submissions, approvals, findings, and
contributors. A later submission creates fresh activities; old approvals do not
transfer. The subject’s task and project policy revisions must still match the
submission for approval to affect readiness.
Checks and integration
Required check receipts are selected by job ID; clients do not submit check
outcomes. A selectable job must belong to the current integration activity attempt,
have a terminal producer state of succeeded, exit code 0,
inputs_unchanged: true, no reconciliation amendment, and exact source revision
and tree matching the intended integrated result. Each job registered for a check
also carries immutable check_identity, check_version, and check_environment
fields that exactly match one pinned required-check entry. One current producer
receipt must cover every roster entry, with no duplicate identity tuple. Candidate
jobs cannot satisfy integrated-result checks merely because candidate and result
happen to share a commit label.
When project policy disables automatic integration, a human records intent with
POST /api/v1/projects/{project}/workflow-activities/{activity}/authorization and
{submission_id, expected_project_policy_revision, expected_workflow_policy_revision, summary}. Authorization is immutable and
applies only to that current candidate and policy pair.
After claiming integration and registering an isolated checkout, the owner calls
POST /api/v1/projects/{project}/workflow-activities/{activity}/publication-intent:
{
"generation": 1,
"submission_id": "uuid",
"observed_target_revision": "full target revision before publication",
"observed_target_tree": "full target tree before publication",
"result_revision": "full intended integrated revision",
"result_tree": "full intended integrated tree"
}
The service saves one immutable intent before any Git-side compare-and-swap. The
client must independently verify that the remote target still equals
observed_target_revision before publishing. Repeating with a new key cannot
replace an intent; retry uses the original key.
A fresh activity GET returns publication_allowed and
qualifying_check_job_ids. Publishing is allowed only while the exact candidate,
policies, approvals, authorization, activity lease, canonical hold, intent, and
one successful stable-input exact-result producer per required roster entry are
all current. Publication intent alone is not permission to publish. Once any
integration result exists, publication is no longer allowed for that activity.
POST /api/v1/projects/{project}/workflow-activities/{activity}/integration-result
accepts {generation, submission_id, publication_state, observed_target_revision, result_revision, result_tree, check_job_ids, summary} where publication state is
published, not_published, or uncertain. Values must match the saved intent.
An uncertain result may omit checks, is durable, makes the activity
recovery-required, and retains
the global hold. It never completes work or releases the hold. A known
not_published result also retains the hold until explicit reconciliation.
A human reconciles with
POST /api/v1/projects/{project}/workflow-activities/{activity}/publication-reconciliation
and {submission_id, disposition, observed_target_revision, observed_target_tree, evidence}, where disposition is published, not_published, or target_moved.
target_moved records that the intended result was published but a later external
actor advanced the target before finalization; it closes the old activity and hold
and creates a fresh integration activity for the same approved candidate. The new
activity requires fresh authorization and checks against its new result. Reconciliation
cannot invent check success. Confirmed publication must match the intent’s exact
result revision/tree. Confirmed nonpublication permits an explicit new integration
attempt after the old hold is closed in the same transaction.
Agents use the separate
POST /api/v1/projects/{project}/workflow-activities/{activity}/agent-publication-reconciliation
operation with {attempt_id, generation, submission_id, disposition, canonical_repository_key, target_branch, observed_target_revision, observed_target_tree, observed_at, local_journal_verified, publisher_stopped, evidence}. observed_at is Unix
milliseconds and must be no more than two minutes old. Both boolean fields must
be true. The disposition can only be published when both
identities equal the immutable intended result, or not_published when both
equal the saved pre-publication target. The endpoint also requires an expired or
revoked integration owner, no registered live/uncertain job, no held reservation,
current candidate/policy/decisions, and the still-held original target. Moved or
missing targets, an unavailable local journal, a running publisher, or any other
uncertainty stay human-gated. The service cannot independently inspect Git or
prove that a workstation process stopped; agent evidence remains a client
attestation. The native CLI obtains its protected journal lock and remote
observation before it sends this mutation.
For a known published result, call
POST /api/v1/projects/{project}/workflow-activities/{activity}/finalize with
{generation, submission_id, observed_target_revision, observed_target_tree}.
These last fields are a fresh post-publication remote observation and must equal
the intended result revision and tree. Finalization verifies the exact current candidate,
task revision, project policy revision, workflow-policy roster, approvals,
authorization, intent/result, exact remote observation, source-bound job receipts,
activity authority, and quiescence of both subject and integration tasks. It then
atomically completes the integration activity and subject task, closes the global
hold, and makes dependencies eligible. The only other completion path is the
human-only external delivery below; there is no generic or agent completion endpoint.
Tasks delivered outside the coordinator
Some changes cannot go through review and integration: a task that touches
privileged paths (for example .github/workflows) is landed by its owner with
scripts/ship.py, which fast-forwards main to a commit whose required checks are
already green. Submitting that commit afterwards would send an already-published
change through review and an integrator publication that is a no-op, and canceling
the task would record the delivered work as canceled. Use the external completion
instead, so the task ends done and counts as done in digests.
POST /api/v1/projects/{project}/tasks/{task}/complete-external takes
{expected_revision, delivered_revision, target_branch, reason, checks}:
delivered_revision is the full revision on the target branch that contains the
delivered change, target_branch must equal the project’s target branch,
reason says how it was delivered, and checks is a list of
{identity, conclusion} results read on that exact revision. Every conclusion
must be success, and when the project has a workflow policy every roster check
identity must be cited (required_check_missing). The task must be open, at
expected_revision, with no current attempt and no active review or integration
work; release or recover the attempt first (task_attempt_protected,
task_workflow_protected). The task becomes done (not archived or deleted),
its dependents become eligible, and the revision, branch, check results, reason and
acting human are kept in the task.completed_externally event, which task history
shows as the completed_externally lifecycle event.
This is an exception to the rule above that the service accepts no caller-authored
assertion about checks, because the service cannot reach the repository: it
records the human’s attestation that the revision is on the target branch and
that its checks passed; it does not verify either. It is a labelled human gate
(task_external_completion). Agent sessions are always refused, whatever the task’s
origin or the project’s delegation, so an agent cannot use it to skip review of an
ordinary candidate. Use it only after confirming, from the remote, that
delivered_revision is an ancestor of the target branch tip and that the required
checks succeeded on it. An ordinary candidate still needs review and integration.
The caller must be a human actor, and only a browser password sign-in is one, so the
manager’s task detail page is the way to use it: Complete as delivered externally
is offered to a signed-in human on an open task with no current attempt and no active
review or integration, prefills the target branch and the roster’s check identities,
and shows the service’s refusal code. A CLI session authenticates with an agent
credential and is refused with 403 operation_not_permitted.
Local worktree cleanup after completion
Every agent cleans up after its own completed tasks, in every project, so
finished work does not waste or clutter workstation disk. The procedure has one
source of truth: the service’s worktree and branch cleanup instruction
(WORKTREE_CLEANUP_INSTRUCTIONS in crates/server/src/discovery.rs). It is the
same for every project and is served in the orientation instructions that each
agent acknowledges before claiming, as step 8 of the orientation’s
completion_workflow, and in the MCP server instructions (also published
anonymously in /api/v1/info under data.agent_startup.mcp.instructions).
Follow that served text; this book deliberately does not restate it.
Coordination hooks
The coordination module must call these workflow hooks while holding its existing writer transaction:
guard_normal_claim(connection, project_id, task_id)rejects internal activity tasks and subjects with a current submission;record_contributor(connection, task_id, actor_id, session_id, now)runs when normal or recovery work is claimed and again before owned edit/checkout.guard_subject_mutation(connection, project_id, task_id)rejects definition/dependency edits while a submission is current. Policy changes may proceed, but immediately make pinned submissions stale until an authorized new submission/reconciliation.guard_release_or_recovery(connection, project_id, task_id)preserves workflow integration holds and refuses recovery/requeue paths that would bypass a current submission or publication uncertainty.- activity checkout/reservation/job registration calls
guard_activity_work(connection, project_id, activity_task_id, now)to reject stale candidates and policies while still allowing checkpoint, renewal, release, and late reporter observations. - task detail/status reads call
workflow_snapshot(connection, project_id, task_id, now)and expose its submission, activities, blockers, and next actions. Ordinary ready lists exclude task IDs present asworkflow_activities.activity_task_id.
The jobs module must accept optional check identity/version/environment at job registration and store them immutably. It must expose a server-side receipt lookup; workflow completion never accepts caller-authored status JSON.
Normal attempts pin attempts.task_revision and attempts.policy_revision when
claimed. Submission compares those stored values to the current task and project
and to the request. Existing pre-migration attempts have null pins and must be
released and reclaimed; callers cannot choose a newer revision at submission.
An authenticated human may cancel a stale current submission with
POST /api/v1/projects/{project}/tasks/{task}/workflow/reopen and
{submission_id, reason}. An agent session uses the same endpoint to revise
(CLI agent-coordinator revise) and must add reason_code and, where noted,
evidence; the project must use recovery_mode=agent, otherwise the refusal is
a labelled human gate (details.gate: "human_reopen_required"):
reason_code | Allowed agent caller |
|---|---|
conflict, check_failed | The session owning the submission’s live integration attempt; evidence required |
candidate_missing | Any agent, when the code submission has no durable candidate ref |
requirements_changed | Any agent that is not a task contributor, when the judged-field digest changed |
author_withdraw | The submission’s author |
Agents may revise a subject at most three times in 24 hours; the fourth attempt
returns revise_limit_reached with details.required_actor: "human" and the task
preconditions show the same code until a human acts; agent claims of the task are
refused with it. On integrator projects the integrator’s own conflict revise
may instead serialize the subject after the landing that moved the target; a
serialized revise does not count toward the three, but six revises of any kind
in 24 hours park the subject (see the integrator revise route in the API
contract). Reopening requires subject and activity quiescence, refuses
uncertain or known publication, cancels pending activities, preserves all evidence,
closes any safely releasable hold, and makes ordinary revision work eligible.