Skip to content

Contracts, Handoffs & Validation

trkr's ticket lifecycle borrows the verification architecture of long-horizon agent systems (notably Factory Missions): correctness is defined before code exists, every stage hands off through structured artifacts instead of prose, and the final judge never sees the source. The premise: verification density, not agent intelligence, is the bottleneck.

The validation contract

A spec's acceptance criteria are written as a validation contract — a numbered list of behavioral assertions with stable IDs:

VAL-1: `trkr new` with empty input exits 1 with a usage message
VAL-2: the created issue appears in the Backlog column

Each assertion is independently checkable — verifiable by a black-box tester running the artifact, without reading the source — and completed by exactly one ticket ("completes, not contributes to"). Where it lives:

  • Intake tickets (trkr new, trkr scope) carry a ## Validation contract section in the issue body.
  • Spec/diagnose stages commit contract.md (VAL lines only) beside spec.md/diagnosis.md; the hard gate refuses the stage without it.

The review stage verifies each assertion and reports it as a CRITERION: pass|fail — VAL-n: … line; the validate stage exercises each one against the built artifact.

The structured handoff

The implement stage's implementation.md is a handoff, not a diary. The hard gate mechanically requires these sections:

SectionContents
## ImplementedWhat was built, mapped to each VAL-n assertion
## Left undoneIn-scope work not finished, and why
## Commands runVerification commands with exit codes, verbatim
## Discovered issuesOut-of-scope problems, BLOCKING — … / NOTE — …, never fixed inline

Review cross-checks the handoff against the diff: an undisclosed gap is a BLOCKING finding.

Findings and the orchestrator

Review and validate print machine-readable findings:

FINDING: BLOCKING — src/poll.ts:42 — retry loop swallows the abort signal
FINDING: NOTE — src/poll.ts:60 — naming nit

When a turn fails and turns remain, the orchestrator — a triage agent, on by default ("orchestrator": false disables) — sorts every BLOCKING finding into one of three buckets:

  • retry — belongs to this ticket; the normal re-walk fixes it.
  • fix ticket — real but out of scope; filed as a new boarded issue with the evidence quoted, optionally blocking the parent via a native blocked_by dependency.
  • dismissed — not real; requires a specific written justification (≥ 20 characters — "low priority" is not one), posted on the ticket.

The orchestrator proposes; the daemon disposes. It can pull a human in early (block) but can never mark a verdict CLEAR, skip a gate, or grant extra turns. If its session fails for any reason, the deterministic re-walk/block behavior proceeds untouched.

The validate stage

With validate configured, every code workflow gains a terminal validate stage in a new Validate column — a black-box, user-testing validator in the Factory mold. Its isolation is mechanical, not prompt-deep: the daemon builds the artifact in the ticket's worktree (validate.build), then materializes a sandbox at .trkr/validate/<issue> containing exactly two things —

  • contract.md — from the committed artifact, or the issue body's ## Validation contract section as fallback;
  • artifact/ — the built outputs named by validate.artifacts.

The session runs in the sandbox, with no worktree, no diff, no implementer notes, and no issue thread. It installs the artifact the way a user would, exercises every assertion, and reports CRITERION lines plus a VERDICT. The daemon — not the validator — commits validation.md back to the ticket's branch as the paper trail. No contract, no build, or a missing artifact refuses the stage and feeds the turn loop; nothing runs against a partial sandbox.

json
{
  "validate": {
    "build": "pnpm build && npm pack",
    "artifacts": ["*.tgz"]
  }
}

What stayed mechanical

Column moves, hard gates, turn budgets, plateau detection, the Blocked-on-a-human escalation, and no-auto-merge are unchanged and remain deterministic daemon code. The agents decide content (what to build, what a finding means); the daemon decides process.