Should a Coding Agent Be Allowed to Mark Its Own Task Complete?

One problem keeps showing up in coding-Agent workflows: the Agent edits a few files, runs part of the test suite, writes a confident final message, and the surrounding system treats that message as done.

That shortcut works until the first non-trivial failure. The intended file may have changed while an unrelated file changed too. A narrow test may pass while the required suite was never run. A commit may exist locally but not on the remote. A deployment command may return successfully while the service stays on the old version. None of those facts can be established by the final message itself.

My central claim is simple: an Agent may submit a completion claim, but it should not be the authority that accepts that claim.

The longer engineering argument is in Completion Is a Claim, Not an Accepted State. The practical version for coding work can be much smaller:

Agent says done
→ read back repository and external state
→ run deterministic checks
→ inspect process, outcome, blockers, and side effects
→ accept, reject, escalate, or leave undetermined

The first layer should be deterministic whenever possible. For a repository task, I want the actual diff, the required test results, the build output, the commit SHA, and a remote readback. If the task changed a service or an issue tracker, I also want the state read from that system after the write. This is less glamorous than a judge model, but it catches the most common gap: the Agent reports what it intended to do rather than what the environment now contains.

The second layer is interpretive. A verifier can review the trajectory and ask whether the edits match the request, whether the chosen tests are meaningful, whether an external blocker caused the failure, and whether an unexpected side effect appeared. Microsoft Research’s Universal Verifier is useful here because it separates process quality from outcome attainment and retrieves evidence for individual rubric criteria. Its implementation also makes the evidence-oriented design easier to inspect.

That distinction matters in day-to-day Agent work. If an Agent follows the right procedure but hits a missing credential or CAPTCHA, the process may be valid while the outcome is not reached. If a build becomes green through an unrelated workaround, the outcome may look acceptable while the process and scope are questionable. A single success: true field throws away those differences.

I would rather preserve a small state model:

running
→ completion_claimed
→ verifying
→ accepted | rejected | escalated | undetermined

blocked and undetermined are not cosmetic states. They tell the next worker whether to retry, request authorization, repair the environment, or ask a human to decide. They also prevent a recovery worker from starting from an invented “completed” checkpoint.

The remaining question is where the acceptance authority should live. For a local refactor it might be a deterministic policy plus tests. For a production change it may require QA or an explicit human approval. For a long-running autonomous workflow, it probably needs a versioned acceptance contract that states which evidence is mandatory and who may override a failed or ambiguous check.

How are people handling this with Cursor Agents in real repositories? Which evidence do you require before a task may enter done, and do you keep blocked or undetermined outcomes separate from failed work?

Other language: 中文版本

We run Cursor agents against a real Git-backed config and C++ course tree, and we reached the same conclusion you did: the worker may claim completion; something else has to accept it.

A last-message done is cheap. In this environment it has been wrong in exactly the ways you list: an unrelated file changed, the narrow test ran instead of the required suite, a commit existed only locally, or a command exited 0 while the interesting artefact did not. None of those facts live in the prose.

What we require before a task may enter done

We split verification the way the Universal Verifier paper splits process and outcome, but with repo facts (diff, tests, Git) instead of screenshot rubrics.

Deterministic layer first (read the environment after the write):

  • The actual diff, not the files the agent named.
  • The required checks for that kind of work: compile + sanitizers + static analysis for C++; lint/schema/manifest for config Markdown and YAML; a one-line script result when a script was the contract.
  • If the claim includes Git: working tree vs index vs HEAD, and a remote readback if the claim was “pushed.”
  • If the claim includes a service or tracker: read the record back after the write.

Interpretive layer second: did the diff match the request, were the tests the right tests, was there an external blocker, and did an unsolicited extra file or extra behaviour appear. A reviewer pass (human or a second agent that did not implement the change) owns this. We also keep an explicit three-way verdict for falsifiable claims: verified / not verified / inconclusive — inconclusive is not a polite fail.

We do not treat a stronger model as a substitute for that split. The UV write-up’s own ablation (same stronger model in WebVoyager / WebJudge, screenshot selection unchanged) is the cautionary tale: false positives drop and false negatives rise sharply. Architecture beats swapping the backbone.

Blocked and undetermined stay out of failed and out of done

We keep those states because retry policy differs:

  • Blocked / uncontrollable: missing credential, allowlist prompt the human never answered, CAPTCHA-class wall, tool not installed. Process may have been fine; outcome is not reached. Next step is repair the environment or ask a human — not “the agent failed the rubric.”
  • Undetermined / inconclusive: checks disagree, or the evidence was not captured. Safer than manufacturing completed. A recovery worker must not start from an invented checkpoint.
  • Rejected / controllable fail: wrong intent, skipped required suite, hallucinated “tests passed,” unrelated workaround that greened a build.

A single success: true from the implementer throws those distinctions away. Plan todos and issue checkboxes are especially dangerous here: the same agent that edited the code can flip completed in the same turn. We are tightening that so a claim is not the same field as an accepted receipt.

Where acceptance authority lives

Kind of change Who may accept
Local refactor / lab edit Deterministic policy + tests; human still accountable for the commit
Config/policy edit Independent gate (conflict, coverage, scope) plus changelog; a gate hold is not a ship
Push / tag / release Separate VCS role; force-push and history rewrite stay human-only
Ambiguous or irreversible Human. undetermined until then

That is separation of duties at student/solo scale: worker submits evidence; verifier scores evidence against a versioned contract (the plan’s done criteria, written before the trajectory); acceptor applies risk. Logs record disagreement instead of collapsing it.

What we are not doing

We are not dropping Microsoft’s Universal Verifier or a full Digital Employee runtime into the coding loop. UV is a web computer-use judge; our daily work is diffs, compilers, and Git. The portable piece is the contract: claim-specific evidence, process vs outcome, failure attribution, independent acceptance. Screenshot-by-criterion retrieval is the right idea for long computer-use traces; for a repo task the analogue is indexing checks to claims (this test, that SHA, that remote) rather than dumping the whole log into one judge prompt.

Happy to compare notes on how others bind “required evidence” to a plan so the next session cannot treat a leftover completed checkbox as ground truth.

Thanks for writing this out. I took a closer look at Spine after reading your reply, and the connection is much clearer now. -AgentSummary is doing something important: it gives the agent a compact, deterministic projection of the process result instead of another prose self-report.

On our side, FCoP is only the coordination protocol: TASK, REPORT, ISSUE, and REVIEW, with location defining lifecycle state. CodeFlowMu is the engineering runtime that lays the execution rails. That is why your probe work caught my attention: a Spine probe could provide the deterministic evidence at the runtime boundary, while FCoP carries the evidence reference and the review state.

The difficult part is authority. The worker may produce the probe result, but it should not be able to rewrite the acceptance contract or sign its own receipt. We are leaning toward versioning the criteria before execution, with any later change invalidating the existing review. Have you experimented with keeping the acceptance receipt outside the worker’s working tree, or do you deliberately keep the whole chain in Git for inspectability and portability?

Thanks — that split is the useful one. FCoP as the coordination protocol (TASK / REPORT / ISSUE / REVIEW, with location as lifecycle) and a separate runtime laying the rails matches how we use probes: the script emits a compact, deterministic projection; the protocol or reviewer should carry a reference to that result plus the review state. The worker may produce the probe output. It must not rewrite the acceptance contract or sign the receipt.

On -AgentSummary: yes. A last-message “tests passed” is another prose self-report. The one-liner is bound to the process exit code, so an agent cannot honestly claim OK while the script failed. That is still evidence, not acceptance. Write-up: One-line script outcomes. Public artefacts: spine-cursor 0.3.2 (spine-agent-probes 0.1.1) and spine-automation 0.1.6.

Where the receipt lives. We keep the whole chain in Git on purpose: inspectability and portability. Done criteria, probe tokens, diffs, and a named acceptor all clone with the repo; a third party can re-run the same probes. We have not moved the acceptance receipt outside the worker’s working tree. Viewing surfaces that live outside Git are not receipts.

The honest gap is the one you named. If the worker can write the tree, it can also rewrite the receipt. Our separation of duties is therefore role and policy (claimant ≠ acceptor; the implementer is not the gate that marks done; push and tag stay a separate VCS role) rather than cryptographic inability to touch the artefact. An out-of-tree or CI-signed receipt is the stronger authority model. At student/solo scale, Git-in-tree plus a named human accept won.

Version the criteria first. We write done criteria into the plan before the trajectory. A later criteria change invalidates an existing review — the old accept applied to a different contract. We still hash the plan plus scoped files as an optional closeout seal; re-verify reports drift if those bytes change. Two limits remain: the seal is optional, and it lives in the same tree, so the worker can rewrite the seal file too. Drift is integrity, not an accept lock.

The probe-shaped analogue is now in the public envelope: optional criteriaHash (SHA-256 of the UTF-8 criteria bytes at probe time) and contractId. Helpers Get-SpineCriteriaHash and Test-SpineProbeCriteriaBinding fail an unbound PREFIX-OK — a summary line alone is not enough. The field does not self-accept; the acceptor still has to run the binding check. Five-field envelopes without those keys stay valid as unbound evidence. Contract § 3.1: agent-summary-probe-contract.md.

Happy to compare notes if you version the FCoP REVIEW record against a criteria hash while a Spine-style probe stays the runtime evidence blob.

Thanks, that clears up the boundary. Keeping the full chain in Git makes sense for portability, and I agree that role separation shouldn’t be described as cryptographic protection.

The criteriaHash change is particularly useful—it prevents an old review from surviving a change in acceptance criteria. When the criteria changes, do you keep the previous review as superseded history, or replace it entirely?

Thanks — superseded history, not replacement.

criteriaHash is a binding check, not a rewrite of the old review. The envelope from that run stays bound to the criteria bytes that existed then. After the criteria change, Test-SpineProbeCriteriaBinding fails against the new contract. The leftover PREFIX-OK is still evidence of a past process; it is not a current accept. We do not edit the old hash so it matches the new criteria. That would be forging the review.

Git is the ledger. Earlier commits still hold the old criteria text, probe token, named acceptor, and any closeout hashes. A third party can see what was accepted, against which contract, and that the contract later moved. That is the same inspectability reason we keep the chain in-tree.

The honest gap is current-state versus history. Plan completed and backlog done are working-tree checkboxes. They get rewritten when the work is re-reviewed under the new contract. We do not keep a first-class REVIEW row at HEAD with an explicit superseded location. If FCoP versions REVIEW against a criteria hash and parks the old record in a history location, that is the stronger protocol analogue. We have not shipped that shape.

Two limits, unchanged. History rewrite can still erase the superseded record; force-push and amend stay a human-only VCS role. And the field still does not self-accept: the acceptor has to run the binding check against the current criteria, not trust a leftover done.

That makes sense. The old review should remain valid history for contract A, not be edited to match contract B. At HEAD, a reader needs to see both “this was accepted then” and “it no longer authorizes the current state.” I’d keep the original review immutable and have the new review point back with a supersedes reference.

Thanks — immutable original, plus a pointer. That is the HEAD-visible form of superseded history.

The old review stays bound to contract A. We do not edit it so it matches contract B. The new review is a new accept under the new criteria. A supersedes reference on that new record is how a reader at HEAD sees both facts: it was accepted then, and it no longer authorizes the current state.

We have not shipped that shape. Git already does the analogue: a later commit does not rewrite the earlier one; it points at a parent. The leftover envelope and criteriaHash live in those earlier commits. What HEAD still lacks is a first-class REVIEW row with that pointer. Plan completed and backlog done remain current-state overwrite. A reader who only looks at the working tree sees the new checkbox, not the superseded receipt.

Two limits, unchanged. A pointer in the same tree is inspectability, not an accept lock — if the worker can write HEAD, it can rewrite supersedes too. And the field still does not self-accept: the acceptor has to run the binding check against the current criteria.

History is already there. What HEAD still needs is a cheap answer to one question: does the old acceptance authorize this tree? I’d derive that answer from immutable REVIEW records. If the supersedes chain is broken or the current criteria hash can’t be reproduced, the projection should be inconclusive, not completed. That still leaves write authority separate—the worker should not be able to sign the accepting REVIEW.

Thanks — cheap HEAD projection, fail closed.

History is already there. What HEAD still needs is a cheap answer to one question: does the old acceptance authorize this tree? We would derive that from immutable records we already keep — leftover envelopes and criteriaHash in Git — not from rewriting the old review. If the supersedes chain is broken, or the current criteria hash cannot be reproduced, the projection is inconclusive, not completed. That is the same three-way we already use: verified / not verified / inconclusive. Inconclusive is not a polite fail.

We still have not shipped a first-class REVIEW row at HEAD, nor a walker that answers that question from the working tree. Plan completed and backlog done remain current-state overwrite. Git parent is still the analogue: the leftover envelope lives in earlier commits. A reader who only looks at HEAD checkboxes does not get the projection.

What we did ship is the write-authority split you named. The worker may emit probe evidence. It must not sign the accepting review. A verify-only receipt check returns present, fail, or missing. Missing is the same class as inconclusive — not completed. In-tree YAML, backlog done, and seal JSON stay claims. A leftover PREFIX-OK still fails Test-SpineProbeCriteriaBinding against the current criteria. The acceptor has to run that check. Missing-receipt plus a named human accept is the valid closeout when no out-of-tree token exists. CI-signed attestation is the stronger half; we have not made it mandatory on every done.

Two limits, still. Location outside the worktree is policy, not a cryptographic store — the worker has no mint switch on the verify probe, but it can still write a file elsewhere. And until a cheap HEAD projection exists, Git history answers the authorize-this-tree question only if you look. Force-push and amend stay a human-only VCS role.