Goal Contract
Turn a rough task into a locked contract before any code is written. Adds the
missing third leg — cost — to the usual goal/acceptance/non-goals, so work
can neither drift off-objective nor balloon past its worth.
Why — the three legs
Every unit of work needs all three. Most setups have the first two and miss the third:
- Context — the situation (claude-mem, vault, ops already provide this).
- Direction — Goal + Acceptance + Non-goals.
- Cost — Budget + Tripwire. This is the leg usually missing. An agent has no
intrinsic price on its own effort, so without a cost leg it both wanders
(drift) and over-builds (a 1-hour job becomes a day). Same hole, two symptoms.
Workflow
- Take the rough statement of the next task/phase. Ask at most ONE clarifying
question, and only if the goal is genuinely ambiguous. Do not interrogate.
- Draft the contract using the template below. Acceptance must be observable;
non-goals concrete; budget real (not "as needed").
- Offer a distilled version — same contract, tighter. Let the user pick full or distilled.
- Emit the paste-ready LOCKED CONTRACT block. The user pastes it back to start work.
Contract template
- GOAL — one sentence, an outcome (not a method).
- ACCEPTANCE — 2–4 bullets, each observable/testable ("endpoint returns 200", "test passes", "phase < 200ms").
- NON-GOALS — concrete things NOT to touch this round (named files, refactors, features, "nice-to-haves").
- BUDGET — an effort ceiling. Pick what fits: ~time (e.g. ~1h), ≤N files, or "smallest change that meets acceptance".
- TRIPWIRE — if budget is hit, OR scope wants to expand, OR a refactor outside the goal tempts → STOP and report; don't grind.
Paste-ready output
=== LOCKED CONTRACT ===
GOAL: ...
ACCEPTANCE:
- ...
NON-GOALS:
- ...
BUDGET: ...
TRIPWIRE: At the budget — or any scope/non-goal temptation — STOP and report before
continuing. No grinding, no gold-plating.
RULES: Restate this contract before the first action. Check every step against GOAL +
NON-GOALS. Report against ACCEPTANCE at the budget and before declaring done.
=== Work only to this contract. ===
Dart ledger (ratified 2026-07-11)
Every locked contract lives in both places: locally (this machinery — the local
gates remain the only enforcement) and as a Dart task — the ledger and source of
truth for history (https://app.dartai.com).
- On a machine with the proctor hooks installed, this is automatic —
dart-ledger.py
creates the task at contract-lock (title ⛓ <GOAL>, tag goal-contract, status
Doing, full contract in the description) and closes it at gate-pass (Done +
outcome comment); yields and /disarm are commented, not closed. You add no manual step.
- Board resolution, first hit wins:
DART_BOARD env → Dart board: <Space>/<Board>
line in the project's CLAUDE.md → ~/.claude/goal-contract/dart-boards.json →
fallback Space/Richard-Jason-Projects with a [project] title prefix.
- No hooks here (other machine, headless)? Do it via Dart MCP:
create_task at lock
with the same conventions, update_task to Done + add_task_comment with the final
disposition at close. The ledger entry is part of the contract — not optional.
- Verify plumbing anytime:
python3 ~/.claude/hooks/goal-contract/dart-ledger.py --status
(token + board for cwd) or --selftest.
For the agent executing a locked contract
- Restate GOAL + NON-GOALS in one line before the first action.
- The smallest change that meets ACCEPTANCE wins. Extra tests, abstractions, or
cleanup not named in ACCEPTANCE are non-goals — adding them is overcorrection.
- Pre-existing brokenness is the one carve-out from "smallest change." If you walk
into a failing test, type debt, dead code, or a stale doc — in a file you're touching,
or otherwise cheap — fix it; don't note-and-leave. If the honest fix would blow the
budget/scope, that's a tripwire: STOP, surface it with the measured cost, and let the
user choose (expand now vs. explicitly defer). This is distinct from gold-plating
above: adding things nobody asked for is still overcorrection; leaving existing
brokenness noted-but-unowned is not allowed. "The linter will catch it" is banned.
Owned means you are taking the problem into this contract to fix it before done —
not that you appended a Follow-Up and walked away. If something truly cannot be fixed
inside this contract, call it deferred/operator-blocked, with owner + next action + proof,
and state whether the goal is blocked.
- Hit the budget, or tempted past scope? Stop and report — never unilaterally decide to go bigger.
Example
Rough input: "make the app faster, the startup is slow"
=== LOCKED CONTRACT ===
GOAL: Cut cold-start latency so the service is ready to serve in under a second.
ACCEPTANCE:
- startup < 1s (startup benchmark)
- endpoints unchanged (no behavior change)
NON-GOALS:
- the request cache, the installer, the dashboard, the migration runner
BUDGET: ~half-day, ≤4 files, behind the existing loader.
TRIPWIRE: if it needs a new subsystem or touches >4 files, STOP and report. No grinding.
RULES: Restate before starting. Check each step against GOAL + NON-GOALS. Report at budget and before done.
=== Work only to this contract. ===
1---2name: goal-contract3description: Spec a rough task into a locked work contract BEFORE starting — Goal, Acceptance, Non-goals, plus the Budget and Tripwire that cap effort. Use before beginning a coding phase, or when the operator says "spec this goal", "pre-goal", "lock a contract", "new phase", "write goals/acceptance/non-goals", or wants to prevent objective-drift and overcorrection (a 1-hour task ballooning into a day). Produces a paste-ready LOCKED CONTRACT block.4---56# Goal Contract78Turn a rough task into a **locked contract** before any code is written. Adds the9missing third leg — **cost** — to the usual goal/acceptance/non-goals, so work10can neither drift off-objective nor balloon past its worth.1112## Why — the three legs1314Every unit of work needs all three. Most setups have the first two and miss the third:1516- **Context** — the situation (claude-mem, vault, ops already provide this).17- **Direction** — Goal + Acceptance + Non-goals.18- **Cost** — Budget + Tripwire. *This is the leg usually missing.* An agent has no19 intrinsic price on its own effort, so without a cost leg it both **wanders**20 (drift) and **over-builds** (a 1-hour job becomes a day). Same hole, two symptoms.2122## Workflow23241. **Take the rough statement** of the next task/phase. Ask at most ONE clarifying25 question, and only if the goal is genuinely ambiguous. Do not interrogate.262. **Draft the contract** using the template below. Acceptance must be observable;27 non-goals concrete; budget real (not "as needed").283. **Offer a distilled version** — same contract, tighter. Let the user pick full or distilled.294. **Emit the paste-ready LOCKED CONTRACT block.** The user pastes it back to start work.3031## Contract template3233- **GOAL** — one sentence, an *outcome* (not a method).34- **ACCEPTANCE** — 2–4 bullets, each observable/testable ("endpoint returns 200", "test passes", "phase < 200ms").35- **NON-GOALS** — concrete things NOT to touch this round (named files, refactors, features, "nice-to-haves").36- **BUDGET** — an effort ceiling. Pick what fits: ~time (e.g. ~1h), ≤N files, or "smallest change that meets acceptance".37- **TRIPWIRE** — if budget is hit, OR scope wants to expand, OR a refactor outside the goal tempts → STOP and report; don't grind.3839## Paste-ready output4041```42=== LOCKED CONTRACT ===43GOAL: ...44ACCEPTANCE:45 - ...46NON-GOALS:47 - ...48BUDGET: ...49TRIPWIRE: At the budget — or any scope/non-goal temptation — STOP and report before50continuing. No grinding, no gold-plating.51RULES: Restate this contract before the first action. Check every step against GOAL +52NON-GOALS. Report against ACCEPTANCE at the budget and before declaring done.53=== Work only to this contract. ===54```5556## Dart ledger (ratified 2026-07-11)5758Every locked contract lives in **both places**: locally (this machinery — the local59gates remain the only enforcement) and as a **Dart task — the ledger and source of60truth for history** (https://app.dartai.com).6162- **On a machine with the proctor hooks installed, this is automatic** — `dart-ledger.py`63 creates the task at contract-lock (title `⛓ <GOAL>`, tag `goal-contract`, status64 `Doing`, full contract in the description) and closes it at gate-pass (`Done` +65 outcome comment); yields and `/disarm` are commented, not closed. You add no manual step.66- **Board resolution**, first hit wins: `DART_BOARD` env → `Dart board: <Space>/<Board>`67 line in the project's `CLAUDE.md` → `~/.claude/goal-contract/dart-boards.json` →68 fallback `Space/Richard-Jason-Projects` with a `[project]` title prefix.69- **No hooks here (other machine, headless)?** Do it via Dart MCP: `create_task` at lock70 with the same conventions, `update_task` to `Done` + `add_task_comment` with the final71 disposition at close. The ledger entry is part of the contract — not optional.72- Verify plumbing anytime: `python3 ~/.claude/hooks/goal-contract/dart-ledger.py --status`73 (token + board for cwd) or `--selftest`.7475## For the agent executing a locked contract7677- Restate GOAL + NON-GOALS in one line before the first action.78- **The smallest change that meets ACCEPTANCE wins.** Extra tests, abstractions, or79 cleanup not named in ACCEPTANCE are non-goals — adding them is overcorrection.80- **Pre-existing brokenness is the one carve-out from "smallest change."** If you walk81 into a failing test, type debt, dead code, or a stale doc — in a file you're touching,82 or otherwise cheap — **fix it; don't note-and-leave.** If the honest fix would blow the83 budget/scope, that's a tripwire: STOP, surface it *with the measured cost*, and let the84 user choose (expand now vs. explicitly defer). This is distinct from gold-plating85 above: adding things nobody asked for is still overcorrection; leaving existing86 brokenness noted-but-unowned is not allowed. "The linter will catch it" is banned.87 **Owned means you are taking the problem into this contract to fix it before done** —88 not that you appended a Follow-Up and walked away. If something truly cannot be fixed89 inside this contract, call it deferred/operator-blocked, with owner + next action + proof,90 and state whether the goal is blocked.91- Hit the budget, or tempted past scope? **Stop and report** — never unilaterally decide to go bigger.9293## Example9495Rough input: *"make the app faster, the startup is slow"*9697```98=== LOCKED CONTRACT ===99GOAL: Cut cold-start latency so the service is ready to serve in under a second.100ACCEPTANCE:101 - startup < 1s (startup benchmark)102 - endpoints unchanged (no behavior change)103NON-GOALS:104 - the request cache, the installer, the dashboard, the migration runner105BUDGET: ~half-day, ≤4 files, behind the existing loader.106TRIPWIRE: if it needs a new subsystem or touches >4 files, STOP and report. No grinding.107RULES: Restate before starting. Check each step against GOAL + NON-GOALS. Report at budget and before done.108=== Work only to this contract. ===109```