GO TO A SECTION

↑ ↓ to navigate · Enter to openExplore all sections →
FDEInterviews logo
Lesson 2 of 44 · FreeCourse syllabus
Course outline · choose a lesson

Write the acceptance contract before the first agent run

Define the observable customer outcome, allowed actions and stop conditions before a run. Bind acceptance to a specific input and artifact revision so later edits invalidate stale proof.

18 MIN

By FDEInterviews · Updated

TL;DR: Write acceptance as a checkable relation between a task, its input revision, the allowed changes and the evidence produced. Treat a passing result as version-bound evidence that can become stale when the task or artifact changes.

Where you are. You have separated the model from the harness; now define what the Northstar coding worker is allowed to finish.

From request to observable claim

The supplied request is small: the procurement workbench should require buyer review when an order total reaches the review limit. Today, an order exactly at the limit skips review. Northstar's synthetic rule uses integer cents and says total_cents >= limit_cents. The fixture limit is 100000 cents. There is no currency conversion or tax calculation in this exercise.

“Fix the approval bug” is too vague to hand to an autonomous worker. It does not say whether an order exactly at the limit needs review, which files may change, or what counts as a finished repair. Given that much room, a worker could change the limit, weaken the test, or add a label that looks right while the API behaves as before.

A useful task contract names the target behavior and rules out those shortcuts. For task T-17, the candidate may change the threshold implementation and its explanatory UI text. It may not change the acceptance cases, the configured limit, the user's role or the procurement service's authorization checks. The output is a patch for review; deployment is a separate operation.

The contract should also name the legitimate reasons to stop. Missing dependency access is an environment block, and an ambiguous purchasing policy calls for a clarification request. Neither should be recorded as “passed with assumptions” just to make the task list look tidy.

Evidence bound to what was tested

Keep a small evidence record:

FieldSupplied valueWhy it exists
Task and specificationT-17, spec-3Identifies the behavior being requested
Candidate artifactc17Identifies the exact patch under test
Fixture setthreshold-2Fixes input cases and expected outputs
Environmentpython-local-1States where the check executed
Verifierthreshold-check-2Identifies the acceptance logic
Resultpass or failRecords an observed check, not a plan to check

These labels are illustrative identities. A real release record needs resolvable revisions or content digests and a trusted writer. A digest detects a byte difference when compared with a trusted value; it does not establish that whoever supplied the value was honest.

Changing the candidate, fixtures or task invalidates the old evidence for the new result. Task T-17 at spec-3 → Candidate c17. Candidate c17 → Protected verifier and fixed fixtures. Protected verifier and fixed fixtures → Evidence for c17 / spec-3. Evidence for c17 / spec-3 → Still current?. Still current? → Ready for patch review when yes. Still current? → Recheck changed artifact or contract when no. New code, fixture or specification → Still current?. Evidence belongs to one revisionSTATETask T-17 at spec-3STATECandidate c17CHECKProtected verifier and fixed fixturesSTATENew code, fixture or specificationSTATEEvidence for c17 / spec-3DECISIONStill current?HOLDRecheck changed artifact or contractCHECKReady for patch reviewyesnoChanging the candidate, fixtures or task invalidates the old evidence for the new result.

A boolean checklist cannot express whether a result is still current, and that is the decision that matters later. A task accepted last week may need revalidation after a new patch changes a shared dependency. Keep the earlier evidence as history and change the task's current status. Do not rewrite history to say it never passed, and do not treat the old pass as proof for the new revision.

Checks that resist shortcuts

The boundary cases below isolate the equality defect. They are necessary for this task, but they are not a complete procurement test suite. Negative amounts, unsupported currencies and role checks belong to other explicit contracts.

python
cases = [(99999, False), (100000, True), (100001, True)]

def needs_review(total_cents, limit_cents):
    return total_cents >= limit_cents

observed = [needs_review(amount, 100000) for amount, _ in cases]
expected = [answer for _, answer in cases]
assert observed == expected
print(observed)

The output is [False, True, True]. Keep the acceptance cases outside the worker's editable files. A worker can add its own unit tests, but those tests cannot be the sole authority for accepting its patch. The independent runner should also report a failing process exit or missing result as a failure to obtain evidence.

The shortcuts named earlier are concrete enough to run. The table takes five candidate patches, including the three shortcuts, and runs each against the boundary cases, against the same cases at a second limit, and against a check that the protected files are untouched.

Candidate patchCases at 100000Cases at 150000Protected files untouchedVerdict
A. Compare the total to the configured limit with >=passpasspassaccept
B. Compare the total to the literal 100000passfailpassreject
C. Keep > and edit limits.toml to 99999passfailfailreject
D. Rewrite the acceptance cases to expect >failfailfailreject
E. Change the review badge text onlyfailfailpassreject

Every cell comes from running the five candidates as predicates on the cases at both limits and comparing their edited files with the protected list. D fails the cases because the runner reads its own copy of them, not the worker's edited one.

Row B is the one to study. A patch that compares the total to the literal 100000 passes every boundary case and edits nothing protected, so a contract built only on the original fixture accepts it. It would fail later, when the customer moves the limit, which is the "change the input" exercise below arriving as a production incident. The fix costs one loop: run the same three cases at a second limit during acceptance, so the constant fails today.

python
def needs_review(total_cents, limit_cents):
    return total_cents >= limit_cents

for limit in (100000, 150000):
    cases = [(limit - 1, False), (limit, True), (limit + 1, True)]
    assert [needs_review(t, limit) for t, _ in cases] == [e for _, e in cases]
print("ok")

This prints ok; swap in the hard-coded version and the assertion fails on the second limit. Row C shows the other half of the contract earning its place. Moving the configured limit to 99999 and keeping > passes the cases at 100000, and only the protected-file check rejects it, which is why protected surfaces are a line in the contract rather than a convention.

A small patch does not earn a wider scope. Changing one comparison character can alter approval behavior for every order at the boundary, so scope follows the operation and deployment the change affects, not the number of edited lines.

Do this before moving on

Deliverable: write a six-line acceptance contract for T-17: outcome, allowed edits, protected surfaces, required evidence, completion boundary and blocked conditions. Use the supplied amounts and exact fixture limit.

Worked review. A satisfactory contract requires review for totals of 100000 cents or more, permits only the threshold implementation and explanatory UI text, protects the limit and evaluator, requires the three boundary results plus a browser observation of the review state, finishes at a review-ready patch, and blocks on unavailable checks or unclear policy. The browser observation tests the wiring; the numeric cases test the predicate.

Change the input. The customer raises the limit to 150000 cents after the patch passes. Keep the old result as historical evidence, update the contract and run cases at 149999, 150000 and 150001. A hard-coded implementation that only works at 100000 must fail. A generic comparison should produce the same three booleans at the new boundary.

Kept this way, a task ledger stays useful across sessions: it records what still has to be proved, so the next worker does not have to infer it from an optimistic progress summary.

Go deeper

Key takeaways

  • Bind acceptance evidence to the specification, artifact, fixtures and verifier.
  • Preserve past results while invalidating their use for a changed revision.
  • Protect acceptance checks and state the boundary between a patch and deployment.

Check yourself

Answer before you look. Recalling it is what makes it stick; recognising it does not.

  1. 1Candidate c17 passes. A later patch changes the same calculation. Which result is justified?

  2. 2Why does the changed-limit exercise include 150000 exactly?

Sign in free to save your progress and return to this lesson.

Back to the course syllabus