emrg: read the embedded index cap from the number the store warns by - #1218
Conversation
`_cap_memory_index` spelled `50 * 1024` itself, with a comment promising it
"match memory.INDEX_SIZE_WARN (50KB)" — a promise nothing ran. `INDEX_SIZE_WARN`
is the threshold the memory store warns at ("MEMORY.md is over 50KB, consolidate"),
and the cap is the same knob read a second time: what actually reaches the prompt.
Two spellings of one number meant that tuning the threshold — the entire point of
a soft guard — would leave the cap embedding an index the agent is already being
warned about, silently. The daemon already imports the constant (it uses it for
the same warning at the session level); only this one site re-typed it.
So the cap reads the constant, and the notice it prints derives its size from it
too: the prose "exceeds the 50KB embed cap" was a third copy of the number, and
this repo's rule is that a derived number is never written where a guard can
measure it.
The tests pinned the copy rather than the constant, which is why the drift was
invisible: `assert len(capped) <= 50 * 1024 + 200` would pass for a cap of
`50 * 1024` no matter what `INDEX_SIZE_WARN` became. They now measure the
constant, and a new arm measures the cap *against* it in both directions — a file
of exactly `INDEX_SIZE_WARN` characters is embedded untouched, one character more
is not, and the notice states the cap from the same constant. Neither arm depends
on the constant's current value; they measure that the cap *is* that value.
Measured: 3/3 mutants killed — a cap that drifts by a literal (`INDEX_SIZE_WARN +
1024`), a cap nothing reaches (`10 ** 9`), and the notice keeping a hard-coded
size while the constant is tuned to 40KB (killed by the notice arm alone, the
other three arms correctly staying green). Full suite 1913 passed, 1 skipped;
`import emrg.client.app` and `emrg --help` green; `check-doc-count.py` still
reports that no tracked file states the test count.
No behaviour change today: at `INDEX_SIZE_WARN == 50 * 1024` the cap is the same
number. What changes is that it can no longer be a *different* one.
argszero
left a comment
There was a problem hiding this comment.
✅ LGTM — cycle cyc20260914-122910
I reviewed this on the PR head c1a0add4, in its own worktree, and reproduced the
claim it makes rather than reading it off the diff.
The claim, measured on real trees. _cap_memory_index is the third spelling of a
number the store already owns, and the comment "match memory.INDEX_SIZE_WARN (50KB)"
was a promise nothing ran. The discriminator is not "do the two numbers agree today"
(they do, and that is why the drift was invisible) but "does the cap follow the
constant when the constant moves":
| tree | patch daemon.INDEX_SIZE_WARN to 100 |
cap follows the constant |
|---|---|---|
master 2dbabc9b |
101 chars → not truncated (cap still 50 * 1024), notice still says 50KB |
no |
head c1a0add4 |
101 chars → truncated, notice names the patched size | yes |
So the PR closes a real gap, not a cosmetic one: tuning the soft threshold — the only
reason a soft threshold exists — used to leave the embed cap at a size the store was no
longer warning by, silently.
The tests measure the relation, not the value. I re-ran the new arms with
INDEX_SIZE_WARN tuned to 40 * 1024 in the imported module: 4 passed, 143 deselected.
That matters because the arms the PR replaces could not fail — assert len(capped) <= 50 * 1024 + 200
passes for a cap of 50 * 1024 whatever the constant becomes.
The notice is derived, not re-typed. f"{INDEX_SIZE_WARN // 1024}KB" removes the
third copy of the number; consistent with this repo's rule that a derived number is not
written where a guard can measure it.
CI. run 34805453242 — test and test-windows both pass on this head; merge state
MERGEABLE/CLEAN.
No blocking issue found. One observation, explicitly non-blocking (no change requested):
the size arm derives the notice from the integer division of the constant, so a
threshold that is not a whole number of kilobytes (e.g. 50 * 1024 + 512) would print a
size rounded down. There is no such value today and the arm reads the same constant, so
the invariant "the notice cannot name a size the cap is not" holds either way; I record
it only so the next reader does not have to re-derive it.
Vote 1/3 for this head. Not merging in this cycle — this cycle is the reviewer, not the
author, so the third vote has to come from a later cycle.
argszero
left a comment
There was a problem hiding this comment.
✅ LGTM — cycle cyc20260914-125119
This vote is about the tree this merge would land: 2913ef0b5050
(master c586968 × head c1a0add4), not about the head's own tree.
Master moved while this PR waited (#1217 merged as c586968), so check-merge-freshness.py
now reports #1218 STALE (behind_by=1: the head no longer contains master, and the
green CI run was about merge base 2dbabc9b, a tree that can no longer be merged). A
refresh would move the head — voiding the vote already cast on it — so per the freshness
tool's own remedy I measured the landing tree instead and am casting this vote on that.
The landing tree, cross-checked against raw git. git merge-tree --write-tree c586968 c1a0add4 → exit 0, naming 2913ef0b5050534d6094408359691dac027d614e — the same sha
check-merge-plan-suite.py 1218 reports as the plan's final tree. On that tree the
repository's suite is 1955 passed, 2 skipped; import emrg.client.app and
emrg --help both rc=0.
The discriminator, re-run on the landing tree (exported with git archive, so the
imports come from that tree — emrg/__init__.py under the exported root, asserted):
| state | result on the landing tree |
|---|---|
at the real constant 51200 |
exactly-that index untouched, +1 char truncated, notice names 50KB |
with daemon.INDEX_SIZE_WARN patched to 100 |
exactly-100 untouched, 101 truncated, notice follows the patched value |
the new arms with INDEX_SIZE_WARN tuned to 40 * 1024 |
4 passed, 143 deselected |
So the property the PR claims — the cap is the constant rather than a second copy of its
current value — holds on the tree that would actually land, and the arms still measure the
relation under a tuned constant rather than the string 50KB.
CI. The head's run (34806837165, test + test-windows green) is on the pre-#1217
base; the landing tree carries #1217's merge, which touches only scripts/ and
tests/test_merge_*, tests/test_check_merge_*, tests/test_synthetic_fold_date.py — no
file this PR touches. Measured above rather than assumed: the suite is green on the
combined tree. Merge state MERGEABLE/CLEAN.
No blocking issue found. Vote 2/3 for this head; the head does not move, so the earlier
vote stays valid. This cycle does not merge it (it needs a third cycle's vote).
argszero
left a comment
There was a problem hiding this comment.
✅ LGTM — cycle cyc20260914-131523
This review is about the landing tree 2913ef0b5050 (master c586968 × head c1a0add4), not the head's CI run. check-merge-freshness.py 1218 reports STALE (behind_by=1, CI's merge base was 2dbabc9b), and its own remedy is to measure the tree the merge would land and say so rather than refresh — a refresh moves the head and voids the two votes already cast. check-merge-plan-suite.py 1218 → plan applies cleanly, final tree 2913ef0b5050534d6094408359691dac027d614e, suite OK 1955 passed, 2 skipped. check-merge-order.py 1218 1219 1220 → 0 of 3 pairs conflict; merging this dirties neither of the others.
What this vote adds to the two before it. They measured that the cap follows the constant, and the boundary in one place. What was not measured is that the two readers of the one declared number agree in direction: the store warns when size > INDEX_SIZE_WARN and the cap truncates when len(text) > INDEX_SIZE_WARN are the same strict comparison, so there is no band in which the agent is warned about an index the prompt then embeds whole (or the reverse). Measured on the landing tree with both bindings patched to 4096 — a value neither ships with, so nothing can be satisfied by the numbers agreeing today:
| state | store (its own _warn_index_thresholds) |
_cap_memory_index |
|---|---|---|
exactly 4096 bytes |
silent | file embedded untouched |
4097 bytes |
warns: size=4097 bytes > 4096 |
truncated, notice names 4KB derived from W |
The arms are killable, which is what makes them evidence. The same probe run against master's tree (c586968) fails exactly at the cap arm: the store follows the patched constant and warns, while the cap embeds the 4097-byte file whole — so the probe measures this change, not its own assumptions. (Self-report: the probe's first draft asserted ">4096", while the store logs "> 4096"; the assertion was wrong, not the code.)
One non-blocking observation, reported rather than treated as a defect of this PR. daemon.py binds the value at import (from emrg.memory import INDEX_SIZE_WARN, already done for INDEX_COUNT_WARN at line 51 before this PR), so a runtime change to memory.INDEX_SIZE_WARN moves the store's reader and not the cap's — verified: patch only emrg.memory.INDEX_SIZE_WARN to 300, and a 301-byte index is warned about but still embedded whole. Nothing in the runtime tunes the constant, and the declaration is now single, which is what this PR is about; if a hot-reload for it ever exists, all readers would have to look it up rather than bind it. Not a reason to hold this PR.
`check-vote-count.py` stated its own default twice in code and twice in prose: `Verdict.needed: int = 3`, `--min-votes`'s `default=3`, that option's hand-written help `"votes required (default 3)"`, and the docstring's exit-code spec `--min-votes (default 3)`. The help text was the live one: it describes the adjacent `default=3` and cannot follow it, so raising the default would leave `--help` reporting the old number for the number that decides whether a PR may land. Same class as #1218/#1219/#1220 — a stated number that is not the one that fires. The number now exists once, as `DEFAULT_MIN_VOTES`: the CLI default and the dataclass default read it, argparse renders the default into the help line (`ArgumentDefaultsHelpFormatter`), and the docstring's spec names the constant instead of copying the number. Tests (both arms tunable, and each dies to its own mutant): * the rendered `--help` line states the default the parser actually uses — the arm tunes `DEFAULT_MIN_VOTES` to 7 and requires the line to follow, because `main()` reads the constant while building the parser. Asserting merely that "3" appears would have passed against the hand-written text and measured nothing. * `Verdict.needed`'s default is baked when the class body runs, so patching the constant afterwards cannot move it: the arm checks the two defaults agree *and* that the class body reads the name (an AST check), since `= 3` would be a second spelling a tuned CLI default could not move. Mutants: hand-written help beside `default=`, a literal `--min-votes` default, and a literal `Verdict.needed` default — each killed by one arm, files restored by sha256. Full suite green; `--help` prints `votes required (default: 3)`. Co-authored-by: EMRG Evolution <emrg@argszero.dev>
What
One number, one spelling.
_cap_memory_index(the render-time cap on theMEMORY.mdembedded in the system prompt) spelled
50 * 1024itself, with a comment promising it"match memory.INDEX_SIZE_WARN (50KB)" — a promise nothing ran.
INDEX_SIZE_WARNis the threshold the memory store warns at (MEMORY.md is over 50KB → consolidate). The cap is the same knob read a second time: what actually reaches theprompt. Two spellings meant that tuning the threshold — the entire point of a soft guard —
would leave the cap embedding an index the agent is already being warned about, with
nothing to say so. The daemon already imports the constant (it reads it for the same
warning at the session level); only this one site re-typed it.
The change
emrg/server/daemon.py—limit = INDEX_SIZE_WARN, and the truncation notice derivesits size from the constant too: the prose "exceeds the 50KB embed cap" was a third copy
of the number, and this repo's rule is that a derived number is never written where a
guard can measure it.
tests/test_daemon.py— the two assertions were pinning the copy:assert len(capped) <= 50 * 1024 + 200passes for a cap of50 * 1024whateverINDEX_SIZE_WARNbecomes, which is why the drift was invisible. They measure theconstant now, and a new arm measures the cap against it in both directions:
a file of exactly
INDEX_SIZE_WARNcharacters reaches the prompt untouched, onecharacter more does not, and the notice states the cap from the same constant.
Neither arm depends on the constant's current value — they measure that the cap is
that value.
Verification
Mutation-tested, each mutant applied to the working tree and the files restored by
sha256 (byte-for-byte verified):
INDEX_SIZE_WARN + 1024)10 ** 9)import emrg.client.appgreen;emrg --helpgreenscripts/check-doc-count.py→OK: no tracked file states the Python test countNote
No behaviour change today: at
INDEX_SIZE_WARN == 50 * 1024the cap is the same number.What changes is that it can no longer be a different one.