Codex vs Claude

Handing Off a Task: Write the Check Before You Write the Brief

David Guzenburg/ / 11 min read

If success is looks right to me, you have not delegated the task. You have deferred it.

workflowspecificationtestingquality gates

Autonomy is not the constraint; verifiability is

The framing around asynchronous agents is usually about capability: can it work unsupervised for long enough, does it recover from failures, will it come back with something complete. Those are real engineering questions and they are not what decides whether a given task should be handed off.

The deciding property belongs to the task, not the agent. A task can be handed off when its completion is checkable without you having watched the work. If success is a passing test suite, a matching snapshot, a schema that validates, a benchmark that improved — hand it off. If success is "looks right to me", you have not delegated the task, you have deferred it.

That single test explains most of the variance in how well asynchronous agents work for a team, and it explains it better than model capability does.

What a done condition actually looks like

Not a description of the outcome. A command, and what its output must be.

"Fix the flaky test in the checkout suite" has no done condition — flaky means it sometimes passes, so one green run proves nothing. "Make npm run test:checkout pass a hundred consecutive times" is a done condition, it is mechanically checkable, and it happens to force the right approach, because you cannot satisfy it by rerunning until lucky.

The act of writing the condition is most of the value. It frequently reveals that you did not know what you were asking for, which is much cheaper to discover before an hour of work than after.

The three shapes that hand off well

Mechanical change with an existing safety net. A rename across two hundred files, a codemod, an API migration, a dependency bump. The test suite is the oracle and it already exists.

Bug with a reproduction. A failing test that should pass. The condition is unambiguous, the work is bounded, and the result is self-demonstrating.

Well-specified addition following an established pattern. A new endpoint like the eleven existing endpoints, a new provider implementation of an interface. Success is the new tests passing and the existing ones still passing.

Everything these have in common: the specification was complete before the work started, and something other than a human's impression can tell you whether it was met.

The three that do not

Anything where the requirement is discovered by seeing something wrong. Anything touching a shared contract, where correctness depends on knowledge of how other teams use it. Anything whose acceptance criterion is aesthetic or strategic — "make this API nicer", "simplify this module".

These fail asynchronously not because the agent produces bad work but because the work cannot be evaluated cheaply. You get a large diff, no basis for judging it, and a review that costs more than doing the task would have.

The tell

If you cannot write the acceptance check before starting, the task is not ready to hand off. That is not a limit on the agent; it is information about the task, and it usually means the specification work has not been done.

Write the check as a file, not as a sentence

The strongest version of a done condition is a script committed alongside the task, which the agent runs and which CI runs identically.

#!/usr/bin/env bash
# The definition of done for this handoff. Both the agent and CI run it.
set -euo pipefail

npm run typecheck
npm run test -- --coverage --changed
npm run lint

# The specific invariants this task is about:
! rg -n "legacyAuthMiddleware" src/ --glob '!*.test.ts' \
  || { echo "legacy middleware still referenced in src/"; exit 1; }

rg -q "requireSession" src/middleware/auth.ts \
  || { echo "new middleware not wired up"; exit 1; }

echo "done conditions met"

The two custom assertions are the part worth copying. Generic checks say the codebase is healthy; task-specific assertions say this task was actually performed. Without them an agent can satisfy the suite by leaving the old path in place beside the new one, which passes everything and completes nothing.

Bound the run, or it will find a way to keep going

An unsupervised agent given an unbounded problem will work. It will keep producing plausible activity long past the point where progress stopped, because there is nothing in the loop that recognises futility.

So every handoff needs limits: a wall-clock or token budget, and an instruction about what to do when it is reached. "If you have not met the done conditions within the budget, stop and write what you tried, what the remaining failure is, and what you would try next" turns a runaway into a useful report. The failure modes of long unsupervised runs are the subject of long-horizon agent runs.

Failure has to be a first-class outcome

The most common flaw in async setups is that only success is designed for. When the agent cannot finish, it produces either a half-done branch with no explanation or an optimistic summary of work that does not meet the criteria.

Ask for the failure artefact explicitly and treat it as a valid result. A handoff that comes back with "I could not do this, here is why, here is what I ruled out" is genuinely valuable: it is a bounded investment that produced information, which is the normal outcome of a lot of engineering work and should not be treated as a defect.

The specification cost is real and it moves

Asynchronous work does not remove effort, it relocates it. Instead of supervising for an hour you spend twenty minutes writing a specification and a done condition, then five reviewing. That is a good trade for the right task and a bad one for a task you could have finished in fifteen minutes.

The break-even is roughly: if writing the specification takes longer than doing the work, do the work. That sounds obvious and it is routinely violated, because specifying feels like managing and doing feels like grunt work, and the incentives point the wrong way.

What the returned work has to carry

You did not watch the session, so the pull request has to stand in for it: what was attempted, what was decided, what the agent could not verify, and what it deliberately left out. Without that, review starts from nothing and takes far longer than the work merited.

The specifics of what that artefact should contain — and why an agent's pull request needs to be shaped differently from a person's — are in reviewing asynchronous agent pull requests.

Parallelism is where this actually pays

One handoff saves you an hour of supervision. Five concurrent handoffs is a different proposition, and it is the reason the model is interesting rather than merely convenient.

It is also where the constraint moves to you, since five completed branches arrive needing review from the same person. That queue, and how quickly it stops being a benefit, is the subject of parallel agents and the serial reviewer.

Start with the tasks you already dislike

A practical entry point. The best first candidates are the mechanical tasks that have a test suite and that nobody wants: the dependency bump that touches forty files, the deprecation warning cleanup, the test migration after a framework upgrade.

They have real done conditions, low stakes, and an existing safety net. They also give you a calibration sample — a handful of handoffs where you can compare the specification you wrote against the result you got, which is how you learn what your specifications are missing.

The habit that makes the whole thing work

Write the check first. Before describing the task, write the command that will tell you it is done. Everything else follows from that: the specification gets sharper because it has to satisfy a concrete test, the agent has an unambiguous target, the review has a starting point, and failure is detectable rather than argued about.

Teams that find asynchronous agents unreliable are usually handing off tasks with no done condition and evaluating the results by impression. The model is not the variable. The check is.

Scope the change surface, not just the outcome

A done condition says what must be true at the end. It says nothing about what the agent was allowed to touch on the way, and unsupervised work has a tendency to expand: a task about one module comes back having also reformatted an unrelated file, upgraded a dependency and fixed two things nobody asked about.

Each of those may be an improvement and collectively they wreck the review, because the diff no longer corresponds to the task. State the boundary explicitly — which directories are in scope, that unrelated fixes should be listed rather than made — and the returned branch stays reviewable.

One task, one branch, one concern

Related, and worth being rigid about. The temptation with asynchronous work is to batch: hand over four related tasks and get one branch back, because it seems more efficient.

It is not, because review is the bottleneck and a branch containing four concerns cannot be partially approved. One of them being wrong blocks the other three. Four branches, each independently verifiable and mergeable, get three of them merged today and one revised, which is strictly better even though it looked like more overhead going in.

What to do with the result you do not trust

Sometimes work comes back meeting every condition and you still do not believe it. That is a legitimate reaction and worth acting on rather than suppressing, because the done conditions are a floor and not a proof.

The productive response is to find the specific thing that is bothering you and turn it into another check, rather than either rejecting the work on a feeling or accepting it against your judgement. Usually the unease points at a case the conditions did not cover, and adding that case improves every future handoff of the same shape. Distrust that produces a new assertion is worth more than distrust that produces a longer review.

The queue is the thing to watch

One organisational note. Once handoffs work, the natural response is to create more of them, and the constraint quietly moves from how much work can be produced to how much can be absorbed.

The signal to watch is the age of the oldest unreviewed branch. While that number stays under a day the system is healthy. When it reaches a week you have converted a productivity gain into a backlog, and the branches are now rotting against a moving main branch, which means the eventual review is harder than it would have been on the day the work arrived.

The other thing worth measuring is your own hit rate. Of the last twenty handoffs, how many came back meeting the conditions on the first attempt? If the number is low, the usual cause is not the agent but underspecified conditions, and the fix is to look at the five that failed and ask what assertion would have caught each one before it was handed back.

Handoff changes what your repository has to explain

A final structural point. Supervised work tolerates a repository that explains itself poorly, because you are present to fill the gaps: you know which of the three test commands is the real one, that the fixtures directory is stale, that nobody uses the makefile any more. An agent working unsupervised has none of that and will discover it by trying things.

So the practice of handing work off exerts pressure on your repository to become self-describing, and that pressure is worth leaning into rather than routing around. Every time you write a done condition and discover the check does not exist as a command, you have found something that was costing new team members a day and nobody had noticed, because everyone who knew was already inside the building.

That is the quiet return on this practice, and it is larger than the per-task saving. The specification work compounds; the individual handoffs do not.

Takeaway

A task is ready to hand off when its completion is machine-checkable without you having watched the work — not when the agent is capable enough. Write the check as a committed script with task-specific assertions, not just a generic test run, because generic checks pass on work that was never done. Bound every run with a budget and an instruction for what to do when it is hit, treat a well-explained failure as a valid outcome, and if writing the specification would take longer than the task, do the task.

Keep reading
Codex vs Claude

Debugging Together: You Hold the Hypothesis, It Does the Volume

Debugging is the task you cannot write a done condition for. The division of labour that works, instrumentation at a scale people will not do by hand, and defending against the coherent explanation that happens to be wrong.

ChatGPT vs Grok vs Claude Code

A Test Loop Is a Property of the Environment, Not the Model Name

Claude Code normally has the shortest path from edit to native test suite. ChatGPT and Grok can reason over errors and may execute code in supported.

Workflow Architecture

Test-Driven Loops: Giving an Agent a Definition of Done

Why TDD fits agent workflows, who should own each step of the cycle, the instruction that makes it runnable, and why test speed is the binding constraint.

Codex vs Claude

Pairing With an Agent: Granularity Is the Whole Game

A check-in is valuable before a commitment and a tax during implementation. Where the real decision points are, the sentence that fixes ambiguous cases, and why the most useful conversation usually happens at the end.

← Strict Paths or Adaptive File Placement?  ·  Automatic Context Compaction or Visible Manual Control? →

All codex vs claude articles  ·  Every article