Skip to content

docs(predict_long): the default window's 64-token floor must be in the docstrings that state the default - #1009

Merged
NandhaKishorM merged 3 commits into
NandhaKishorM:mainfrom
aashish254:fix-window-default-floor
Oct 7, 2026
Merged

NandhaKishorM merged 3 commits into
NandhaKishorM:mainfrom
aashish254:fix-window-default-floor

Conversation

@aashish254

Copy link
Copy Markdown
Contributor

What

window_budget has guarded the default scan window with max(64, max_len - head_max_len - 8) since the feature landed, and four docstrings taught the budget on its own. They now teach the floored expression, and tests/test_predict_long.py gained a gate that reads the expression out of window_budget's own source with ast and requires that exact text in each of them:

  • laya/common.py — window_budget's docstring gains the sentence naming its own default (it previously named no formula at all).
  • laya/agent.py — Agent.predict_long's window: entry.
  • laya/router.py — Router.predict_long's window: entry.
  • laya/onnx_agent.py — ONNXAgent.predict_long's window: entry.
  • tests/test_predict_long.py — 21 new checks: the AST derivation of the guard, the behaviour on a config where the floor decides rather than the room clamp, and one prose check per docstring (teaches the expression, states the budget nowhere without the floor, teaches it inside the sentence that names the default).

No behaviour changes. laya/common.py:399 is untouched.

Why

The floor is the whole answer exactly where the prose is used most: docs/hooks/patterns.md tells a contributor to size the sequence for a many-option question, i.e. a widened head_max_len, which is the only regime where max(64, ...) decides instead of falling through. Measured on main (a4a8921) at max_len=256, head_max_len=200 with a 2-option question — the shape that gate's tests use:

the code: laya/common.py:397  requested = window if (window and window > 0) else max(64, max_len - head_max_len - 8)

laya/agent.py Agent.predict_long          names the budget: True | states a floor: False
laya/router.py Router.predict_long        names the budget: True | states a floor: False
laya/onnx_agent.py ONNXAgent.predict_long names the budget: True | states a floor: False
laya/common.py window_budget              names the budget: False | states a floor: False

  the budget the docstrings teach = 48
  the head actually built        = 88, so the room is 167
  window_budget returns          = window 64, stride 32, room 167
  the floor decides, not the budget: window == 64 and != 48
  a reader sizing a scan from the prose used 48-token windows, 25.0% under what runs

So a scan planned from the documented default ran with 48-token windows while every real call used 64 — 25% short, on every window, with the stride off by the same ratio. The three call sites are one helper, so all three are wrong by the same 16 tokens. This came in with 9acde82 (the guard) and stayed after ef93a94 rewrote that prose for a different reason, which is why a gate rather than a one-off edit: the docstrings were edited around the constant without ever reading it.

The gate deliberately reads the number and the expression text out of the code instead of typing 64, so a docstring that drifts to a different constant fails as loudly as one that drops the guard, and a future change to the floor has to be taught in the same pass. It is scoped to these four docstrings on purpose: the bare formula is a true statement about the state room elsewhere (state_room measures the head that was built), and this gate is not in a position to renegotiate that.

How it was verified

python -B tests/test_predict_long.py      # branch: 284 passed, 0 failed   |  main: 266 passed, 0 failed
python -B tests/test_onnx_long.py         # 53 passed, 0 failed (unchanged)
python -B tests/test_hooks_api.py         # 453 passed, 0 failed (unchanged)
/opt/homebrew/bin/ruff check laya/ tests/test_predict_long.py --select=E9,F63,F7,F82,F401,F811 --line-length=120
# All checks passed!
python -m compileall -q laya/ tests/      # clean

Numbers moved: the suite goes 266 → 284 checks; the docstrings' taught window moves 48 → 64 tokens at the config above (the code always ran 64). No parity numbers change — nothing in laya/ computes differently.

The witness is reproducible: /Users/aashish/github contribution /GIT/laya-bench/witness_window_floor_main.py (output in window_floor_witness_main.log) reads main's laya/ tree and prints the block quoted above; laya-bench/witness_window_floor.py is the same probe against the branch.

Mutation check (laya-bench/mutate_window_floor.py, log laya-bench/window_floor_mutation.log), two phases, every mutant applied to a reset tree and the suite re-run:

baseline: 284 passed, 0 failed
phase 1: 13/13 drifts killed
  D1 common.py's docstring loses its floored default (main's prose)        282 passed, 2 failed
  D2 agent.py back to the unfloored budget                                 281 passed, 3 failed
  D3 router.py back to the unfloored budget                                281 passed, 3 failed
  D4 onnx_agent.py back to the unfloored budget                            281 passed, 3 failed
  D5 all four revert together                                              273 passed, 11 failed
  D6 the docstring teaches max(48, ...) instead of max(64, ...)            281 passed, 3 failed
  D7 the bare budget reappears beside the floored one in agent.py          283 passed, 1 failed
  D8 the expression leaves the sentence that names the default             283 passed, 1 failed
  D9 the code's floor moves 64 -> 48                                       271 passed, 13 failed
  D10 the code's floor is dropped (exactly main's bug)                      266 passed, 18 failed
  D11 the code's guard arguments are reordered                              266 passed, 18 failed
  D12 the default stride stops halving the floored window                  278 passed, 6 failed
  D13 router.py carries the expression in the `stride:` entry              282 passed, 2 failed

Phase 2 blinded one arm at a time; every blind was first run alone and each left the suite green (284 passed, 0 failed), so none of them was already failing for an unrelated reason. Two arms are load-bearing and the rest are genuinely redundant:

  • the bare-budget ban — the only thing that kills D7 (drift escapes when it is blinded: 284 passed, 0 failed);
  • the as-the-default position arm — the only thing that kills D8 (escapes when blinded: 284 passed, 0 failed);
  • D1–D6 are caught by the doc-verbatim arm and the ban together; D9–D11 by the AST-shape arm, the rigged derivation, and the behaviour arms together; D12 also by the pre-existing long/overlap: stride is half the budget check; D13 by the doc-verbatim arm and the paragraph slice together.

D9 and D11 are the ones that answer "is this just a grep for the number you shipped with?" — the derivation rigged to the literal max(64, max_len - head_max_len - 8) still leaves the behaviour arms failing, and reordering the guard's arguments is caught by the shape arm, not by text matching.

Secret scan of git diff a4a8921...HEAD: no key, token, password, credential or private-key material; the only added lines containing the word "token" are three prose lines counting state tokens.

Overlap with open PRs

Checked with per-hunk line numbers before editing, not file-level. #687 (perf/state-prefix-tokenization) touches all four files; its nearest hunk in laya/router.py is pre-image line 1372 and my edit starts at 1380, so the two are 8 lines apart and could conflict on context lines — trivially resolvable, and I'd rather flag it than let a file-level "no overlap" claim stand. #687's nearest laya/agent.py hunks are 1620 and 1724 against my 1646–1653; its only laya/common.py hunk is line 78 against my 382–383 and 399. #716's nearest laya/onnx_agent.py hunk is 446 against my 429–432. No open PR has a hunk in tests/test_predict_long.py.

Checklist

  • Focused on one change (split unrelated work into another PR)
  • Rebased on the latest main
  • Tests pass locally
  • Docs or examples updated when the public API changes

`window_budget` has sized the default as `max(64, max_len - head_max_len - 8)` since
the scan landed (9acde82), and the three `predict_long` docstrings taught the budget
on its own. On a widened head -- `max_len=256, head_max_len=200`, the shape the hooks
pages tell you to build for a 60-option question -- the taught 48 is not what runs:
the floor decides, and every scan that reader sized was 25% short. The helper's own
docstring named neither the formula nor the floor, so it could not be checked against.
…hat computes it

Reads the floored expression out of `window_budget`'s own AST rather than typing 64
here, requires the four docstrings that teach a default to carry that exact text
inside the sentence naming it, and pins the behaviour on a config where the budget
falls under the floor: `max_len=256, head_max_len=200` with a 2-option question
returns a 64-token window and a 32-token stride, not the 48 the prose used to say.
21 checks.
…t abort it

`_DEFAULT_EXPR in _para` raises TypeError when the AST reader finds no guarded default, which
turns the loudest possible drift -- the guard gone from `window_budget` -- into an aborted suite
with no named check. Guarded with `bool(...)` so that mutant reports a FAIL per docstring.
@NandhaKishorM
NandhaKishorM merged commit a6cd556 into NandhaKishorM:main Oct 7, 2026
32 checks passed
@NandhaKishorM

Copy link
Copy Markdown
Owner

Merged in 0.3.29.

The default window's 64-token floor belongs in the docstrings that state the default, since a reader computing their own window from the stated default and not knowing about the floor gets a different number than the code uses.

Good timing with #966 in this release, which changed when predict_long scans at all. Anyone reading those docstrings now gets both the floor and the corrected one-pass condition.

NandhaKishorM pushed a commit that referenced this pull request Oct 8, 2026
The default scan window of `predict_long` is guarded with a 64-token floor:
`max(64, max_len - head_max_len - 8)`. Both pages taught the budget alone,
which is wrong exactly where the floor is the answer -- at `max_len=512`,
`head_max_len=448` the taught figure is 56 while the scan runs 64-token windows.

README.md: state the expression verbatim as the teaching, plus the measured
figures that show it in action (312 -> 64 on English; 760 on the other two).

`docs/hooks/patterns.md`: pair the floored number with the word `window`,
explain the clamp's cost signal (the warning), and name that the state's
room still binds below the floor while a full head is refused.

A new gate in `tests/test_state_budget.py` pins the code to what these pages
teach: AST-read `window_budget`'s `requested` Assign for the literal expression,
require the page paragraphs that pair the subtraction with `window` to carry it,
and verify the behavioural claims (floor decides when budget is under, room
binds below the floor, ValueError when the head fills the sequence).

The gate file has no overlap with open PRs; hunks are scoped to the prose edits
on purpose. CI runner shape: script-style suite. Witness block: 14 passed + 1
skipped under pytest, 30 passed + 1 skipped as a script. Gate lints clean in
CI scope (`laya/` and `tests/test_state_budget.py`) and compiles fully.
No sensitive data in diff (grep scan: keys/tokens/passwords: none).

If a change moves numbers, report before/after: (512, 448) -> 56 stated, 64
computed; k=110 question leaves 62 tokens but scans at 64. OPEN fallback order
decision because the gate's witness does not prove which arm is load-bearing.

See also #1009 (docstrings + gate for the floor), this is the prose complement.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants