Conversation
`{{ evolution_cwd }}/memory/` is `~/.emrg/evolution/memory/` — outside the
workspace-write sandbox's boundary, so every write there is refused, and
outside the `.emrg/` root, the only memory the daemon loads. The identity file
and the "key findings" step both aimed at it, making those two instructions
unfollowable; the stray directory they left behind holds 36 files nothing reads
(the trusted root holds 980).
Retarget both to `{{ evolution_cwd }}/.emrg/memory/` and guard the class: a new
test walks every `{{ evolution_cwd }}`-rooted memory path in the builtin
templates and asserts the sandbox's own trust list allows it — the list is read
from `_trusted_write_zones()` rather than copied, so a moved boundary is
reported as a stale prompt. A self-test first proves the measured shape really
is outside the boundary on this machine, so the check cannot pass vacuously.
Verified: 1959 passed / 1 skipped (master 1958 + this guard); both mutants
(re-inserting either bad path) fail the guard; rendered prompt names only the
trusted root.
|
Superseded by #1226 — closing this one rather than leaving a duplicate in the queue. #1226 does both of this PR's prompt edits, but as part of a larger change: the The valuable half of this PR is not lost: its guard test No votes had been cast on this PR, so nothing is invalidated by the close. |
…ompetition, paper) (#1226) * emrg: retire the state-file/reflection-file mechanism (open-source, competition) Rant 2026-09-14T14:35:47: "delete the state-file and reflection-file mechanisms entirely, no residue; the session itself is the memory." Every phase hand-off in the open-source and competition prompts was a line written into a per-project `*_state.md`, and every round ended by appending to a `*_reflections.md` diary. Nothing reads either file: the daemon already replays the task's session history into every round, so the phase hand-offs are re-based onto the round's closing summary (the message the next round inherits) and the durable lessons onto the memory index, which is part of the prompt. No phase logic is lost: the branches still fire, they just read the state from the session instead of a file. Both prompts keep their seven self-review questions, now as what the closing summary must answer. Two guards, each killed by a mutant in both directions: - prompt memory paths must land where the workspace-write sandbox trusts them (ported from #1225, which this change supersedes - its two prompt hunks are inside the section rewritten here) - a built-in template must not teach the retired mechanism, with an explicit PENDING_STATE_SWEEP set that may only shrink (promote / paper / journal are the remaining sweep stages) Measured: rendered prompts contain 0 references to the retired mechanism (33418 and 18564 chars); full suite 1960 passed, 1 skipped. * emrg: retire the state-file mechanism from the paper template Stage 2 of rant 2026-09-14T14:35:47 ("the session itself is the memory"). Stage 1 (#1226) swept open_source_prompt.md and competition_prompt.md; this sweeps paper_prompt.md and drops it from PENDING_STATE_SWEEP in the same change. paper_prompt.md taught a per-task `paper_state.md` (current phase / last completed / next step / blocked / unhandled rants) plus an append-only `reflections.md` diary, and every hand-off was a line written into one of them. Nothing reads either file — the daemon replays this task's session history into every round (daemon.py, under a fixed session_id), so the session is the state, and the memory index is the durable layer. The phase logic is not lost, it is re-based: the phase assessment already reads the project files (literature/heilmeier-catechism.md, result logs, draft .tex, the experiment log), which is where the research state actually lives; the round's closing summary carries "last completed / next step / blocked"; and the rant log (~/.emrg/rants.jsonl) replaces the hand-copied "unhandled rants" field. §6 "Reflect" becomes "Reflect and Record": the same seven questions, answered in the closing summary, with durable lessons going to memory entries instead of a diary file. Verified: no mechanism reference left in the template; paper_prompt.md is dropped from PENDING_STATE_SWEEP in this same commit (the set keeps meaning "not yet done"), and test_paper_template_renders_with_context now pins the new contract instead of asserting the retired path renders. Both the sweep guard and that render test fail when the mechanism is re-introduced into the swept template (mutant restored byte-for-byte). * emrg: catch the bare "state file" instruction the sweep's fingerprint missed Sweeping a template is only half the work: the guard that says "no residue anywhere else" has to be able to see the residue. Measured this cycle, paper_prompt.md — already swept on this branch — still told the agent to read Agent.md / abstract / state file to determine direction terms to derive its arXiv keywords. A file nothing reads, named as a source of the search terms, for a phase that produces the round's literature scan. It survived because the fingerprint spelled the prose form as "the state file": an instruction that writes the noun phrase without the article was invisible, and a guard that reports a clean sweep by not looking is worse than no guard. Off by one word, in other words, and the same class as the rest of this branch: the mechanism outlived the change that retired it because nobody re-read what the check actually looked for. * paper_prompt.md: drop the state file from the read list (the direction terms come from Agent.md / the abstract, which the phase reads anyway). * test_prompt_templates.py: match the bare noun phrase, not only the article form, and cover the reflection-file paraphrase too — while keeping the sweep's own denial sentences legal ("there is no state file — the session itself is the state" is the replacement text, and flagging it would forbid saying what replaced the mechanism). A new test pins both halves of the pattern: the instruction form that must be flagged, and the denial that must not be. Verified: the widened pattern flags paper_prompt.md and nothing else among the swept templates, and still flags the two templates left in PENDING_STATE_SWEEP, so the set keeps meaning "not yet done". Full suite on this branch 1965 passed / 3 skipped. Both new checks were killed by a mutant and the files restored byte-for-byte (sha256): restoring the old fingerprint fails the coverage test, and re-adding the instruction to paper_prompt.md fails the sweep guard. * emrg: the memory write root's index is not the index in the prompt Three paragraphs the sweep re-based onto memory entries under `{{ evolution_cwd }}/.emrg/memory/` also called that directory's index "part of this prompt". It is not. `_collect_memory_data` embeds `session.cwd/.emrg/memory/MEMORY.md` — the *task project's* index — and the session index, and never `{{ evolution_cwd }}/.emrg/memory/MEMORY.md`. Measured by pointing `EVOLUTION_CWD` at a directory whose memory index carries a marker and rendering the system prompt through the real builder: the marker stays out while the project's appears. On this installation the two roots differ by construction — `{{ evolution_cwd }}` is `~/.emrg/evolution` (988 files, the durable record) while the embedded index belongs to the session's cwd, `{{ evolution_cwd }}/emrg`. The pairing is what was false, not the path: writing memory entries under that root is correct (the workspace-write sandbox trusts it), and the sentences that say "the memory index is embedded in this prompt" without naming a path are correct and stay. Each paragraph now points at the `read` tool instead, so the agent is told where its own entries are rather than where they are not. A guard pins the pairing (`test_no_template_calls_the_write_root_index_its_own_prompt_index`): a paragraph that names the root and claims prompt-embedding fails, with a non-vacuity floor of 3 paragraphs naming the root and a self-test that the detector still detects the shape it was written for. Re-planting the false sentence turns it red; the file is byte-restored afterwards (sha256 equal). Suite on this tree: 1975 collected = 1972 passed + 3 skipped. --------- Co-authored-by: EMRG Evolution <emrg@argszero.dev>
What
open_source_prompt.mdtold the agent to write two things to{{ evolution_cwd }}/memory/— the identity file (identity-github-role.md) and the end-of-cycle "key findings" step. This points both at{{ evolution_cwd }}/.emrg/memory/.Why
{{ evolution_cwd }}is~/.emrg/evolution, so{{ evolution_cwd }}/memory/is~/.emrg/evolution/memory/— and that path is wrong twice over:workspace-writetrust list covers~/.emrg/evolution/.emrg/, not~/.emrg/evolution/. Every write these instructions demand is refused with "blocked write outside workspace". (The governing rant, 2026-09-14T14:35:47, reports an open-source task hitting exactly that refusal 32 times in one day.)~/.emrg/evolution/.emrg/memory/. The wrong root exists and holds 36 stray files nothing reads; the trusted root holds 980. So even a write that slipped past the command-line scan (anopen()inside a heredoc, as the rant documents) would land in a folder no memory loader ever consults — instructions that look obeyed and produce nothing.These two sites are the only
{{ evolution_cwd }}write targets that are not part of the state-file / reflection-file mechanism, which the same rant deletes wholesale; that removal is staged separately and this PR deliberately does not pre-empt it.The guard
test_prompt_memory_writes_land_where_the_sandbox_allows_themwalks every builtin template, extracts each{{ evolution_cwd }}-rooted path that routes through amemory/segment, and asserts the sandbox trusts it. The expected boundary is read frombash_tool._trusted_write_zones()— not copied into the test — so if the trust list moves, this test reports "the prompts may be stale" rather than agreeing with a second copy of the rule.It cannot pass vacuously: a self-test first asserts the shape the rant measured (
{{ evolution_cwd }}/memory/identity.md) is genuinely outside every trusted zone on this machine, and the scan must find ≥2 real sites.Named limit (in the docstring): only
{{ evolution_cwd }}-rooted memory paths are checked. State-file / reflection-file paths are out of scope by design.Verification
pytest tests/{{ evolution_cwd }}/memory/identity-github-role.md{{ evolution_cwd }}/memory/("key findings").../evolution/.emrg/memory/identity-github-role.md, findings →.../evolution/.emrg/memory/; wrong root absentpython -c "from emrg.client.app import run_client",python -m emrg --helpcheck-doc-count.py,check-node-test-count.py