Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion emrg/server/prompts/system.j2
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ Each skill is a file of instructions. When one bears on what the host asked, rea

- **Every call carries an `intent`**: one plain sentence on why it is made and what it should reach. It is never executed, but the host reads it, so it must be true and specific.
- **Read a file before you change it**, so the change matches what is there.
- **Issue every call you can already specify, in one response.** Sending several is the normal case: a call goes alone only when its arguments need an answer you have not read yet, and then its `intent` names the specific answer it waits for. Every round re-sends the conversation and emits fresh output.
- **Plan the round before the first call.** Enumerate what this round has to establish and the readings it needs, and write that enumeration out as the opening lines of your reply, before you call anything. Every reading you can already specify goes out in one response; a call stands alone only when its arguments need an answer you have not read yet. The plan is revised between rounds, when a reading changes what the round can still settle — never between two calls of the same round. One round re-sends the whole conversation and emits fresh output, so a lone call buys a single reading at the price of a round.

## Memory

Expand Down
27 changes: 17 additions & 10 deletions tests/test_evolution_prompt_round_cost.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,21 +72,28 @@
),
}

#: The rule's unit, its criterion and its boundary, as shipped in **both** carriers. Editing either
#: The rule's unit, its criterion and its reason, as shipped in **both** carriers. Editing either
#: one alone is the defect this pair catches, so `test_both_carriers_state_the_same_unit_and_criterion`
#: reads the live text of each rather than one file's expectations about the other.
#:
#: These are the longest phrases the two carriers genuinely share, and that is a measurement rather
#: than a preference: R10 is a rulebook entry a cycle reads and `system.j2` is one bullet in every
#: session's prefix, so the two state the same rule at different lengths — `issue every call you can
#: already specify, in one response: sending several tool calls in one round … is the normal case`
#: against `**Issue every call you can already specify, in one response.** Sending several is the
#: normal case: …`. The phrases below are where they agree word for word (the rule's direction,
#: the boundary that decides a lone call, and what that lone call must still say), and a drift in
#: any of them still moves both — which is the property the comparison exists for.
SHARED_UNIT = "every call you can already specify"
#: session's prefix, so the two state the same rule at different lengths — R10's `issue every call you
#: can already specify, in one response: sending several tool calls in one round … is the normal case`
#: against the bullet's `Every reading you can already specify goes out in one response; a call stands
#: alone only when its arguments need an answer you have not read yet`.
#:
#: The marker set moved on 2026-10-21 (issue #2437), when the bullet's subject changed from a count of
#: calls to a plan of the round, and it moved because a marker that one side dropped cannot be a phrase
#: two carriers share. What went is `names the specific answer it waits for`: the rewritten bullet does
#: not restate it, so that clause now lives in R10 alone, and by that alone it is not a shared phrase —
#: a comparison holding it would report every future rewrite as a drift. What came in is the reason
#: (`emits fresh output`), which both carriers do still state word for word. The ruled-out alternative
#: was to keep the old marker and edit R10 in the same PR: the host scoped this change to the bullet,
#: and a rulebook entry rewritten to fit a one-line prompt change is the drift this test exists to stop.
SHARED_UNIT = "you can already specify"
SHARED_CRITERION = "its arguments need an answer you have not read yet"
SHARED_BOUNDARY = "names the specific answer it waits for"
SHARED_REASON = "emits fresh output"

#: The unit this rule may no longer be stated in. The first version of R10 taught exactly
#: these; the host retired the merge-several-calls-into-one reading explicitly at
Expand Down Expand Up @@ -185,7 +192,7 @@ def test_both_carriers_state_the_same_unit_and_criterion() -> None:
"emrg/server/prompts/system.j2": SYSTEM_PROMPT.read_text(encoding="utf-8"),
}
for marker, label in ((SHARED_UNIT, "unit"), (SHARED_CRITERION, "criterion"),
(SHARED_BOUNDARY, "boundary")):
(SHARED_REASON, "reason")):
missing = [name for name, text in carriers.items() if marker not in text]
assert not missing, (
f"the {label} `{marker}` is not in {missing}: R10 and system.j2 carry one rule, so "
Expand Down
68 changes: 58 additions & 10 deletions tests/test_several_calls_rule_reach.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,15 +37,26 @@
itself. That is still a placement and wording guard: the share is re-measured by the instrument,
over the window a later cycle names, never here.

The bullet's subject changed on 2026-10-21 (issue #2437), because the count was the wrong subject: it
left a reader free to meet the rule one call at a time, since its only test was whether a call "needed"
an answer. The rule now asks for a **plan of the round, formed before the first call, and written out as
the opening lines of the reply** — the act, not the attitude. Two guards carry that: `RULE_TERMS` holds
each part of the ruling, and `RULE_ACTION_PHRASES` holds the two acts separately, one test case each, so
deleting either one reddens a test that names it. Also measured here on the day of the change: **9.7%**
of 137,592 rounds carry two or more calls, and 92.5% of the lone ones carry an `intent` naming no awaited
answer — the same instrument, re-read, because a wording change is cheap to claim.

What is asserted
----------------
1. The rule is in `system.j2` **and in the render**, and once only: a block in the file that no render
contains is not a rule any session runs under, and a second copy is two copies free to drift.
2. It carries each part of the ruling — the unit (one round, several calls), the boundary (a call whose
arguments depend on another's answer waits for its own round), the reason (cost, not speed), and the
precedence of the no-background-process red line.
3. The three misstatements are **gone**: no `parallel`, no `for speed`, no 并发. One statement, not two.
4. **Reverse**: no wording that caps, budgets or quotas *rounds* may enter the shared prompt. The host
2. It carries each part of the ruling — the act (plan the round first), the unit (one response, several
readings), the boundary (a call whose arguments depend on another's answer waits), **when the plan may
be revised** (between rounds, never between two calls of one round), and the reason (cost, not speed).
3. Each of the two action phrases is asserted **on its own**, per reader, so a mutation that removes one
is caught by a case naming it rather than by a longer assertion that happens to still be true.
4. The three misstatements are **gone**: no `parallel`, no `for speed`, no 并发. One statement, not two.
5. **Reverse**: no wording that caps, budgets or quotas *rounds* may enter the shared prompt. The host
rejected a round budget explicitly (rant 2026-10-15T12:35:03, 「c 没意义,肯定不行」 — it truncates long
tasks), so a future edit must not smuggle one in beside the rule that saves rounds.

Expand Down Expand Up @@ -76,15 +87,29 @@

#: The rule's identity: the bullet's lead phrase. Written as the phrase a rewrite would keep, so a second
#: copy anywhere in the template is visible as a count of 2 rather than as longer prose.
RULE_LEAD = "**Issue every call you can already specify, in one response.**"
RULE_LEAD = "**Plan the round before the first call.**"

#: The two actions the rule is made of, each a verbatim substring of the shipped wording and each asserted
#: on its own so that dropping one reddens a test naming it. **Enumerating** is the act the rule asks for
#: before any call; **writing the enumeration out** is the same act made observable, and it is the half a
#: wording can lose while still reading like advice. Measured on the previous bullet — which asked for a
#: count and carried neither phrase — the enumeration is what a reader never produced: 114,898 of 124,272
#: lone rounds (92.5%) carried an `intent` naming no awaited answer, read by
#: `scripts/check-batching-share.py` over 137,592 rounds of this installation's history.
RULE_ACTION_PHRASES = (
"Plan the round before the first call",
"write that enumeration out as the opening lines of your reply",
)

#: One term per part of the ruling, each a verbatim substring of the shipped wording — so this list cannot
#: drift away from the sentence it checks without this file saying so.
RULE_TERMS = (
RULE_LEAD, # the unit: one response, several calls
"a call goes alone only when its arguments need an answer you have not read yet", # the boundary
"names the specific answer it waits for", # what the lone call must still say
"Every round re-sends the conversation and emits fresh output", # the reason: money
RULE_LEAD, # the act: plan first, then call
"Every reading you can already specify goes out in one response", # the unit: one response, several
"a call stands alone only when its arguments need an answer you have not read yet", # the boundary
"The plan is revised between rounds, when a reading changes what the round can still settle",
"never between two calls of the same round", # when the plan may be revised — and not
"One round re-sends the whole conversation and emits fresh output", # the reason: money
)

#: The precedence clause's carrier: the section that owns the red lines, whose own paragraph says no
Expand Down Expand Up @@ -197,6 +222,29 @@ def test_the_shared_prompt_states_the_several_calls_rule(read) -> None:
)


@pytest.mark.parametrize("phrase", RULE_ACTION_PHRASES, ids=["plan-the-round", "write-the-enumeration"])
@pytest.mark.parametrize("read", [
pytest.param(lambda: SYSTEM_PROMPT.read_text(encoding="utf-8"), id="template"),
pytest.param(_render_system_prompt, id="render"),
])
def test_each_action_phrase_carries_its_own_case(read, phrase) -> None:
"""One case per action phrase, per reader — so deleting one reddens the test that names it.

`test_the_shared_prompt_states_the_several_calls_rule` asserts the whole bullet at once, which is the
right shape for "is the rule stated" and the wrong one for "did this half survive": a rewrite that
dropped a single phrase would report the other terms as present and this file as passing. The two
phrases are what the rule asks a reader to *do* — enumerate what the round needs, and write that
enumeration where the host can see it — and each has to be able to fail on its own for a mutation arm
to mean anything.
"""
block = _how_you_work_block(read())
assert block, f"system.j2 has no `{RULE_SECTION}` section for the rule to sit in"
assert phrase in block, (
f"the rule's action `{phrase}` is missing from `{RULE_SECTION}`: the bullet asks a reader to act, "
"and an act it no longer names is an act no session performs"
)


def test_the_batching_rule_never_licenses_backgrounding() -> None:
"""The red line's precedence, in the shape the compacted prompt gives it.

Expand Down
Loading