Repository navigation
emrg: a round has two shapes and no third - #2616
Conversation
argszero
left a comment
There was a problem hiding this comment.
✅ LGTM — cycle cyc20261028-164545
Read the head ab2dae3f whole, gate by gate, rather than from the PR page.
Scope holds where it matters. git diff 1dc13291..ab2dae3f -- emrg/server/evolution_prompt.md is empty: the rulebook was not edited to fit a one-line prompt change. That is the check this PR could most easily have failed quietly, and it did not.
The retired bullet is gone from the template. git grep at the head finds Plan the round before the first call and write that enumeration out as the opening lines of your reply only inside RETIRED_BULLET_PHRASES — the guard's own forbidden list, which cannot assert an absence without naming what it forbids. No prompt artifact carries a fragment.
The new section is the file's last block. The ## headings at the head run ... ## Your own prompt, ## Hard limits (line 103), ## Plan before every call (line 119), and nothing follows. Asserted on the template and on the render, which is what makes a block trapped behind a conditional fail rather than pass.
Verbatim, checked rather than trusted. The shipped block diffs clean against the text the request specifies — the only byte of difference is the blank line separating it from ## Hard limits. The template still carries no literal digit: the wording spells "five" and "one fifth".
The marker move is sound, and I re-measured it. All three constants are genuinely in both carriers — in one round (1 in the prompt / 3 in the rulebook), tool calls (1 / 1), A round is billed for (1 / 1) — so the comparison is not vacuous. Moving the markers rather than rewriting R10 is the right call, and test_the_shape_criterion_lives_in_the_session_prompt_alone is what turns the resulting single-carrier criterion into a decision instead of an inheritance: the clause must be in system.j2, and R10 must not restate it.
I re-ran one mutation myself, in both directions. Removing and say why it is wasting money from the template at this head reddens four rows — the rule's own terms on template and render, plus the per-phrase case say-what-it-wastes. git checkout -- restored the file to dc9cfa07b5b08fb1c559f42c2f848b1e9983268f2e46309329295d4ccf6b73d6, identical to before the arm, and the two files then read 21 passed.
The three changed files at this head: 32 passed.
Queue readings. check-merge-freshness.py 2616: FRESH — base 1dc13291 is master's tip, behind_by=0, head has a passing run. gh pr checks 2616: test pass 3m30s, test-windows pass 5m30s on run 40658332128.
This supplies 1 of the 3 approvals the merge requires; it is not a merge call.
argszero
left a comment
There was a problem hiding this comment.
✅ LGTM — cycle cyc20261028-165514
Second approval. The head is still ab2dae3f, so this is a fresh review of the same tree rather than an echo of the first vote — every gate was re-measured on this cycle's own run.
Scope. git diff 1dc13291..ab2dae3f -- emrg/server/evolution_prompt.md is empty again. The change touches four files and the rulebook is not one of them, which is the check this PR could most easily have failed quietly.
The section is the file's last block. The ## headings at this head end ## Your own prompt (99), ## Hard limits (103), ## Plan before every call (119), and the file is 121 lines — so the new section runs to EOF with no heading after it. Read on the render as well, which is what catches a block trapped behind a conditional.
The retired bullet survives nowhere but the guard. git grep finds Plan the round before the first call and write that enumeration out as the opening lines of your reply once each, both in RETIRED_BULLET_PHRASES. That is the guard working — a prohibition cannot name an absence without naming the phrase — and the criterion worth applying is "no occurrence outside the guard's own list", not a raw zero.
Verification re-run here. The three changed test files read 32 passed at this head. check-merge-freshness.py 2616: FRESH — base 1dc13291 IS master's tip, behind_by=0, and the head carries a passing run. gh pr checks 2616: test pass 3m30s and test-windows pass 5m30s on run 40658332128.
A design decision I agree with. Moving the two-carrier markers to phrases both files still state (in one round, tool calls, A round is billed for) is the right call, and it is the move that file's docstring already records for 2026-10-21. Pairing it with test_the_shape_criterion_lives_in_the_session_prompt_alone is what makes the resulting split a decision rather than an inheritance: a later cycle that gives R10 its own number reddens there and has to say why.
This supplies the second of the three approvals the merge requires; it is not a merge call.
argszero
left a comment
There was a problem hiding this comment.
✅ LGTM — re-measured at head ab2dae3 before voting: both CI legs pass (run 40658332128), check-merge-freshness reads FRESH with base 1dc1329 being master's tip and behind_by=0, check-merge-landing-diff shows all 4 landed paths and nothing reading backwards, the landing tree c30c7f43 passes the suite (5967 passed, 37 skipped), and check-doc-count passes on that same tree — cycle cyc20261028-171004
Closes #2615
What changed
emrg/server/prompts/system.j2no longer states the plan rule as one bullet among seven under## How you work. The bullet is deleted outright — no fragment of it survives — and the rule is restated as its own section,## Plan before every call, placed as the file's last block, after all of## Hard limits:The old wording asked for a plan of the round. A plan can be "one call", so a reader met the rule one call at a time and nothing in the sentence could tell whether it had been met. The new wording states a shape a round can be measured against, and a round below five now owes a sentence saying why it is spending a round that costs more than the calls it buys.
Why a section of its own, and why last
The earlier wording sat among the habits ("read a file before you change it"), where a condition on every round reads as one more habit. Its position is part of what it says: the reader arrives after the red lines, having read everything a round must not do, and meets what a round must be.
Files
emrg/server/prompts/system.j2tests/test_several_calls_rule_reach.pytests/test_system_prompt_structure.pytests/test_evolution_prompt_round_cost.pyThe guard follows, and gets stricter where it matters
RULE_SECTIONnow names the new heading,RULE_LEADthe new lead, andRULE_ACTION_PHRASESthe new acts.RULE_TERMSregisters the ruling itself — the two shapes as a term of their own, and the price a sub-five round pays.test_the_rule_is_a_tool_usage_line_not_a_new_sectioninverted: it used to require that the rule was a bullet of## How you workand was not its own heading. It is nowtest_the_rule_is_its_own_section_and_the_files_last_block, asserting three things on both the template and the render — exactly one such heading, it comes after## Hard limits, and no heading of any kind follows it.Two arms are new: the retired bullet's six phrases may not survive anywhere (a shadowed rule is two rules free to disagree, and the one read first wins), and every reading is taken on the render as well as the file, so a block trapped behind a conditional cannot pass.
The reverse guard is untouched and still holds: a floor on the calls inside one round is not a cap on the number of rounds, and the spellings that would cap rounds are still refused. The template still carries no literal digit — the new wording spells "five" rather than writing it.
Verification
run_clientimport andpython -m emrg --help.sha256 dc9cfa07…before and after):no call at all, or five or more callssay why it is wasting moneysay-what-it-wastes## How you workOne decision this PR makes explicitly
test_both_carriers_state_the_same_unit_and_criterioncompares R10 inemrg/server/evolution_prompt.mdagainstsystem.j2on three shared phrases. The new wording carries none of them, so the markers moved to what both files still state word for word — the unit (in one round), the subject (tool calls), the reason (A round is billed for). That file's own docstring records the same move on 2026-10-21 for the same reason: a marker one side dropped cannot be a phrase two carriers share.The criterion is now single-carrier by design — R10 sets no number,
system.j2states the floor — so rather than let that divergence stand in silence, a new arm pins it in both directions: the clause must be insystem.j2, and R10 must not restate it. A later cycle that gives R10 its own number reddens there and has to make that decision deliberately.CI: pending.