You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit e46cb68
Browse filesBrowse the repository at this point in the historyBrowse files
Enforce a pre-spawn model budget for planning invocations (#163) (#196)
* Enforce a pre-spawn model budget for planning invocations (#163)
createClaudeCommand now refuses a planning request whose prompt and schema
together exceed 64 KiB of UTF-8 (a byte count standing in for tokens, as
Claude's tokenizer is not available offline), and passes --max-turns 50 so
the run itself is bounded by turns, not only by time and output bytes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Quote Claude's errors when a run stops without a result (#163)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Name the turn limit in planning errors; label budget assumptions (#163)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Check the turn-limit envelope against a real Claude Code 2.1.281 run (#163)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Rewrap parseClaudeOutput's comment
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Rewrap parseClaudeOutput's comment
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Use the observed turn-limit envelope verbatim; note the limit-plus-one count (#163)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Word the model-budget docs to their evidence (#163)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Tie the planning input budget to the author's limits; note text-based turn-limit detection (#163)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Rewrap the plan-author builder note
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Check a prepared draft at the prompt limit against the model budget (#163)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Split the over-budget size into prompt and schema (#163)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Name every part of the request in the over-budget advice (#163)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Say which part of the backstop claim a test covers (#163)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Rewrap the budget note
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Match the turn-limit text anywhere; note the errors text reaches every phase (#163)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Make the other-phases note its own bullet
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Say that other output with the turn-limit text gets the same advice (#163)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Re-record planning fixtures with the --max-turns budget (#163)
Recorded by the maintainer with scripts/record-planning.ts through the
planning container (Claude Code 2.1.281). Both answers validate and
replay through E2/E3.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Copy file name to clipboardExpand all lines: docs/implementation/agent-isolation.md
+31Lines changed: 31 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -36,6 +36,7 @@ Each row is a T9 requirement for the Docker suite. The suite fails if any row fa
36
36
| A read-only phase cannot write `/work`|`agent-container`: phase worktree for all five phases |
37
37
| Planning and questions cannot run a process |`agent-policy`: tool sets exclude the command tool, and command dispatch refuses these phases |
38
38
| Codex runs in no phase, because it cannot read code without its shell (see [Codex is refused in every phase](#codex-is-refused-in-every-phase)) |`agent-policy`: the Codex command refuses all five phases; `agent-adapter`: the Codex start call refuses them and allocates nothing; `runner-execution`: an execute task with Codex is refused before task storage exists |
39
+
| A planning request over its model budget is refused before the start, and the planning command passes `--max-turns 50` (see [planning model budget](#planning-model-budget) for what is and is not shown about the stop) |`agent-policy`: a prompt and schema of exactly 64 KiB build a command with `--max-turns 50`, and one byte more is refused; `agent-adapter`: the start call refuses an over-budget request and allocates nothing, and an `error_max_turns` envelope is a provider failure whose text is its `errors`; `planning-provider`: the planning error quotes the turn limit |
39
40
| A planning answer is checked against the mounted schema |`agent-policy`: only the Claude planning command passes `--json-schema`; `agent-container`: the profile refuses a command whose schema differs from the mounted file; `agent-adapter`: the answer is read from `structured_output`|
40
41
| The Docker daemon keeps no log of an agent container (#165): the supervisor's bounded capture through `docker start --attach` is the only copy of its output. This covers the agent container only. The runner's other containers keep the daemon's default log: the task-storage script containers (`docker run --rm`, `agents/container/storage.ts`), whose log is removed with the container while the client bounds what it reads, and the storage keeper and egress proxy, which write no output |`agent-container`: the created container's `LogConfig` is driver `none` with no option but `cache-*` ones; the validator refuses a container with a `json-file` log or a bounded `local` log; every probe that returns output shows the attach still delivers it. `agent-daemon-log`: the rule (`keepsNoDaemonLog`) also refuses driver `none` with a log option, which only the API can set, and accepts the `cache-*` options a daemon copies into every container |
41
42
| Task and scratch byte and inode limits hold |`agent-container`: task capacity; scratch capacity for Codex and Claude (`/tmp`, `HOME`, `CODEX_HOME`, output directory) |
@@ -102,6 +103,36 @@ Use only these entry points to run an agent:
102
103
one link, at most 64 KiB of UTF-8, and a JSON object with `"type": "object"`; otherwise the start call throws.
103
104
Leave out a draft 2020-12 `$schema` line: Claude's `--json-schema` rejects it (see [plan
104
105
format](../plan-format.md)).
106
+
- <aid="planning-model-budget"></a>**Planning model budget (#163).**`createClaudeCommand` checks it when it
107
+
builds the planning command, so the adapter's start call throws before it creates a container or network. The
108
+
runner's host clone and task storage already exist by then, and the runner removes them. The budget has two parts:
109
+
-*Input, before the start.* The prompt and the schema together must be at most `MAX_PLANNING_INPUT_BYTES` (64 KiB)
110
+
of UTF-8: the 32 KiB prompt plus 32 KiB of schema text. A larger request is refused with an error that gives its
111
+
size, split into prompt and schema, and asks the person to narrow the request (the issue and comments, the paths,
112
+
the lessons, the feedback or the previous plan). Bytes stand in for tokens because Claude's tokenizer is not
113
+
available offline. A count of bytes is an upper bound on the tokens if every token covers at least one byte; this
114
+
is assumed, not verified against Claude's tokenizer. `prepareDraft` and `prepareSuggestions` already limit the
115
+
prompt and the schema text to 32 KiB each, so this check refuses nothing they produce. It is a backstop for a
116
+
caller that skips them. A test checks a draft at the prompt limit with today's schema; no test fails if the
117
+
32 KiB schema bound in `core/planning-author.ts` is removed. It does not count Claude Code's own system prompt
118
+
and tool definitions, or the tokens of the answer.
119
+
-*The run.* The command passes `--max-turns 50` (`PLANNING_MAX_TURNS`). If each turn sends at most one context
120
+
window, a turn limit bounds the tokens a run can use with no tokenizer. That is assumed: calls the CLI makes for
121
+
itself, such as compaction, may not count as turns, and the bound is loose (about 50 context windows; the CLI
122
+
reported `num_turns: 2` when it stopped at a limit of 1, so it may make one call more than the limit). Claude
123
+
Code 2.1.281 accepts the flag in `--print` mode (its help hides it) and stops at the limit with an
124
+
`error_max_turns` result and exit status 1. This was checked with `--max-turns 1` in a run of the 2.1.281 macOS
125
+
arm64 build outside the container; the Linux build in the image was read from its bundled source only. The
126
+
adapter reports that result as a provider failure whose text is the result's `errors` strings ("Reached maximum
127
+
number of turns (1)" in that run). That holds in every Claude phase, so a failed question run quotes them too.
128
+
The planning error then names the 50-turn limit, asks the person to narrow the request, and quotes that text. The
129
+
planning error finds the stop by that text, so other output that contains it, such as Claude's own `result`, gets
130
+
the same advice; the output is quoted either way. A token or dollar limit was not chosen: `--max-budget-usd`
131
+
counts the dollar cost of API calls, which is not what an OAuth subscription token pays, and the CLI has no flag
132
+
that limits the tokens of a whole run. 50 turns is about 12 s for each turn in the ten-minute budget; a planning
133
+
run reads files and answers, so most turns are reads. Lower it if runs are seen to loop; raise it only with a
134
+
recorded run that needs more.
135
+
-*Other phases.* They do not pass `--max-turns` yet; their runs are bounded by time and output bytes only.
105
136
-**Codex.**`startCodexInvocation` throws for every phase, and allocates nothing. Use Claude. See
106
137
[Codex is refused in every phase](#codex-is-refused-in-every-phase).
107
138
5. To keep a stopped writable attempt's partial output, call `exportTaskDiff(storage, { base, imageId })` before
Copy file name to clipboardExpand all lines: docs/implementation/plan-library-alignment.md
+1-1Lines changed: 1 addition & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -23,7 +23,7 @@ Issue #6 tracked the v1 plan library contract and the runner obligations that ca
23
23
| Output shown in the UI bounded: captured output comes only from the supervisor, which bounds stdout and stderr; when a planning run exits with an error, the provider's message quotes its output with whitespace collapsed, cut to at most 300 UTF-16 code units (`runner/planning-provider.ts`) | Done before this change; 300-unit quote tested in this change |`test/agent-supervisor.test.ts` output-limit cases (real Docker); `test/planning-provider.test.ts` ("quotes at most 300 code units of a failed run's output, with whitespace collapsed"). The Docker daemon's own log is #165 (*later:* the daemon keeps none) |
24
24
| Output limits at the exact byte |**This change** (tests; the supervisor already enforced the limits) | Supervisor enforcement: `test/agent-supervisor.test.ts` (real Docker). Decoder behaviour only, not a limit: the Claude decoder handles an envelope of exactly the stdout ceiling, and the 1 MiB parser limit applies to the decoded answer at the exact byte: `test/agent-adapter.test.ts`. At #6 the decoder re-serialized the answer, so the limit applied to the re-encoded text. *Later:*#166 returns the exact text of `structured_output` in the CLI's envelope, so the limit applies to those bytes |
25
25
| Prompt byte budget before spawn | Done before this change |`MAX_PROMPT_BYTES` in `core/planning-author.ts`; `test/planning-author.test.ts` ("accepts exactly the prompt byte limit and rejects the next byte without truncation") |
26
-
| Model budget before spawn | Moved to #163| —|
26
+
| Model budget before spawn | Moved to #163. *Later:* done in #163|`MAX_PLANNING_INPUT_BYTES` and `PLANNING_MAX_TURNS` in `agents/policy.ts`; `test/agent-policy.test.ts` ("accepts a planning request at exactly its model budget and refuses one byte more"); [agent isolation](agent-isolation.md#planning-model-budget)|
27
27
| Planning profile probed at startup | Moved to #164| — |
28
28
| Docker daemon log limits | Moved to #165. *Later:* fixed in #165 with `--log-driver=none`, checked before launch |`agents/container/profile.ts`, `agents/container/run.ts`; `test/agent-container.test.ts` ("creates the agent container with no Docker daemon log", "refuses an agent container with %s at validation") (real Docker), `test/agent-daemon-log.test.ts`. Agent containers only; the runner's other containers (task-storage scripts, the keeper, the egress proxy) keep the daemon's default log |
29
29
| Claude structured output under the v1 parsing contract | Moved to #166 (found during this change). *Later:* fixed in #166|`agents/adapters/claude.ts`; `test/agent-adapter.test.ts` ("lets draft validation refuse %s in a structured answer") |
0 commit comments