Skip to content

Session Goals

/goal gives a session a success condition. After each agent run, an evaluator judges whether the condition is satisfied. If it isn’t, the agent is automatically prompted to keep going — so you can state an outcome once instead of typing “continue” a dozen times.

/goal "all tests pass and the README is updated" --max-turns 20 --max-cost 5

  1. You set a goal. The condition is sent to the agent immediately as its first directive.
  2. The agent works — reading, editing, running commands — for as many tool calls as it needs.
  3. When the agent finishes a run, the evaluator checks the condition.
  4. Not met → the evaluator’s reasoning is fed back and a new run starts automatically.
  5. Met, or a budget is exhausted → the goal deactivates and control returns to you.

The loop pauses itself when other work is in flight: if background subagents are running or messages are queued, /goal waits rather than talking over them.


EvaluatorHow it decidesCost
llm (default)Appends a judge question to the session’s own conversation and asks the session’s model for a yes/no with a reasonOne short call, with the conversation served from the provider’s prompt cache
keywordChecks whether any --keyword appears in the run’s outputFree, local

The LLM evaluator is told to judge on evidence — command output, test results, file contents — rather than the agent’s claim that it finished. Ending a run with real output (“42 passed”) resolves a goal; ending it with “the tests should pass now” does not.

Rather than assembling a separate transcript, the evaluator re-sends the exact context your session just sent — same system prompt, same tools, same messages — with the judge question appended as one final user message. Providers cache on an exact prefix, so that conversation is served from cache and only the appended question is billed as new input. It also means the judge reads the real conversation at full fidelity, and nothing is shared with a provider your session wasn’t already using.

/goal "docker image builds" --evaluator keyword --keyword "Successfully built"

An uncertain verdict — evaluator model unreachable, no credentials, unparseable reply — never auto-continues. The loop stops and hands control back to you, so a broken evaluator can’t spin a session.


Budgets are guardrails, not targets. The first one to trip deactivates the goal.

FlagCounts
--max-turns NCompleted agent runs
--max-tokens NCumulative tokens across every LLM round-trip, including evaluator calls
--max-cost NCumulative USD across every LLM round-trip, including evaluator calls

Budgets are enforced mid-run too, so a runaway tool loop can’t blow past a token or cost ceiling while waiting for a run to end.


FlagDefaultPurpose
--max-turns NnoneStop after N completed agent runs
--max-tokens NnoneStop after N cumulative tokens
--max-cost NnoneStop after N cumulative USD
--evaluator llm|keywordllmWhich evaluator decides
--keyword "text"noneSuccess string for the keyword evaluator (repeatable, required by it)
--every N1Evaluate once every N completed runs
--min-turns N1Wait N completed runs before the first evaluation

Budget values must be greater than zero. Text that isn’t a recognized flag is treated as part of the condition, so /goal "fix the --dry-run handling" works as written.

CommandEffect
/goal or /goal statusShow the active goal, counters, and budget
/goal clearCancel the active goal (aliases: stop, off, cancel, reset, none)

An active goal appears in the TUI status bar and as a badge in the web UI session header, showing the completed run count (against --max-turns if set) and the latest evaluator reason.


Defaults for every goal live under goal in ~/.pizzapi/config.json, or in the web UI under Runner Settings → Fast Model:

{
"goal": {
"evaluatorModel": "anthropic:claude-3-5-haiku-20241022",
"evaluatorMaxTokens": 512,
"evaluateEveryNTurns": 1,
"minTurnsBeforeEvaluate": 1
}
}

There is deliberately no evaluator model picker. The evaluator runs on your session’s model so it can reuse the cached conversation; choosing a different one would silently give that up.

evaluatorModel remains as an advanced escape hatch for the rare case that needs it — pinning a judge (for example a local, fully private model) opts out of cache reuse and sends a standalone, truncated transcript instead. If a pinned model has no working credentials, the evaluator falls back to the cheapest authenticated one rather than silently switching itself off.

Evaluator spend is tracked separately and shows up in the Usage dashboard alongside normal session usage.

See Configuration for the full option reference.


By default the LLM evaluator sends your conversation to the same provider and model the session is already using, so evaluation introduces no new data exposure.