Skip to content

emrg: promote keeps its state in the session, not in a file - #1422

Merged
argszero merged 1 commit into
masterfrom
docs/promote-keeps-its-state-in-the-session
Sep 19, 2026
Merged

argszero merged 1 commit into
masterfrom
docs/promote-keeps-its-state-in-the-session

Conversation

@argszero

Copy link
Copy Markdown
Owner

What

promote_prompt.md was the last template still teaching the retired state-file / reflection-file mechanism (rant 2026-09-14T14:35:47 — "the session itself is the state"). This sweeps it, and the PENDING_STATE_SWEEP set in tests/test_prompt_templates.py reaches empty, which is the completion signal that set was built to carry.

Changes

  • emrg/server/promote_prompt.md
    • §0.1 becomes Cross-round continuity (there is no state file): the daemon replays this task's session into every round, so the history plus the embedded session-memory index are where the last round left off. Three things are deliberately not reconstructed from the session — channel availability and login state (re-probe the real browser, §0.3), registered accounts (the memory entry plus the §0.3 browser check, never a second registration), and published posts and their numbers (the platform's own page). Reality is always fresher than a note.
    • §4 becomes Recording (the closing summary): §4.1 is the round snapshot the next round reads, §4.2 names the durable facts that live in session memory entries (channel accounts, blog posts, blog drafts, banned list, the cumulative mention stats / promotion metrics trend, the periodic method verdict), §4.3 keeps the PR emrg: promote_state.md housekeeping + categorization rules (rants 2026-08-24T15:27:37/15:28:41) #957 convergence discipline (the carrier changed, the discipline did not), §4.4 is the four-zone categorization.
    • §5 becomes Reflection (answered in the closing summary, mandatory every round) — the same 8 questions, answered in the round's closing summary instead of appended to a log.
    • Every scattered "record it in the state file" reference (blocked, blog posts / blog drafts, promotion opportunities, homework record, flagged/negative, mention stats, promotion metrics, filed issues, the pitfalls table, §6 Act) now names the closing summary or the memory entry that actually carries it.
  • tests/test_prompt_templates.py
    • PENDING_STATE_SWEEP is now an empty set with an assertion that it stays empty: a template re-listed there would otherwise pass the "still mentions the mechanism" loop and silently re-open the set. Both directions are mutation-verified (see below).
    • The widening test test_widened_fingerprint_is_measured_on_the_real_templates no longer measures against a live pending template (there is none). Its positive control is frozen: the 5 lines the widening gained on promote_prompt.md are pinned verbatim, and each must still match the widened pattern while still being missed by the narrow one. Narrowing the pattern again turns it red — the property the == 5 assertion used to protect — without tying the guard's own control to the residue it exists to remove. The other direction stays live: no built-in template may be flagged, and the replacement text may not be either.

Measurement

  • Full suite on the branch: 3559 passed, 21 skipped (148s).
  • Import + CLI: from emrg.client.app import run_client OK; python -m emrg --help OK.
  • Guards: check-doc-count.py OK, check-node-test-count.py OK, check-rant-citations.py OK (56 citation sites, no frozen debt).
  • Rendered with jinja2: promote_state.md absent, reflections.md absent, zero hits under the widened retired-mechanism pattern; meanwhile the field vocabulary the prompt still relies on renders (channel accounts, blog posts, blog drafts, banned list, homework record, mention stats, promotion tracking, "Register one account per channel, once").
  • Mutation arms (all restored byte-for-byte afterwards):
    1. plant §0.1's state-file section back into the template → 2 failed (residue guard + widening guard);
    2. re-list the swept template in PENDING_STATE_SWEEP → the new emptiness assertion fires;
    3. plant the residue and re-list it as pending — the case only the new assertion can catch, since the "still mentions the mechanism" loop is satisfied by definition → red.

Note on the remaining rant item

The rant's other remaining acceptance item is host-side and outside this repo and this task's sandbox: the ~8.9MB of *_state.md / *_reflections.md under ~/.emrg/evolution/. Nothing in this change reads them (they left the code path when the mechanism was retired), so their deletion is not a precondition for this PR.

@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 cyc20260919-152659

Reviewed the diff and verified it in both directions before voting. This is the last template of the state-file / reflection-file sweep (rant 2026-09-14T14:35:47), so I looked at what it keeps, not only at what it deletes.

Both arms, measured by this cycle (PR tree into a scratch worktree; the repo's own venv via PYTHONPATH=<tree>):

  • ARM A (head f2bc862e): tests/test_prompt_templates.py9 passed.
  • ARM B (same tests, master's emrg/server/promote_prompt.md swapped in): 2 failed, 7 passed — the sweep guard fails against the un-swept template, so the guard and the template land as one change rather than as a test that would pass either way.

The two things I checked beyond the test file.

  1. The facts are re-homed rather than dropped. §4.2 puts the durable lists (channel accounts, blog posts, blog drafts, banned list, the cumulative mention stats / promotion metrics trend) in session memory entries, and §0.1 says which three bodies of fact must be re-read from reality instead of from a note (browser login state, registered accounts, platform numbers). That matters here specifically: the retired file held the account list, and the prompt's own red line 2 forbids registering a duplicate — a blind deletion would have left that rule with nothing to check against.

  2. The new memory path is the index the prompt actually embeds. The guard test_no_template_calls_the_write_root_index_its_own_prompt_index exists because a writable root must not be described as loaded. promote_prompt.md now names {{ source_dir }}/.emrg/sessions/{{ session_id }}/memory/, and daemon._collect_memory_data embeds session.memory_dir / "MEMORY.md" (emrg/session.py:65 puts it under the session dir), so that path is the embedded one — unlike {{ evolution_cwd }}/.emrg/memory/, which the guard already forbids pairing with that claim.

The guard hole this closes. With no pending template left, test_widened_fingerprint_is_measured_on_the_real_templates can no longer measure against a live one; the PR freezes the five lines the widening gained as its positive control (each must still match the widened pattern and still be missed by the narrow one) instead of re-fitting a number. And PENDING_STATE_SWEEP is now empty plus an assertion that it stays empty: without that assertion, a template re-listed as pending and still carrying residue satisfies the "still mentions the mechanism" loop by definition, so the set could be silently re-opened. Reaching empty is what "no residue" meant, so asserting it is the point.

@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 cyc20260919-155217

Reviewed as a stale head: check-merge-freshness.py 1422 reports STALE (head f2bc862e, base f4e7328d, behind_by=2), so I did not refresh it — a push would move the head and void the vote already standing. Instead I measured the tree the merge would land: check-merge-plan-suite.py 1422 on base 3114adee → final tree f9247298911d, suite OK: 3642 passed / 22 skipped. This review is about that tree.

Both arms, measured by this cycle (PR tree into a scratch worktree; the repo's own interpreter):

  • ARM A (head f2bc862e): tests/test_prompt_templates.py9 passed.
  • ARM B (same tests, master's emrg/server/promote_prompt.md swapped in): 2 failed, 7 passed — the sweep guard fails against the un-swept template, so the guard and the template land as one change rather than as a test that would pass either way.

What the review checked beyond the test file. This is the last template of the state-file / reflection-file sweep, so the interesting question is what it keeps:

  1. The facts are re-homed, not dropped. §4.2 moves the durable lists (channel accounts, banned list, blog posts and drafts, the cumulative mention/promotion trends) into session memory entries, and §0.1 names which bodies of fact must be re-read from reality instead of from a note. That matters here specifically: the retired file held the account list, and this prompt's own red line 2 forbids registering a duplicate — a blind deletion would have left that rule with nothing to check against.
  2. The new memory path is the index the prompt actually embeds. The prompt now names {{ source_dir }}/.emrg/sessions/{{ session_id }}/memory/, which is session.memory_dir / "MEMORY.md" — the file daemon._collect_memory_data embeds — unlike {{ evolution_cwd }}/.emrg/memory/, which the existing guard forbids pairing with that claim.
  3. The widening guard keeps a positive control after the pending set empties. With no pending template left there is nothing live to measure the widened fingerprint against, so the five lines the widening gained are frozen as the control (each must still match the widened pattern and still be missed by the narrow one) instead of re-fitting a count.

PENDING_STATE_SWEEP is now empty with an assertion that it stays empty, which is the part that could otherwise be re-opened silently: a template re-listed as pending and still carrying residue satisfies the "still mentions the mechanism" loop by definition. Reaching empty was the task; asserting it is what keeps it reached.

@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 cyc20260919-162257

Reviewed as a stale head and voted on the landing tree, not on the head: check-merge-freshness.py 1422 reports STALE (head f2bc862e, base f4e7328d, behind_by=3), with 2 valid votes standing. A refresh moves the head and the counter voids both, so the head was left alone and the tree the merge would land was measured instead — scripts/check-merge-plan-suite.py 1422 → final tree e6c51f5e770b, suite OK 3677 passed / 22 skipped. check-merge-order.py 1422 1423 1424 reports 0 of 3 pairs conflicting, and all 6 ordered pairs merge into a healthy tree.

Both arms measured by this cycle, not carried over: ARM A (the PR tree) tests/test_prompt_templates.py9 passed; ARM B (the PR's tests against master's emrg/server/promote_prompt.md) → 2 failed / 7 passed. The two failures are test_retired_state_file_mechanism_is_gone_or_being_swept and test_widened_fingerprint_is_measured_on_the_real_templates, i.e. the guard really does redden on the residue it exists to catch.

What the change does, verified by reading it rather than the PR text:

  • PENDING_STATE_SWEEP is now empty and asserted empty (assert not PENDING_STATE_SWEEP), and the final loop no longer skips pending templates — so the guard got stronger as the residue went away, which is the direction that matters.
  • The sweep re-homed the facts instead of deleting them: §0.1 reads continuity from the session history plus the embedded memory index and names the three bodies of fact deliberately not reconstructed from a note (channel login state, registered accounts, published numbers) with the reason each is fresher read from reality.
  • The frozen widening control is faithful, which I measured rather than assumed. The docstring says the five strings are "taken verbatim"; they are in fact the reduced forms of the pre-sweep lines 38/329/331/363/376 with the {{ source_dir }}/.emrg/sessions/{{ session_id }}/ segment elided. That reduction is cosmetic for the property under test: against the test's own compiled _RETIRED_MECHANISM, every real line and the frozen string standing in for it share the same two readings (widened=True, prefix-free=False), which is exactly the discrimination the control claims. Non-blocking wording nit for a follow-up, not a defect in the guard: a reader who greps the strings against history will not find them.

Residual scan on the swept template finds 4 hits, all of them the negative statements the replacement text is supposed to make ("there is no state file", "no reflections file to update") — the guard's own docstring says a line that denies the file stays legal, so this is the intended shape, not leftover residue.

Not refreshing this head: that is the deliberate choice the freshness check prescribes when votes are at risk, and I have no change to propose that would justify voiding two votes.

@argszero
argszero merged commit 0e558c0 into master Sep 19, 2026
2 checks passed
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.

1 participant