Set a completion condition. Amplifier keeps taking turns until it's actually met.
/goal <condition> — what "done" means, in your words.
Normal Amplifier turn. Same session, same tools, nothing special.
A separate cheap model reads the condition plus the conversation and answers one question: is this done?
The next turn starts automatically, with the evaluator's reason handed in as guidance. No keystroke.
The goal clears and control returns to you.
Auto mode removes per-tool prompts. /goal removes per-turn prompts.
/goal ships in Claude Code (v2.1.139+) and in OpenAI's Codex CLI. Developers arriving at Amplifier from either tool already type it. Until now, Amplifier didn't answer.
Continuation happens in the conversation you're already in — not a new session tree. This is the part a /goal-experienced user notices first.
The judge reads the conversation. It doesn't run commands or read files on its own — matching the reference behavior.
No percentage, no burn-down. The evaluator's last reason is the entire progress signal — because a synthesized number would be a guess presented as a measurement.
Design principle we wrote down and held to: parity first; the name is the contract.
Going in, the obvious read was that /goal exists to keep a long-running agent anchored to intent. It doesn't. Anthropic's stated rationale is narrower and more honest: removing the human from the per-turn loop.
That reframing changed the build. We cut the anchor/drift machinery entirely — the reference feature doesn't have it, and a user coming from Claude Code has no mental model for it, so its absence is invisible to their parity judgment. Drift work is parked, not dead. It's a differentiator, not a parity requirement.
“You are not the architect anymore. You are the carriage return.” — community framing of what /goal is escaping
# set a goal and start working /goal <condition> # same, with a hard turn cap /goal --max-turns 20 <condition> # status: condition, turns used, last verdict /goal # stop (aliases: stop, off, reset, none, cancel) /goal clear
amplifier run --mode single \ "/goal all tests in tests/ pass with pytest -q exiting 0"
/clear also clears an active goal. Ctrl-C stops a goal run — once for graceful, twice for immediate. An evaluator failure clears the goal and reports the error, rather than defaulting to “satisfied.”
Goal set: count upward from 1, saying exactly ONE number per response... (max 6 turns) ⟳ goal: turn 1/6 — The assistant has only said "1" so far and must continue through 4... ⟳ goal: turn 2/6 — The assistant has only counted to 2 and must still say 3 and 4... ⟳ goal: turn 3/6 — The assistant has only counted to 3; it must still say 4... ✓ goal achieved: The assistant counted one number per response — 1, 2, 3, and finally 4.
Four turns, one keystroke. The reason on each line is what gets handed to the next turn as guidance — it is both the progress display and the steering signal.
| Claude Code | Amplifier | |
|---|---|---|
| Bounding a run | No mechanical cap. You write “or stop after 20 turns” into the condition text. | --max-turns 20 — a flag, parsed and enforced in Python. |
| Who enforces it | A model judges whether 20 turns have passed. | An integer counter. It counts. |
| Bad input | Prose. Interpreted. | Fails loud — no goal set, no turn runs. |
| On trip | — | ⚠ goal: hit turn cap (20) — stopping. Announced, never silently truncated. |
/goal --max-turns abc do something
→ Goal not set: Invalid --max-turns value: 'abc' -- must be a positive integer.
Everything else is parity by design. This is the one place we chose to diverge, because a sentence asking a model to respect a budget is not a budget.
The loop landed in the CLI app layer first. It worked — interactively. But headless was not merely unfinished, it was impossible: execute_single never enters the REPL code path the loop was living in.
The fix was to ask a better question — whose loop is this? — and move it down into the streaming orchestrator, which the ecosystem already calls THE control surface for the execution loop.
| App layer (first attempt) | Orchestrator (shipped) | |
|---|---|---|
| Interactive | ✅ proven | ✅ proven |
| Headless --mode single | ❌ impossible | ✅ proven |
| Other apps (daemon, agent, web) | ❌ N reimplementations | ✅ free |
| Diff at spike time | 290 lines in the app | 213 lines in the module + ~15 in the app (382 as shipped, after the review fixes below) |
The app keeps only the /goal command. The orchestrator owns the loop. With no goal set it's a single pass-through call — zero behavior change.
A bare print() from the orchestrator would have injected text into amplifier-agent's JSON stdout protocol channel — and vanished into journald for daemon and web hosts.
Fixed: an orchestrator:goal_progress event, rendered by a CLI hook.
orchestrator:complete was firing N times per user turn — silently corrupting turn counts in every persisted session, for anyone counting them.
Fixed: goal_turn / goal_final discriminators.
Evaluator calls bypassed cost tracking, approval hooks, and rate limiting. Real spend that no accounting layer could see.
Fixed: evaluator calls emit PROVIDER_REQUEST.
All three were invisible from the CLI, where the feature “worked.” That one ecosystem question was worth more than any amount of additional testing.
The obvious way to exercise a continuation loop is to give it something big. It didn't work. A capable agent one-shot a 1,303-line SQL engine with 155 passing tests — zero continuations. Downgrading the model to Haiku didn't help either.
| Task | Model | Continuations |
|---|---|---|
| URL shortener (28 tests) | Sonnet | 2 — and from an ambiguous condition, not difficulty |
| Lisp interpreter (67 reviewer-authored tests, TCO) | Sonnet | 0 |
| SQL engine (155 tests, 1,303 lines) | Sonnet | 0 |
| Lisp interpreter | Haiku | 0 — 43 LLM calls vs Sonnet's 16, all inside one turn |
| SQL engine | Haiku | 1 |
The insight: a weaker model produced more iterations inside a turn, not more turns. Continuations come from ambiguity or premature stopping — never from difficulty. So we rebuilt the test around agents that stop early. Zero continuations on well-specified work is the gate succeeding, not failing to engage.
An agent told to implement one function at a time and report after each stopped after 1 of 6.
“only the slugify function has been implemented so far; the other 5 functions remain as stubs.”
Pushed back → driven to completion. Verified by running the code: all 6 present and correct.
A spec required 12 functions plus a per-function changelog policy. The agent did all the obvious work and returned.
“The CHANGELOG.md contains all 12 function entries with descriptions, but they are all under a single dated entry (2026-07-30) rather than having ‘a distinct dated entry for every one of the 12 functions’ as required by the goal condition.”
A detail a human reviewer would very likely wave through. Verified after the fix: 12 functions, 12 distinct dates.
Both verified independently — by running the resulting code, not by trusting the agent's report. A third trap that demanded real executed test output produced zero continuations: the agent behaved correctly, so there was nothing to catch. That's the right outcome too.
The evaluator is strict by design — it will not call an ambiguous condition satisfied. A vague condition either loops against work that's already finished, or accepts a shallow claim. Both are avoidable by writing three things down.
Something checkable. “every call site compiles” — not “make the API good.”
Name the command exactly: cd ./app && pytest -q exits 0 with ≥20 tests passing. “Tests pass” makes the evaluator guess.
Negative constraints decay first over a long run. CONSTRAINT: do not modify files outside src/.
One run had its pytest command mangled by shell escaping, so the condition never named the check. The agent finished the real work on turn 1 — 28 tests passing — and the evaluator correctly refused to accept the ambiguous condition, pushing back twice on work that was already done. Vague conditions cost money. That's why the docs lead with this.
We tried to reproduce a real incident from session history: an agent that polled sleep 900 262 times over 65+ hours, succeeding every single time and never progressing. /goal cannot touch that — and we'd rather say so than let someone trust a guard that isn't guarding.
Reproduced with a job-status script that always exited 0 and never reached 100%: it was polled 59+ times inside a single turn with --max-turns 8 set. The cap never fired — because the turn never ended, so the evaluator never got a vote.
| Inner loop (tool iterations) | Outer loop (/goal) | |
|---|---|---|
| Bound | Unlimited by default | --max-turns |
| Guards against | nothing — runaways live here | premature completion |
| Evidence | cap demonstrably cannot reach it | catches it cleanly, with specific reasons |
--max-turns bounds goal continuations, not work inside a turn. For unattended runs, bound the process externally too — a wall-clock timeout, a CI job limit. This is written into docs/GOAL_COMMAND.md under “What the cap does NOT bound,” rather than left for someone to discover the hard way.
Feature commits landed 2026-07-29 across amplifier-app-cli and amplifier-module-loop-streaming. The design and validation record — including the failed hypotheses — is a 584-line design doc in the working repo. All four feature commits are authored to the Amplifier bot account, so git records no human author for them; the work ran in a single workspace, and the ecosystem review that found the three defects was a separate pass over it.
Data as of: 2026-07-30 · Feature status: Active — merged and shipped.
Gaps and caveats, stated plainly. The terminal transcript on the “Real output” slide is verbatim from a released-build run supplied for this deck; the working repo retains an earlier pre-cap variant of the same demo, not this exact capture. The 59+ poll count and the 262/65-hour figures are floors reported in the design record, not exhaustive counts. Continuation counts come from a modest number of runs, not a statistically powered sample. No performance or cost-savings benchmark was run, so this deck makes no speed or savings claim. This deck was assembled from the local working repository directly; no separate research agent run backs it.
amplifier run --mode single \ "/goal all tests in tests/ pass with pytest -q exiting 0"
docs/GOAL_COMMAND.md in microsoft/amplifier-app-cli — including how to write a condition that converges, and what the cap does not bound.
microsoft/amplifier-app-cli #240 — merged, 75279e8. The /goal command and the progress renderer hook.
microsoft/amplifier-module-loop-streaming #30 — merged, d4bf50d. The continuation loop itself.
Works in interactive chat, headless, and in any app that mounts the streaming orchestrator. With no goal set, nothing changes.