Skip to content

Latest commit

 

History

History
337 lines (294 loc) · 20.6 KB

task-workflow.md

File metadata and controls

337 lines (294 loc) · 20.6 KB

Versioned repository task workflow

Enable tracker.provider.task_workflow: true for GitLab with Codex native Goals. Other trackers retain their existing execution contract. Configuration rejects an unsupported combination. The five logical states are ready, running, waiting, review and ended. Select exactly one ordinary type label: type:research, type:development or type:mixed.

People and the engine own different labels. Humans own symphony:ready (authorization and scheduling, kept while the task is live; removing it revokes scheduling), symphony:rework (in-scope rework against an existing criterion) and symphony:accept (accept the uniquely bound published manifest). The engine owns symphony:running, symphony:review, symphony:blocked and symphony:failed; it never writes a human label, and it repairs its own state label from the journal on every poll. Each human label event is consumed once by its native event ID, so a label left in place does not act twice.

Repository Issues own scope, deliverables, decisions and acceptance. The existing run journal stores decision references, execution restrictions, native identity, accounting intervals and evidence references. It does not become another task tracker. Both scheduler polls and native execution use the same journal and execution exclusion. Poll reconciliation runs independently of available agent slots and includes retained tasks without routing labels or with closed Issues.

Confirmed agreement

The sole assigned user confirms the first agreement by applying symphony:ready after a reviewable candidate note has been published. Its native actor must be present; the assignee never substitutes for a missing event actor. The Issue description contains a self-checked contract block:

<!-- symphony-contract
{"contract_sha":"SHA","type":"development","criteria":["result"],"deliverables":[{"id":"code","kind":"mr"}],"acceptor":"123","reviewers":[],"acceptance_mode":"separate","budget_ms":21600000}
-->

contract_sha is SHA-256 of the remaining JSON object (UTF-8, compact JSON, sorted keys, unescaped Unicode). Before authorization, publish a human-readable copy of the agreement with a symphony-candidate trailer. Its payload is {"action":"authorize","data":CONTRACT_WITH_ISSUE_VERSION}: data repeats the complete checked contract and adds issue_version, the digest of the full Issue description. TaskDecision.candidate_block/1 renders this trailer; rendering or publishing a candidate grants no execution authority.

The candidate must have native creation and update times proving unchanged content before the label event. The newest preceding candidate must be unique; cross-stream timestamp ties, edits, missing timestamps or missing actors cannot be resolved from polling order. Use an explicitly versioned authorize note when that proof is unavailable. The ready event stores the candidate's source reference and is checked again on admission and reconciliation. An old ready cannot authorize a replacement candidate or the latest editable description. Editing the description currently invalidates this legacy whole-description agreement; publish and authorize a new candidate. The parent-plan protocol will separate contract content from ordinary progress prose.

The description contains the human-confirmed goal, scope, permissions and acceptance criteria. IDs in criteria and deliverables refer to that agreement. An agreement names an acceptor and optional reviewers explicitly delegated to request in-scope rework. GitLab membership or an ordinary comment grants no authority. Later authorization changes require that recorded authorizer. When a valid description block exists, a note that carries the authorize action must repeat the block's agreement fields (type, criteria, deliverables, acceptance_mode, acceptor, reviewers, deadline, close_on_conclusion, dependencies, budget_ms) before it is accepted; a note cannot install a different agreement than the block.

Later decisions are ordinary human-readable Issue notes with this structured trailer:

<!-- symphony-workflow
{"action":"rework","contract":"label:42@1@2026-10-16T00:00:00Z","data":{"criteria":["result"],"changes":[]}}
-->

Research requires reports; development requires MRs; mixed tasks require both. separate is the default. merge is permitted only for one MR that completely delivers a development task. Set close_on_conclusion: true only when the authorizer explicitly grants native Issue closure after valid acceptance or cancellation. Omitted or false leaves closure to the human. A stopped execution and successful close readback are required before the next poll can report ended. An optional deadline uses Unix milliseconds and remains in force while paused. Optional dependencies are objects with id, kind (issue or mr), positive number and optional criterion list scope; omitted scope blocks the whole task. Dependencies are in the same repository and resolve on native closure or merge.

A decision reference is note:ID@VERSION, where VERSION hashes the compact JSON object containing the present body, created_at, updated_at fields and actor (the native author ID), with sorted keys. Mutable display names and profile metadata do not change a source version. The authorization reference is label:ID@ACTOR@CREATED_AT, taken from the authorizing symphony:ready label event. Every later decision names a top-level contract with the confirmed authorization reference, alongside action and data. For example, this is a complete pause decision:

{"action":"pause","contract":"label:42@1@2026-10-16T00:00:00Z","data":{"restriction":"manual-pause"}}

These complete decision objects are the contents of Workspace's --decision-file; the CLI appends the structured trailer to a separate human-readable explanation. Putting contract only inside data does not identify the decision's agreement and is rejected. Identity comes from the native note author, never the payload. Notes are paginated, ordered by source version time and deduplicated durably across restart. Edits form new versions; previous operations are not undone. Edited or missing authorization, manifest or acceptance evidence cannot grant new permission or prove completion.

Action Actor and required data
pause Authorizer; optional stable restriction ID; always task-wide
resume, release Authorizer; restrictions names exactly the restrictions being released
restrict Authorizer; kind, optional restriction, scope, detectable dependency condition
budget Authorizer; positive add_ms, added once to the cumulative limit
rework Acceptor or delegated reviewer; existing criteria, changes: []
manifest Authorizer or acceptor; complete delivery data below
accept Acceptor; manifest names the submitted manifest reference
cancel Authorizer; nonempty reason; cancellation is distinct from acceptance

symphony:rework and symphony:accept are the label form of the rework and accept decisions. A rework label needs an unchanged symphony-candidate note with action rework, the exact contract and explicit repair data. An accept label binds an unchanged native manifest note that demonstrably precedes it; it never borrows the current journal manifest. Without a unique binding, use an explicit versioned decision note. Pause, resume, restrict, budget, manifest and cancel still use the note form above.

Unmapped rework or nonempty changes creates a scope restriction. It does not expand authorization. Local restrictions leave independent criteria available; a changed active work scope stops and rechecks the native execution before continuing with fresh instructions. Task-wide pauses and cancellation override all execution. A release cannot remove a later pause. Dependency satisfaction, budget extension and pause release each affect only their own restrictions.

People can also write ordinary explicit requests such as 请暂停这个任务。, Please pause this task. or Please resume execution.. These intents use verified authorizer identity and source order; resume releases preceding pauses, including an older pause observed later, and the decision handoff of the question it answers. Unclear requests to pause or resume enter a clarification wait.

A blocking question is answered in the Issue itself. While the agent's handoff holds a task-scope decision restriction, the sole assignee's first note after the engine posted its question releases that restriction, so answering needs no label. Answering is not resuming: it leaves a pause the owner asked for in place, and only an explicit resume releases that pause. Notes carry second precision, so a note in the question's own second counts only when its note id follows the notice. Engine notices end with a <!-- symphony-engine-notice --> line of their own; the engine never answers its own question, and a reply that quotes the notice counts. A review handoff is unchanged and still waits for symphony:accept or symphony:rework.

Other prose remains discussion and requires a concrete decision where its meaning is unclear. A coordinating human session can translate an authorized decision into the structured entry. The executing agent may submit manifests and evidence; native dynamic tools and the decision CLI reject its attempts to originate human control decisions. This is an auditable tool boundary, not an OS credential sandbox. The execution must not bypass it through another credentialed client.

Delivery and acceptance

A published MR and a completed agent turn are execution facts. Intermediate MR review waits for human review. A verified publication request may specify continue_after: "merged"; only the recorded source version's native merge can release that review wait. Remaining work then passes admission again. Without that explicit continuation, review comments and merge do not requeue execution. Use native Related to for separate acceptance, mixed tasks and multiple MRs. For the final MR, the Workspace publisher accepts --handoff --manifest-file FILE. Prepare every delivery item before publication and use paired $publication placeholders in the current MR item's URL and version. The publisher binds that verified MR and saves the complete manifest in the same atomic review request. Symphony verifies the contract and item coverage, posts a human-readable full checklist with its versioned manifest, and stops execution. Do not combine final submission with intermediate continuation. Stopped-run polls recover unfinished review requests, including an already merged MR or a notice beyond the first comments page, without allocating a replacement executor.

The final handoff includes a 需要你做什么 section. It links the submitted artifacts, states when a report-only task correctly has no MR, and names the remaining action: merge the approved MR, add symphony:accept for separate acceptance, or request in-scope rework. The Scheduler generates this section from the manifest and acceptance mode so the action does not depend on an executor inventing prose.

Publish an overall manifest decision using this complete --decision-file object:

{
  "action": "manifest",
  "contract": "label:42@1@2026-10-16T00:00:00Z",
  "data": {
    "contract": "label:42@1@2026-10-16T00:00:00Z",
    "items": [{"id":"code","kind":"mr","url":"https://gitlab.example/team/project/-/merge_requests/9","version":"FINAL_SOURCE_SHA","criteria":["result"],"checks":true,"review":true}]
  }
}

For a manifest, both contract fields are required and must name the same authorization: the top-level field binds the decision, and data.contract binds the delivery manifest. The MR publisher's --manifest-file instead takes only the data object shown above, with paired $publication placeholders for the current MR. It constructs the complete decision when posting the handoff.

The complete manifest covers all declared deliverables and criteria, and each item's kind and native artifact type must match the agreement. All required MRs must be merged with the recorded validation/review evidence before conclusion. checks and review attest to the linked MR's business validation and technical review; Symphony does not infer them from an agent exit or GitLab mergeability. Reports use an Issue note URL ending in #note_ID, with the digest of its JSON-encoded body. GitLab /-/issues/NUMBER and /-/work_items/NUMBER routes identify the same Issue for report links; the origin, repository, Issue number, note ID and current body digest must still match. Put detailed reports and fixed evidence links in that note. This first protocol accepts same-repository MRs and Issue-note reports. Other artifact types must be represented by an evidenced report note; arbitrary mutable URLs are not accepted as versioned deliverables.

Submitting the complete manifest stops execution. Pending technical review or unmerged MRs keep the task waiting; only complete native delivery evidence enters overall acceptance. Separate acceptance requires the acceptor's accept decision naming that exact manifest. Its complete --decision-file object is:

{"action":"accept","contract":"label:42@1@2026-10-16T00:00:00Z","data":{"manifest":"note:MANIFEST_ID@MANIFEST_VERSION"}}

Use the authorization label's reference for contract and the submitted manifest note's reference for data.manifest; they identify different source decisions. The authorization reference comes from the symphony:ready label, so use its label:ID@ACTOR@CREATED_AT value. The symphony:accept label carries no manifest ref of its own: the adapter must reconstruct its exact preceding manifest from native history before reduction. A manifest note and label may arrive in the same poll only when their native times prove the order. An accept note without data.manifest is rejected, even when a current manifest exists.

Merge acceptance requires the declared single MR, verified source version, recorded checks/review evidence and native merge. A substantive report or source change invalidates the affected acceptance; submit and accept a new manifest.

A valid conclusion, native Issue closure and confirmed native stop are three separate facts. Only all three produce ended. An externally closed Issue with no valid basis remains waiting; it is not reopened automatically. Reopening adds an explicit reopen restriction. A pause does not revoke an existing merge acceptance agreement, and a legitimate merge during pause can conclude the task without restarting an agent. Cleanup is independent of business acceptance.

Accounting, recovery and rollout

The first configured limit is frozen into cumulative execution accounting unless the authorizer specifies another budget. Active execution, automatic waiting and rework backoff charge the task, including across restart. Human pause, decisions, MR review and overall acceptance stop charging only after native stop confirmation. Intervals are settled once; retries and new attempts do not reset consumption. Each attempt is capped by both the configured run limit and remaining allowance. A backwards wall clock stops work for a decision rather than granting new time. Successful handoff/rework attempts do not consume the separate cumulative failure counter. One confirmed-stopped transient failure is reworked automatically; the next failure becomes a persistent Incident that only a new human decision clears. There is no retry label, and a fresh decision does not reset the counter or the execution budget. Recording an Incident clears any pending resume_requested, so a rework granted while a run was still executing cannot silently recover a later failure. Recovery requires a new human decision and an executor state of confirmed stop: a run that failed before it allocated a thread is recoverable by a later decision, while a possibly-live thread stays an incident until the native stop is confirmed.

Cleanup additionally compares current decision-note versions with the last observation saved by the engine; a newly arrived or edited decision prevents deletion until reconciliation. It never reconstructs a second decision reducer. Executor lock receipts bind recovery and release to the recorded attempt/owner; a late callback from another scheduler cannot finalize the new execution.

The engine writes only its own state labels, and every write is verified by native readback before it is trusted. It never adds or removes a human label; an unexplained removal of symphony:ready creates an external_routing restriction that needs an explicit authorizer release. After confirmed stop, scope disputes, exhausted budgets and unclear control requests produce a deduplicated Issue notice with the required next action. Repeated polls and a lost local notice receipt do not repost the request. An unexplained external removal creates external_routing; it requires an explicit authorizer release and is not automatically undone. Every admission and post-hook activation rereads facts. When the old worker is gone, the poll owner halts the recorded Goal, interrupts its active native turn and validates native idle under the same execution lock. It neither starts a new thread nor resumes the stopped Goal. Confirmed observations update the last turn identity for later recovery; unknown allocation, native errors or an unproved stop remain waiting. The recorded worker host remains bound to a resumed thread. Queued stop messages take priority over native Goal handoff checks; active fact reads are asynchronous and fail closed on error or expiry.

Deploy both the engine and its integration before enabling the option in retained private workflows. Stop and reconcile the old service using its native execution records; never launch another instance over an active journal. Keep workspaces, records and existing executions. Legacy records are readable but receive no inferred agreement or acceptance; provide a real Issue confirmation before resuming them. A stale execution lock or unknown native allocation remains a recovery incident. Do not delete locks to manufacture stop confirmation.

The candidate-binding update also changes note source digests to include native creation time and author ID. The Workspace scheduling entry publishes an explicit versioned execution approval before routing the Issue, avoiding timestamp guesses when native actions occur in the same second. The terminal verifier accepts both explicit note decisions and the engine-selected label bindings. Deploy the engine and these helpers together; an engine-only rollout is unsupported.

observed_label_refs fingerprints native label facts independently of consumed decisions. Freshness reporting may acknowledge an unbound event without turning it into an authorization. A new or changed relevant event fences cleanup until the scheduler has observed it. Duplicate or malformed contract blocks never fall back to an unstructured agreement. Existing labels without candidate bindings cannot be promoted to authorization from the latest description. Retained source versions must be reconciled explicitly; do not rewrite historical receipts to make old authorization appear current.

Delegated Coordinator decisions

An optional tracker.provider.delegated_authority policy names one human principal (equal to the configured assignee), native bot publisher ID and base64 raw Ed25519 public_key. A structured decision can carry authority with grant and base64 signature. The grant signs canonical JSON containing schema 1, principal, publisher, host/repository, Issue number, issuance Unix seconds, decision (without authority), and evidence identifying the exact native Codex user request. The signature attests the trusted Coordinator's scope assessment; it is not a human signature. Executors cannot grant themselves authority merely by supplying an actor field or publishing under the bot.

Native author must match the configured publisher, the note must be unedited and created within five minutes of issuance (allowing sixty seconds of backward clock skew), and repository, Issue, principal and complete payload must match. Repeated signed notes select the earliest valid native ID once. The execution journal pins that exact source version; copies cannot revoke it or replace it after editing or deletion. Modified or missing sources cannot retain current authorization. The native note reference continues to hash the bot author. Parsed events keep publisher and verified human actor separately. Replay and restart use the same verification; no old execution acquires new authority. Unsigned native human decisions and old notes remain supported without edits. Workers receive no signing key. This is an application trust boundary, not isolation from a malicious same-user process.