feat(ai): a repository states its own read window, and GATE-READ-1 says which caller it means - #415
Merged
Merged
Conversation
…ys which caller it means `MAX_READ_CHARS` was 64,000 because I measured this repository — 97% of its Python files fit whole. That is a fine way to pick a default and no way to pick a number nobody else can change. A repository of short modules pays for a window it never fills, on every read, on every later turn; one carrying generated clients an order of magnitude larger cannot read its own code and has no way to say so. `Workshop(max_read_chars=...)` states it, a contributed policy layer may lower it, and `ls` prints what the stack contributed. The value travels the road `max_test_runs` already walks — `InvokePolicy` to the binding site to `ToolRunnerImpl` — with `_window` at the end instead of the test counter. All three session constructors take it, not only the one that executes: a review reads files exactly as an implement does, and a cap honoured in one and not the other is two answers to one question. `max_tool_result_chars` stops being a field and becomes a property returning the read window. #413 set the two equal so they could not be edited apart, and making both settable would put that defect back in through the front door: a repository raising its window and leaving the result bound at the shipped default would be cut in half again, with nothing saying so. One number an adopter states. GATE-READ-1 has said "the cap is not negotiable by the caller" for a year without saying which caller, and the two it could mean are not alike. The caller is the MODEL, which reaches the reader through `read_file`'s `offset` and `limit` — neither of which is a byte budget — and the row now says so, because leaving it ambiguous is how a reader resolves it the wrong way: either weakening the rule that stops a model widening its own prompt, or abandoning the configuration because "the gate says no". `test_gate_read_1_a_model_cannot_widen_the_window_it_reads_through` now runs against a non-default cap, so it proves the rule rather than the constant. Closes #414 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Contributor
in-lockstep review — intentsucceeded · $0.0779 · 3 in / 779 out tokens
|
Contributor
in-lockstep review — performancesucceeded · $0.0593 · 3 in / 116 out tokens No findings. |
Contributor
in-lockstep review — securitysucceeded · $0.0910 · 6 in / 1089 out tokens
|
Contributor
in-lockstep review — testssucceeded · $0.0790 · 3 in / 854 out tokens
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #414.
MAX_READ_CHARSwas 64,000 because I measured this repository. That is a fine way to pick adefault and no way to pick a number nobody else can change.
The value travels the road
max_test_runsalready walks —InvokePolicy→ the binding site →ToolRunnerImpl— with_windowat the end instead of the test counter. All three sessionconstructors take it, not only the one that executes: a review reads files exactly as an implement
does, and a cap honoured in one and not the other is two answers to one question.
Workshop.max_read_charsisint | Nonerather than a literal, becauselockstepmay not importaiunder the layering contract and a copied default is two numbers to keep in step.InvokePolicy.underresolves the absence.One number, not two
max_tool_result_charsstops being a field and becomes a property returning the read window. #413set the two equal so they could not be edited apart; making both settable would put that defect
back in through the front door — a repository raising its window and leaving the result bound at
the shipped default would be halved again, silently.
test_the_result_bound_cannot_fall_below_the_configured_read_windowdrives that over threeconfigured sizes rather than over the default alone.
Which caller GATE-READ-1 means
The row has said "the cap is not negotiable by the caller" since #402 without saying which caller,
and the two it could mean are not alike:
_windowthroughread_file'soffsetandlimit.offsetmoves thewindow,
limitselects lines, and neither is a byte budget. That is the whole of the clause, andit is why the value is read from the session the framework built and never from the request.
lockstep.pyowns the repository, the spend and the context budget thecap rations, and already states
max_turns,max_tokensanddeadline_seconds.Leaving it ambiguous is how a reader resolves it the wrong way — either weakening the rule that
stops a model widening its own prompt, or abandoning the configuration because "the gate says no".
The row now names the model, and
test_gate_read_1_a_model_cannot_widen_the_window_it_reads_throughruns against a non-defaultcap over four argument shapes, so it proves the rule rather than the constant.
A contributed policy layer may lower the window and may not raise it — the direction every
ceiling in the stack moves, since a layer narrowing what a model may pull into a prompt is a control
and one widening it is not.
Objectives
O2 — what a person writes by hand is what nobody could discover for them, and the size of their
files is exactly that. O8 — until now the only way to change this number was to edit the
framework. O13 — the bound on what one read pulls into every later turn should be stateable by
the repository paying for it.
Verification
make checkgreen from the committed tree: 2874 passed, 6 skipped; ruff format reports norewrites, so what is pushed is what passed.
GATE-POLICY-2's field pin intest_core.pyis updated rather than worked around: that gaterequires every
ResolvedPolicyfield to be composed byunder(), and this one is, withtest_a_policy_layer_may_lower_the_read_window_and_may_not_raise_itdriving it through the stackinstead of trusting the field's presence.
Five falsification controls fired and restored from the commit, each asserting its own edit
landed first:
Workshopend-to-end test goes red;A sixth was written and discarded as a no-op: flipping
_window's whole-file condition tothe module constant left the test green, because the fixture is larger than both values and the
branch is not taken either way. The two slice controls above replace it. Recording it because a
control that cannot fire looks exactly like one that did.