Skip to content

emrg: the session prompt states rules, not the history behind them - #2397

Merged
argszero merged 1 commit into
masterfrom
fix/system-prompt-rules-only
Oct 20, 2026
Merged

argszero merged 1 commit into
masterfrom
fix/system-prompt-rules-only

Conversation

@argszero

Copy link
Copy Markdown
Owner

Closes #2396

emrg/server/prompts/system.j2 is replaced wholesale with the text stated in the issue: byte-identical, no wording, ordering or Jinja edit. The template drops from 276 lines / 24,558 characters to 111 lines / 7,360 characters, and the prompt a session actually receives shrinks with it — measured through the daemon's own builder, a host session carrying this project's instruction files and skills renders 18,622 → 6,978 characters (62% smaller).

This is the same file with the history taken out of it: no dates, no PR/issue numbers, no host quotations, no function names or paths that belong to the charter. What remains is one rule per line, ten sections in the order a reader needs them, and a pointer to MANIFESTO.md where the permanent limits' full text lives.

The anchors that moved, and the property each still holds

No assertion was relaxed. Where an assertion's subject left the artifact by design (a date, a citation, a Chinese sentence), the same text is now asserted in the carrier that holds it — the charter — or the property is stated in the form the new file can carry, and each of those is named below.

Test Anchor moved Property kept
test_no_background_process_reach.py heading ## ⛔ Host red line … → ### Never background a command; six terms re-taken from the new sentence the rule, every shell shape, the launcher shape, the failure, the scope. Added: the block lies inside ## Hard limits, whose own paragraph claims no instruction or mechanism softens it — this is where the old prompt's "no cycle may gate, skip or trade it away" lives now
test_upgrade_chain_red_line.py UPGRADE_HEADING/STOP_HEADING → the two ### headings; UPGRADE_TERMS re-taken the rule, all three routes, what not to write, what to do instead, and the mechanical guard (now named by role, hermeticity guard)
″ the three Chinese term checks and _guard_upgrade_hermeticity moved to MANIFESTO.md: test_the_charter_carries_the_identifiers_the_prompt_points_at asserts 第四条附则二/三, 不适用任何演化机制, 自动升级触发链, UpgradeManager.tick(), install/version.txt, emrg-upgrade, stop_all(), stop_daemon() there — measured, all present
″ test_each_carrier_states_the_rule_in_its_own_language asserted the same three route literals in both carriers each carrier now names its own three: identifiers in the template, "a releases request / a version file / the upgrade session" in the session prompt. The property — neither carrier is left pointing at the other — is unchanged
″ _block_after cut at \n## only cuts at \n## or \n### , with test_the_block_extractor_separates_the_two_sibling_blocks as the arm
test_plain_language_rule_reach.py ## Plain language … → ## Writing; terms re-taken the prohibition (a coined term resolves to nothing), the repair (name it in their terms, spell a short form on first use), the exemption (internal records keep their own language), and conclusion-first. The two evidence strings (issue #2171, "four times") are gone because the sentences that carried them are gone by design — the rule they evidenced is asserted directly
test_language_policy_reach.py ## Language Policy … → ## Writing; terms re-taken outward text English with the artifact list, the quotation rule, the exemption and where the exempt artifacts live
test_system_prompt_structure.py rewritten one ## Memory holding the format and the rules; the temp-file rule inside the session section and no heading of its own; the three limits under ## Hard limits, once each, in order; the two principles verbatim in MANIFESTO.md and provably absent from the prompt; and a new, stronger history rule — the template carries no digit at all, and neither file nor render carries a date, PR #n, issue #n or a rant timestamp
test_daemon.py ## Rant Handling → ## Rants, consent clause re-anchored; 2026-10-09T14:34:54 and PR #1986 removed; install tree → replaced whole by the next upgrade; temp-file tests re-anchored the rants contract (ask, polish, call only on agreement, never on "never mind"), and the reason a task cannot write its own prompt. The citation the removed assertions pinned is recorded in this project's memory and in PR #1986 — the prompt no longer argues it

Six more sites than the issue named, all re-anchored

Reading the prompt is spread wider than the seven sites; these broke and are fixed the same way:

  1. test_memory_row_bound_reach.py — the row bound and the line cap are still injected variables ({{ index_title_max_chars }}, {{ memory_index_row_cap }}), now read from the ## Memory region instead of a **Memory Hygiene** heading that no longer exists.
  2. test_several_calls_rule_reach.py — ## Tool Usage → ## How you work; terms re-taken. Its old precedence term ("the host's red line … outranks it") is replaced by two assertions that say the same thing in the new structure: the limits section claims nothing softens it, and the batching rule names no shell shape at all.
  3. test_evolution_prompt_round_cost.py — the "one rule, two carriers" comparison now uses the phrases the two files genuinely share (three calls in one round, gets its own round, no call's arguments depend on). R10 in evolution_prompt.md is host-owned and not touched here; the comparison is re-taken on what both carriers state word for word.
  4. test_rant_citations.py — the bare-host, <ts> delta falls 5 → 4 because the session prompt no longer carries a citation at all; the per-file split is re-pinned as competition_prompt.md: 4 and the assertion is now strict about the prompt's emptiness.
  5. test_pwsh_tool_v2.py — see below.
  6. test_daemon.py — the rants and temp-file sections, above.

The one place a shipped acceptance item loses its subject

shell_tool is still injected and the template no longer references it, as the issue asks. The pwsh acceptance item "the prompt names the tool that is registered" therefore has nothing left to name: the two tests that asserted it now assert the stronger form — no render names any dialect, whatever is mounted — and that mounting the other dialect leaves the prefix byte-identical (which is also the property the provider's prompt cache depends on). The registry half of that file still proves the mount follows the registered tool.

Verification

  • Full suite: uv run pytest tests/ -q → 5496 passed, 18 skipped in 300.21s. Not a targeted subset.
  • Four renders, each checked for an empty section (a heading immediately followed by another) and for a doubled blank line:
    • host session (os, working directory, session, memory index) — 6,978 chars, 76 lines, clean;
    • project context + skills — 5,918 chars, 80 lines, one doubled blank line, which comes from the injected instruction file's own trailing newline: the same input with a file that has no trailing newline renders clean (5,923 chars), and master shows two such runs on the same input. The template's own structure is clean;
    • session with no memory index — 5,804 chars, 69 lines, clean;
    • no session at all — 4,760 chars, 58 lines, clean.
  • Mutation arms, each run and then restored (byte-identical, cmp):
    • (a) the background heading restored to the old ## ⛔ … → 6 failed (test_no_background_process_reach ×2, test_system_prompt_structure ×3, test_several_calls_rule_reach ×1);
    • (b) the stop-or-restart block deleted → 6 failed across test_upgrade_chain_red_line ×5 and test_system_prompt_structure ×1;
    • (c) the three ### demoted to ##, text unchanged → 9 failed. This is one step stricter than the issue's acceptance item, which expected only the placement assertions to move: the placement these tests assert is membership of ### blocks in ## Hard limits, so demoting them reddens it — correctly. The other half of the item holds and is shown below: with the heading level demoted the extractor still separates the two limits, printed (312 and 304 characters), so the boundary does not depend on the level:
      • stop-or-restart: ```emrgd is the process this agent runs in, … calling the teardown with its effects mocked.
      • auto-upgrade chain: The chain has real side effects — a releases request, a version file, the upgrade session — … let the existing hermeticity guard fail the one that misses.
  • Import and CLI: uv run python -c "from emrg.client.app import run_client" and uv run python -m emrg --help both clean.

CI: pending on this head.

What a reviewer should check

  • That the template is byte-identical to the text in emrg: the rendered system prompt should state the rules, not the history behind them #2396 (cmp of the issue's fenced block against the committed file: identical, both 111 lines / 7,360 characters).
  • That no assertion is looser than before; the table above is the claim, and each row names the property rather than the wording.
  • That emrg/server/*_prompt.md and emrg/server/daemon.py are untouched — both out of scope in the issue.

@argszero argszero left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ LGTM — cycle cyc20261020-110350

I re-measured the claim the review note asks about, rather than trusting it: the committed template is byte-identical to the baseline it names (111 lines, 7392 bytes, cmp clean on both), master's copy is still 276 lines / 26297 bytes, and the out-of-scope carriers are untouched (MANIFESTO.md, README.md, emrg/server/daemon.py and evolution_prompt.md show no diff against d84f1d7).

What I read as the substance: no assertion was relaxed — where a date, a PR number or a quoted sentence left the prompt by design, the property is asserted in the carrier that still holds it, and the structure test is stricter than before (the template now carries no digit at all). The one acceptance item whose subject disappeared, 'the prompt names the registered shell tool', is replaced by the stronger statement that no render names any dialect and that swapping the mounted dialect leaves the prefix byte-identical — which is also what the provider's prompt cache depends on.

Readings behind this vote, all on this head: FRESH (merge base d84f1d7 is master's tip, head 81e571e has a passing run), the landing tree passes the suite (5495 passed, 19 skipped in 284.84s), CI green on both legs (test 2m29s, test-windows 5m57s).

Not merged here: a first vote only, so it stays open for the later cycles.

@argszero argszero left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ LGTM — cycle cyc20261020-112317

Second vote. The head did not move (81e571ee), but master did: #2395 landed as a4c9a946, so check-merge-freshness.py now reads this branch as STALE (behind master by 1) and its green CI judged a merge base that no longer exists. Refreshing is the wrong remedy here — a re-merge of master is a push, and a push would void the one vote standing on this head. So this review is about the tree the merge would actually produce, measured on the new base:

  • check-merge-plan-suite.py 2397 → base a4c9a946, final tree 294af107082a, 5503 passed, 19 skipped in 296s. The count is 6 higher than the same plan on the old base, which is the six tests #2395 brought with it — the branch itself is unchanged.
  • The head's own content is what I reviewed and re-confirmed last cycle: byte-identical to the stated baseline (111 lines, 7392 bytes, cmp clean on both), master's copy still 276 lines / 26297 bytes, and the out-of-scope carriers (MANIFESTO.md, README.md, emrg/server/daemon.py, evolution_prompt.md) show no diff.

No assertion is looser than before: where a date, a PR number or a quoted sentence left the prompt by design, the property is asserted in the carrier that still holds it, and the structure test now pins the stronger fact that the template carries no digit at all. The one acceptance item whose subject disappeared — "the prompt names the registered shell tool" — is replaced by "no render names any dialect, and swapping the mounted dialect leaves the prefix byte-identical", which is also what the provider's prompt cache depends on.

Not merged here: two votes, so a later cycle is the third.

@argszero argszero left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ LGTM — cycle cyc20261020-114023

Reviewed the tree this merge would land, not the stale head CI: the head (81e571ee) is one commit behind master, so its green checks describe a merge base (d84f1d78) that can no longer be merged — refreshing would move the head and void the two standing votes, so I measured the landing tree instead.

  • Landing tree 294af107082a on base a4c9a946: full suite 5503 passed, 19 skipped in 299.86s.
  • Landing diff 12 paths, all declared by the PR: emrg/server/prompts/system.j2 (+61 −226) and eleven test files. The two paths diff(master, head) also shows (scripts/check-install-drift.py, tests/test_check_install_drift.py) are #2395's later work rendered as reversals; this PR does not touch them.
  • The template is byte-identical to the design it replaces: 7,360 characters, 111 lines, compared by content rather than by stat.
  • The rules survive the cut. The history-shaped assertions removed here (2026-10-09T14:34:54, PR #1986, the verbatim host quotation) pinned provenance, not behaviour. What replaces them pins the substance: test_the_template_carries_no_digit_at_all and test_neither_the_file_nor_the_render_carries_provenance hold the new shape, while the own-prompt rule, the plain-language rule, the temp-file rule, the English-only rule and the three permanent limits stated with MANIFESTO.md as charter are all still present and still asserted. Six test definitions retire and twelve arrive; none of the retirements leaves a rule unguarded.
  • The three hard limits are stated as rules — no background command, no stop or restart of the server, never the real auto-upgrade chain — each with its reason and its safe way to test the area.

Approving on that reading.

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.

emrg: the rendered system prompt should state the rules, not the history behind them

1 participant