Skip to content

Agent-owned workflow task contract

Experimental workspace adapter. The shared task service is exposed through an authenticated workspace HTTP facade, a separate workspace MCP endpoint and the Unix agent-task CLI. The CLI drives operator enrollment, typed task calls, exact local collection and bounded signed upload. Existing server-executed skill workflows and the legacy read-only MCP endpoint remain available unchanged.

The contract lets an existing coding agent describe one installed workflow task at a time, perform work locally and return bounded assertions or observations without creating a second workflow engine. The existing journal remains the durable scheduler and recovery record. The authority remains the only component that can finalize a successful exact-subject receipt and independently rechecks current evidence at approval and native landing.

The Rust types live in skill_workflow/task_contracts.rs. Version 1 uses capability agentscm.agent-workflow-tasks/v1, strict JSON decoding, bounded collections and domain-separated SHA-256 identities. Unknown fields and unsupported versions fail.

Identity and requests

WorkflowTaskSubject binds repository, execution profile, protected target, immutable revision, Change and context, policy epoch, installed requirement, compiled plan, immutable skill and workflow step. WorkflowTask adds a logical task ID, required/optional graph status and a compact instruction entrypoint. Every offered task, including an optional step, has at least one criterion. A changed bound input produces a different task digest.

WorkflowTaskAttempt represents one fresh execution. It binds the task ID and digest, fresh attempt ID, immutable claim_request_id, immutable execution operation ID, authenticated owner and agent, executor registration and a positive bounded lease with monotonic fence. validate_for_claim binds the attempt to the exact claim request_id and limits the issued lease interval to requested_lease_ms. A timeout, expired lease or disconnected client does not prove that external execution stopped. A replacement gets a new fence; old-fence mutations cannot finish it.

Every mutation has a separate idempotency request_id. TaskClaimRequest defines a usable claim shape, including the new execution identity and requested bounded lease. TaskMutationRequest covers heartbeat, cancellation and authorized reconciliation and binds the exact task and attempt digests, execution identity, actor, executor and fence. Its canonical request digest is domain-separated by action. TaskResultAssertion likewise has its own result request identity. A future journal must bind each request ID to its exact canonical bytes: an identical retry returns the retained outcome, while the same ID with changed task, attempt, actor, fence, action, reason or result conflicts.

TaskStatusRequest is separate. It has no idempotency key, consumes no mutation identity and is always a side-effect-free exact-attempt read. Recovery-envelope reads are side effect free too.

Instructions and named references

InstructionEntrypoint contains capability and schema version, workflow and policy digests, every criterion, named references and one permitted workflow progression. Required criteria stay inline; progressive loading cannot hide an obligation. Reference reads are permitted to an authenticated actor that may view or has claimed the task, independently of that progression field.

A named reference contains only its name, expected SHA-256 and canonical relative path. Paths are ASCII, use / as the only separator and reject empty, . or .. components, leading /, backslashes, control characters (including NUL and DEL) and : drive/prefix forms. ReferenceFetchRequest supplies the exact task ID and digest, name, expected digest/schema and authenticated actor. The service must load the stored canonical task and derive repository, immutable revision and path; caller data is never source permission and cannot select an arbitrary path. Candidate-authored instructions remain data and cannot relax operator policy.

ReferenceFetchResponse preserves available, missing, denied, uncertain and limit as distinct bounded outcomes. Available content is bounded UTF-8 whose SHA-256 must equal both the stored reference and request commitment. There is no partial-success fallback.

InstructionEntrypointResponse returns an entrypoint or an explicit schema mismatch. A mismatch is not an empty task and cannot fall back to mutable client help.

Trust and criterion results

Trust classMeaningMay directly satisfy admission?
agent_assertionAuthenticated agent statement.No.
registered_executor_observationObservation attributed to an enrolled local executor through the ordinary assertion route.No.
independent_controller_evidenceExisting controller-owned collection and retention path.Only through existing controller finalization and exact-current admission checks.
authority-validated collector dependencyOriginal typed command or finite-review records transferred by an operator-enrolled collector profile and independently replayed or revalidated by the authority. This is assigned internally and is not a wire-selected assertion class.Yes, only through an imported v2 graph receipt and live native rechecks.

TaskResultAssertion carries one unique row for every task criterion, with a bounded outcome, findings and evidence commitments. Counts and completeness are derived, never submitted. Task-aware validation rejects duplicate, unknown or omitted criteria, wrong task/attempt bindings and a pass with no eligible required criterion. A mixed task uses not_claimed for controller-only rows; a registered executor cannot claim those rows. Agent assertions remain nonqualifying regardless of their row outcomes.

Independent review remains controller_only. An ordinary enrolled observation still cannot claim that criterion. The phase-three collector route is separate: an immutable operator-installed profile can authorize a particular enrollment epoch, repository, workflow profile and step to transfer original typed records. A collector signature attributes those records to its enrolled key; it does not by itself prove execution, independence or truth. The authority derives the trust class only after current-registry checks and full record validation.

ExecutorEnrollment::validate_scope_and_binding checks only structural repository/step scope and exact binding. ExecutorRevocation::matches_binding only compares identities. Neither is admission. The native authority now retains bounded operator-supplied Ed25519 public keys, immutable repository/step-scoped enrollment epochs and revocations. Its trusted-local administrative methods generate no keys or credentials, and its current-registration helper loads the full authoritative binding and revocation state. Later admission must call that native lookup transactionally and recheck current policy; no caller-provided epoch or revocation slice can establish currency.

Enrollment grants no repository-wide execution, extra source access, credentials, receipt creation or landing permission. Current-registry revocation invalidates observations from that registration even when retained earlier; re-enrollment uses a new epoch and does not rehabilitate old evidence. Required failure, incomplete coverage, cancellation and uncertainty remain blocking.

Observation signing bytes

For a registered observation, clone the validated TaskResultAssertion, set executor_provenance to null (the field is serialized as JSON null, not omitted), then serialize using the compact serde_json::to_vec encoding specified below, in declared struct-field order: version, task_id, task_digest, attempt_id, attempt_digest, request_id, execution_operation_id, agent, executor, fence, trust_class, outcome, criteria, executor_provenance. Nested field order is owner_id, agent_id for agent; registration_id, executor_id, registration_epoch, verification_key_id for executor; and criterion, outcome, findings, evidence_digests for each criterion row. Preserve all array order, including findings and evidence digests, and serialize empty arrays as [].

Emit no insignificant whitespace, BOM or trailing newline. Strings use double quotes; escape quotation mark and backslash as \" and \\, and backspace, tab, newline, form feed and carriage return as \b, \t, \n, \f, \r. Encode other U+0000–U+001F characters as lowercase \u00xx (validation still rejects NUL in findings). Emit every other character directly as UTF-8, including /, DEL, non-ASCII characters, emoji, U+2028 and U+2029, without Unicode normalization. Serialize integers as unsigned base-10 digits with no leading zeros, sign, fraction or exponent; retain the full u64 value without floating-point conversion. Enum strings use their declared snake_case names. This specifies the current struct serialization, not RFC 8785 canonicalization or JSON key sorting. The observation_serialization_golden_vector Rust test fixes representative bytes and their digest.

Compute SHA-256 over the ASCII domain agentscm.agent-workflow-observation.v1 followed by one NUL byte, then the JSON byte length as an unsigned 64-bit big-endian integer, then the exact JSON bytes. Store the digest as 64 lowercase hexadecimal characters. Ed25519 signs the raw 32 digest bytes decoded from that hexadecimal value; the signature is 64 bytes encoded as 128 lowercase hexadecimal characters. Current types validate shape and binding only; they do not perform cryptographic verification.

Recovery

TaskRecoveryEnvelope binds the request and immutable execution identities, exact task and attempt digests and old fence. It reports one of three dispositions:

  • confirmed: at least one confirmed effect, no unresolved effects, and only inspect/stop;
  • conclusive_rejection: the current request had no effect, with no confirmed or unresolved effect, permitting inspect or stop in every observed state; identical safe request retry is permitted only for pending; or
  • uncertain_effect: at least one typed unresolved effect, never an identical retry.

observed_state independently describes the existing attempt, not an effect of the rejected request. A finished, replaced or cancelled attempt can therefore truthfully accompany a conclusive rejection, but cannot authorize identical retry. Leased, expired and unknown attempts also cannot authorize that retry; the contract provides no safe retry precondition for those states.

Confirmed retained assertion requires a finished attempt. Fence supersession explicitly records the old and strictly greater fence; expiry alone never implies supersession. prepare_fresh_attempt is allowed only for replaced with confirmed monotonic fence supersession and only unresolved execution_may_still_run. That means an explicit authorized new execution with a fresh identity, not automatic replay. result_acceptance_unknown, resource custody or any additional unresolved external effect blocks fresh execution and requires inspection, reconciliation or stop.

Late old-fence results remain attributable to the old attempt and cannot erase uncertainty, finish a replacement or satisfy another revision. Recovery contains no executable command, credential or filesystem-path field.

Phase-two local state machine

The existing guarded skill-workflows/state.sqlite journal now has bounded logical-task, attempt, assertion and idempotency rows. The shared Rust core derives tasks only from the installed WorkflowRequirement, a compiled immutable current context and fixed action-class rules. It provides requirements, next-task selection, instruction/reference reads, claim, heartbeat, status, cancellation, authorized reconciliation, result retention and recovery projections for later thin service adapters.

Request identities are scoped to the exact authenticated owner and agent. One request ID cannot change bytes or cross claim, heartbeat, cancel, reconcile and result kinds; exact committed retries return their retained outcome. Claims serialize per logical task. Fences increase monotonically, expiry is a read-time observation rather than proof of termination, and only explicit reconciliation supersedes an unfinished generation. Restart and status reads do not dispatch or replay work. Missing journals are not created by requirements, next-task, status, entrypoint, reference or recovery reads.

Every selection and read invokes a caller-supplied current authorization guard. For new transitions, compilation and reference hashes are derived before entering the authorization callback. The callback must hold current actor/workspace authorization across the short transition, acquiring locks in this order: actor/workspace guard, journal lease/transaction, native authority writer reservation. While holding that native reservation, the core rechecks repository, policy, Change, exact context bytes, target, installed requirement and enrollment/revocation through the same authority connection, commits the journal, then releases the read-only native transaction. Native writers serialize across that commit; this is not distributed atomicity between databases. Contention fails closed without replay. The guard is never held across external execution. The workspace adapter implements this callback under its held workspace lock; any future adapter must provide the same atomic authorization boundary with its own actual lock.

Status, recovery and exact committed transport retries load integrity-checked retained tasks and attempts after current actor authorization, even when their source/context/policy or executor enrollment is stale. Attempt views report currently_applicable separately from the retained outcome; an old recorded lease is not current execution authority. New heartbeat/result effects still require the current subject and enrollment. Cancellation and reconciliation may fence the original stale attempt under current actor authorization and authoritative repository/Change existence, including after executor revocation. Before a fresh claim for the same repository, Change, profile and step, a bounded scan of at most 4096 retained task identities refuses unresolved older subjects until explicit reconciliation. Replaced attempts retain execution uncertainty; replacement uses a fresh execution identity.

Direct claims check pending state and completed dependencies inside the serialized journal transaction. Only task-bound registered observations satisfying the task’s criteria complete an observation dependency. Ordinary observation-only claims and results for controller-only steps are refused. Retained assertions are checked against their canonical task and exact stored attempt before graph progression. Named reference bodies are refused above 128 KiB before allocation; task derivation reuses resource hashes from bounded compilation.

Registered observations are accepted only after exact task/attempt/fence binding, authoritative current enrollment lookup and strict Ed25519 verification over the specified observation digest. Agent assertions and registered observations are retained as explicitly nonpassing material. They do not update the existing workflow graph, cannot claim controller-only review, and cannot create workflow_receipts or satisfy native admission.

Operator-enrolled collector transfer

An operator may install a CollectorProfile for one exact current executor registration epoch, repository, workflow profile and step. Enrollment remains observation-only by default; installing a profile is a separate act and never silently promotes an ordinary executor. Profiles are immutable. Re-enrollment creates a new registration epoch and old evidence stays invalid.

A command profile pins the complete CommandRecipe and SHA-256 of the concrete OCI backend’s observed runtime identity. The transferred record must carry that same runtime identity. A finite-review profile pins the executable digest, provider scope, Claude Code transport basis, model, installed policy digest, required path coverage and finite budgets. Fixture and unqualified HTTP bases are rejected at installation, import and native admission. Every recorded reservation and observation must use the pinned basis. Candidate data cannot select an executable, provider, model, credential, budget, image or fallback. There is no automatic hosted/server model dispatch and no fallback when the selected local collector cannot run.

The Unix trusted-local adapter accepts only a server-derived claimed descriptor matching that installed profile. It materializes the exact immutable native snapshot, uses the existing OCI command collector or finite review controller, and keeps its signing key, state and provider credentials outside candidate mounts. It durably reserves local executions and review calls before external effects and retains the signed transfer before upload. Reopening retained state does not rerun a command or model call. The production review collector accepts only the concrete Claude Code transport, constructed from an operator-selected executable and private working directory. It hashes executable bytes and checks them before and after dispatch; the signed transfer binds the observed executable/provider scope or OCI runtime identity. Collector state and snapshot parents must be owner-private and non-symlink, and attempt-derived snapshot paths use a single digest component. This trusts the whole local operator account and process: executable checks have inherent time-of-check/time-of-use limits, and a compromised enrolled signing key can fabricate internally consistent records. It is not hostile-host attestation.

Collector transfer is a distinct signed capability with a 16 MiB envelope limit, not the 256 KiB assertion format. Its header commits to the authority audience and repository, profile, exact task and attempt digests, execution operation, fence, originating actor and grant generation, registration epoch and key, immutable request, observed collector identity, fresh upload request and ordered typed-record lengths and digests. It carries only original ExecutionRequest plus ExecutionRecord, or ReviewRequest plus ReviewRecord; it has no artifact URL, run projection, database or receipt-upload field.

On import, the authority regenerates the pinned command request and validates the existing collector’s outcome, output, isolation and cleanup rules. For review it rebuilds immutable sources and the installed request, then runs the existing finite controller against a validation-only replay transport and journal. Replay checks every prepared canonical reservation/request, ticket, source exposure and retained observation and compares the complete derived review record, coverage, findings and outcome. Imported success flags alone never qualify.

Validated imports remain in authority-owned tables with their original signed bytes and become versioned dependencies of the existing compiled workflow graph. Native SourcePaths results are recomputed from the immutable snapshot. The graph determines success or completion with optional omissions; uploaded data cannot insert legacy server-owned records or finalize a client-selected run. The resulting v2 receipt names every transfer, task, attempt, fence, registration epoch/key/profile and originating actor/grant.

Approval and integration recheck the exact current subject, installed policy and requirement, retained signed transfers, current profile and executor registration/revocation under the native authority lock. They also require an authority-owned live authorization guard for every originating actor and grant; direct native entry points fail closed for imported receipts. A completed attempt lease need not remain live, but executor revocation, origin-actor revocation, changed policy/source/context/profile or re-enrollment prevents admission. A context requiring command/v1 still needs an exact successful native command or an enrolled imported command matching its entire subject, recipe, toolchain and environment; optional graph flags cannot waive that requirement. Legacy server receipts retain their existing behavior.

An exact committed upload retry is looked up after current combined caller/origin authorization and before current compilation or replay. It returns the historical retained view with separate applicability, including after source or policy drift; changed bytes under the same operation ID conflict. It does not finalize a receipt. reconcile_collector_finalization retries receipt creation from retained validated transfers under current authorization and the current subject, without collector execution. A retained transfer without a native receipt remains nonpassing.

The workspace adapter implements the combined authorization callback under one held workspace lock, covering the initiating actor/action and all originating grants through retention, finalization or admission, followed by the journal and native transaction. Any future adapter must provide the same atomic authorization boundary. Native receipt transactions recheck the exact permitted origin manifest. Finalization keeps dependency metadata only and streams at most 32 transfers one at a time, checking each stored length against 16 MiB before reading it; native admission applies the same per-record bound.

Capability matrix

CapabilityShared Rust corePublic API / CLI / MCPAdmission status
Agent assertionYesWorkspace HTTP and MCPAlways nonpassing. The server supplies the authenticated actor; client-selected passed remains an assertion.
Signed enrolled observationYesWorkspace HTTP and MCPAlways nonpassing; cannot satisfy controller_only.
Claimed task and fenced recoveryYesWorkspace HTTP and MCPRestart never dispatches or replays work. Status and recovery reads do not initialize a missing journal.
Operator-enrolled TEST command collectorYes, UnixClaim/descriptor through HTTP, MCP or typed CLI; CLI collection and bounded signed uploadEligible only after exact request/record validation and imported graph receipt rechecks.
Operator-enrolled REVIEW finite controllerYes, UnixClaim/descriptor through HTTP, MCP or typed CLI; CLI collection and bounded signed uploadEligible only after deterministic trace replay, coverage/outcome validation and imported graph receipt rechecks. No live provider qualification is claimed.
Live originating-actor authorizationYes, workspace adapterOne workspace lock covers initiating action and every origin grant through journal/native transitionRequired for imported receipt creation and every native approval/integration. Token, member and user revocation serialize with this lock.
Schema discovery (capabilities)YesWorkspace HTTP and MCPTask-independent and read-only; reports capability, supported schema versions and operation names. Same authorization as requirements.
Exact-subject qualification (qualification)YesWorkspace HTTP and MCPRead-only; never finalizes, writes a receipt or changes task state. qualified needs a current subject and an exact receipt that passes the live origin and executor checks used at admission; it is advisory and admission still rechecks.
Existing server workflow executionYesExisting interfaces unchangedExisting receipt/admission path unchanged.
Legacy coordination/read-only MCPExisting behaviorExisting /mcp unchangedLegacy reader tokens gain no task mutation tools.

Synthetic tests use fixture keys, a fake Docker control plane and explicitly fabricated Claude Code response envelopes. They exercise binding, replay, recovery and admission, including optional-command rejection and revocation during finalization. A local Docker 29.8.2 smoke with an already-present public Ubuntu 24.04 image exercised snapshot materialization, a trivial no-network shell check and confirmed cleanup through the production collector. This is a narrow observed command path, not hostile-runtime qualification. No live provider/model execution is qualified and no model API credits were spent. The ignored collector_real_docker_smoke test requires explicit AGENTSCM_COLLECTOR_SMOKE_DOCKER, AGENTSCM_COLLECTOR_SMOKE_SOCKET and AGENTSCM_COLLECTOR_SMOKE_IMAGE settings and never pulls an image.

Workspace HTTP and MCP adapter

Operator startup configuration may add agent_task_profiles alongside existing profiles, or use it alone. Each entry pins only repository_id, profile_id, native_repository, absolute data_dir, target and requirement_digest, plus an optional publish_forge_status (default false) that makes the Hub publish a commit status for candidate pull requests (GitHub) or merge requests (GitLab, fake-tested only); see workflow status. A task-only profile needs no Docker socket, command executable or model executable. Existing server profiles are automatically available to the same facade without changing server-executed workflow behavior.

The strict HTTP operation endpoint is POST /v1/hub/workspace/repositories/{repository}/profiles/{profile}/agent-tasks. Signed collector transfers use the separate bounded .../agent-tasks/collector-upload endpoint. The server derives the task actor and live origin grant from the workspace bearer credential; request JSON cannot select an actor, native path, executable, provider, model or source path. The response names capability agentscm.agent-workflow-tasks/v1 and schema version 1.

Workspace MCP is POST /v1/hub/workspace/mcp?repository={repository}&profile={profile}. It exposes capabilities, requirements, next, instructions, named reference, claim, collector claim, heartbeat, result, status, cancel, reconcile, recovery, collector descriptor, collector finalization and qualification tools through the same facade. Large signed transfer upload remains on the authenticated HTTP upload route. The original /mcp tool list, reader-token behavior and agentscm_workflow_status remain unchanged.

capabilities needs no subject and returns {"capability","supported_schema_versions":[1],"operations":[...sorted...]}. A reference request whose expected_schema_version is unsupported returns {"status":"schema_mismatch","capability","required_schema_version","supported_schema_versions","server_schema_versions","action"}, where supported_schema_versions echoes the versions the caller offered (as for instructions), server_schema_versions lists the server’s and action is a fixed retry hint, instead of an invalid-request error.

Selected task-only operating profile

The local Community workspace pilot uses only agent_task_profiles in strict execution config version 1. That keeps coordination, task discovery, qualification and native actions available without starting the server command/model worker or requiring Docker/Claude on the Hub service path. A runtime is needed only when the installed workflow task and collector profile select it. Existing profiles remain an explicit, separately qualified server-execution option; source_profiles cannot be paired with a task-only profile.

Run trusted collection on the same host against the exact authoritative native data_dir selected by the task profile. Use the same exact pinned agentscm artifact bytes for Hub and collector CLI, and retain reviewed config/profile/enrollment bytes plus their digests. No cross-version Hub/CLI compatibility is promised. The CLI reference gives the implemented key, enrollment, runtime-probe, task-call, collect and retry-upload commands; do not substitute a background claim loop.

Successful CLI transport is not successful evidence. agent-task collect exits successfully when it durably completes and uploads a typed failed collector record as well as when the check passes. After every collection/upload, query qualification for the exact current subject and require qualified: true, subject_current: true, the expected revision, a non-null current receipt and succeeded required tasks before presenting the subject for approval. qualification is still advisory; explicit approval and native integration recheck admission.

Hub /v1/hub/health is not this readiness check. It pings only Hub storage and does not authenticate the actor, open the task profile again, probe collector dependencies or validate a receipt. The deployment guide defines the selected supervision and readiness split.

Instruction entrypoint

agentscm init-repo writes a short “Workflow tasks” section into its managed instruction block (AGENTS.md, Cursor rules, copilot-instructions); re-running it replaces the block in place and leaves other content alone. The flow is discover then fetch: call capabilities for the supported schema, follow next, read instructions (the required criteria are inline) and call reference for one named reference only when needed. A schema_mismatch from instructions or reference carries server_schema_versions and a fixed action hint (both bounded; older payloads may omit them) and never a command to run. install-stop in task-stop mode prints an instructions pointer to the same guidance. There is no background updater or package resolver; a client updates by being reinstalled.

qualification takes the same subject as next and is read-only; unlike collector_finalize it cannot create a receipt. It returns qualified, subject_current, receipt_digest (null unless qualified), the subject revision, per-task {task_id,task_digest,step_id,required,state} with state pending|claimed|succeeded|failed|uncertain|blocked (an expired or cancelled attempt is uncertain until reconciled), bounded failures of {task_id,code} with stable codes rather than agent-authored text, and next. Subject currency and evidence come from one read snapshot that runs admission’s current-subject check (repository, Change version, context digest, policy epoch, target, readiness) and its evidence checks (source-path check, workflow receipt, and the command check unless the receipt is the imported kind); it is advisory because landing rechecks. A superseded subject reports subject_current: false, no tasks and {"operation":"checkpoint"}. Failure codes include attempt_in_progress (a task claimed under a live lease), authorization_changed (the live origin or executor guard refused) and prior_subject_unresolved (an older subject’s attempt for the same change, profile and step is unresolved, so next is reconcile with that older task_id rather than a claim the claim path would refuse). A finished failed attempt (collector_not_passed, result_failed and similar) has next: none: the claim path only claims a task with no attempt or a replaced one, so retrying needs a new checkpoint, which creates a new subject with fresh task IDs. next is typed data, never a command: collector_claim or reconcile with a task_id, collector_finalize, checkpoint or none. A client decides what to run; a missing receipt, an uncertain task, an older subject or a revoked executor or origin is never qualified.

These routes do not run a local collector for the caller and do not dispatch the server executor. The authority-derived collector descriptor is input to agentscm agent-task collect, which holds the enrolled private key and operator-pinned runtime state outside candidate source. The CLI recompiles the descriptor against the exact native authority in --data-dir, rechecks authenticated status before dispatch and signing, retains the signed transfer before upload, and provides retry-upload for the identical retained bytes. It never loops claims or falls back to the server executor or another provider.

The supported deployment keeps the authoritative immutable native revision, context and installed requirement in the local --data-dir used by the collector; this includes a collector on the workspace-service host or another explicitly synchronized native installation with the exact authority state. The task-only profile does not transfer native source or authority state to an independent laptop. Hosted source transfer remains limited to its server-execution profile, and the collector CLI does not claim automatic bootstrap or two-machine portability. Omitting MCP or CLI calls cannot bypass native admission, and no endpoint intercepts edits or wakes a disconnected agent.

Stop hook (agent-owned mode)

skill-workflow task-stop (Claude Code) and task-codex-stop (Codex CLI) are read-only Stop hooks for agent-owned tasks. They take CHANGE --endpoint URL --repository OWNER/NAME --profile ID --token-file ABS_PATH --workspace PATH, with the local native --data-dir as the global flag, and share the payload decoding, the 8-continuation counter and the output shapes of the other Stop hooks. The hook reads the Change’s current version, context digest, revision and installed requirement digest from the local authority, calls qualification with the token file (30 second limit), and reads the native workspace without changing it. The workspace is opened read-only (SQLite read-only flags, no schema migration, no journal-mode change; a workspace whose schema is not current blocks) and is read before and after the call, and the local Change subject is read again after it and must equal the one asked about (otherwise the hook blocks with next: checkpoint). task-stop does not migrate, alter or write the workspace or authority databases and creates no store files other than the Stop guard’s own agent-stop counter; SQLite may recreate its own -wal/-shm sidecar files when reading a WAL database. The native store is opened existing-only: a missing directory or lock file blocks instead of being created. The hook passes only if both reads are clean with the same base revision and generation, otherwise it blocks as changed during the run. qualification runs admission’s current-subject and evidence checks in one read snapshot but remains advisory, since landing rechecks.

It passes only if qualified, subject_current, the workspace is clean (ready, no pending operation, a complete scan with no changes, state and status agree on base and generation) and the workspace base equals the qualified revision. Every other outcome blocks: an unqualified or superseded subject, uncheckpointed edits, a different base, a network failure, an offline service, a 401 or 403 after token revocation, or a malformed answer. The answer is validated strictly: next.operation must be one of collector_claim, reconcile, collector_finalize, checkpoint or none; task_id is required for the first two and absent otherwise; ids and codes match [A-Za-z0-9._-]{1,128}; at most 32 failures; receipt_digest and revision are 64 lowercase hex characters; and revision must equal the local subject revision. Local and remote errors are reported as fixed categories (local store, workspace or token unavailable; service request failed; token rejected; malformed response) without paths or server text. Malformed stdin or counter errors exit 1, which is non-blocking, and an exhausted or unreadable counter ends continuation without running qualification. The block feedback holds the typed failures (task_id, code), a fixed next_action sentence chosen from next.operation, and next itself. When the local bytes are dirty or not at the qualified revision, next is {"operation":"checkpoint"} because the server answer describes a different subject.

next_action and next are data for the agent to read; the hook never executes them. It never claims, collects, uploads, finalizes, reconciles or checkpoints, so the agent must run those steps through agentscm agent-task or MCP and then try to finish again. install-stop accepts both commands; the token file must be an absolute path. Native landing still requires a current receipt whether or not the hook is installed. Tested with synthetic fixtures against the workspace service, not live-qualified in a vendor client. A synthetic MCP protocol client (initialize, tools/list, capabilities, reference, qualification, revocation) exercises the task API without a vendor hook; it is not a live editor test.