Cost and caps
Every iteration reads what Claude Code reported spending, in dollars, and how many turns it took. Each iteration’s status line shows them, with its duration, and the summary line ends with the run’s total:total_cost_usd in its result event), which can differ from your bill. To read it, each iteration runs claude -p with --output-format stream-json, with or without --verbose; an older Claude Code that prints no result event still works, without the cost.
Two flags cap a run:
--max-turns <n>passes--max-turnsto everyclaude -p. An iteration that hits it stops with an error,Claude Code reached the --max-turns limit, shown on its status line and in the progress file. Three in a row end the run like any other errors, and so does one on the last iteration (exit code 1).--max-budget-usd <amount>caps the whole run. Each iteration’sclaude -pgets what’s left as--max-budget-usd, and the loop stops before an iteration once less than X, so the run stops rather than overshoot. That run ends with statuserror, since the iteration failed, not—max-budget-usd reached. Without—max-budget-usdsuch an iteration showscost unknownand adds nothing to the run's total, which then readsat least $X. A—paralleltask stopped or killed before itsresult` event shows the same way.
--sandbox and --parallel (see Run tasks in parallel).
What each task cost
Each iteration also adds its cost and duration to the task it started on, sotaskgraph show tells you what a task has cost across every run:
--id names, or the one taskgraph next picked when the iteration started; an iteration on a subtask counts on its parent task. A --parallel task counts its one Claude Code run the same way. With --merge, the Claude Code run that resolves the task’s merge conflict adds its cost and time to the task too, but not an iteration. An iteration whose Claude Code never started (not installed, say) adds nothing. One that reported no cost adds its time and the budget it was given, if any; a --parallel task that reported no cost adds only its time. Presets that pick their own work, such as linting, record nothing.
show --json (and get_tasks with fields: 'spend') returns it as an optional field, "spend": { "usd": 1.24, "durationMs": 725000, "iterations": 3 }. Only loop writes it: AI commands never see it in their prompts, and update-task, update and expand keep it. The merge driver merges it like any other field: when two branches both ran loop on a task, its spend is a clash for you to settle, since with squash merges and rebases the driver can’t tell new spend from spend already counted.
Model and effort by complexity
When the task an iteration works on has a complexity score (1 to 10),loop picks the model and effort for that iteration from it, so a one-line fix doesn’t cost as much as a refactor. The task is the one --id names, or the one taskgraph next picks with the default and tdd presets. It passes --model and --effort to claude -p, which take precedence over the model and effortLevel in your Claude Code settings:
parse-prd gives each task it writes a score, so a freshly planned project (including one taskgraph go planned) gets these bands with no extra step. A report from analyze-complexity wins over that score for the tasks it covers. Each iteration reads the score as it is then, so a task re-scored during the run (by scope or analyze-complexity) runs at its new band from the next iteration.
A task without a score (a subtask, a task added some other way that analyze-complexity hasn’t scored, or a preset that picks its own work such as linting) runs as before, with Claude Code’s own model and effort. The status line and the progress file show what each iteration ran at, next to its cost:
--model <model> runs every iteration on that model (an alias such as opus, or a full model name). It replaces the band’s model only; the band’s effort still applies.
Set your own bands under loop.bands in .taskgraph/config.json. A task goes into the first band whose maxScore its score doesn’t exceed; a band without model or effort leaves that one to Claude Code. effort is one of low, medium, high, xhigh or max:
maxScore runs with Claude Code’s defaults. "bands": [] turns the picking off. A band loop can’t use (a maxScore that isn’t a whole number from 1 to 10, two bands with the same maxScore, an unknown effort, a model name that starts with -) stops the run before Claude Code starts, with the band named.
Session reuse
Each iteration starts a newclaude -p session by default, which re-reads CLAUDE.md and explores the repo again before it starts on the task. --reuse-session resumes the previous iteration’s session instead (claude -p --resume <session-id>), so the next task starts with what the last one learned. Every API call in a resumed session carries the earlier task’s history too, so it can cost more than it saves; compare the spend of a run with and without it on your project. With --reuse-session, the status line and the progress file say which each iteration did, resumed or fresh, and a resumed iteration’s prompt opens by saying a new iteration starts.
Even with --reuse-session, an iteration starts a new session when:
- the previous iteration failed, timed out or reported no session
- the previous iteration changed no task’s status and made no commit, or said all tasks were done while one was still ready
- the previous session’s context reached
loop.resumeMaxContextTokens(default 100000), read from the input, cache-write and cache-read tokens of its last API call - the task runs at a different model or effort than the previous one (see Model and effort by complexity)
- the run uses
--sandbox, whose sessions stay in the container;--paralleltasks always start fresh
0:

