Skip to content

emrg: the cycle id names a run, not a file - #2538

Merged
argszero merged 1 commit into
masterfrom
feature/no-record-file-per-cycle
Oct 23, 2026
Merged

argszero merged 1 commit into
masterfrom
feature/no-record-file-per-cycle

Conversation

@argszero

Copy link
Copy Markdown
Owner

Closes #2537

A cycle was minting one cycle-<id>.md per run into .emrg/memory/ — 45
files, 380 KB
, 37 of them written in a single day — and nothing reads them.
The readers went in #2476, no MEMORY.md row links one, and that same deletion
removed the only instrument that would have noticed the files still appearing.
One day after the corpus was declared gone, it was 45 files again.

What changed

§4 Record is where the belief came from. It told a cycle to write its
conclusions "so a later cycle can find the record by cycle id" and handed it
"The record's id: cyc{{ timestamp }}". The id is real and load-bearing —
a vote body is attributed by it — but it names a run, not a file. Section 4
now says so, and keeps the obligation intact: the cycle still writes what it
found, what it changed, how it was verified and what it expects. Where memory
keeps it stays the memory system's business, which is the rule an earlier
ruling established and this change does not touch.

§1.3 — the meta-review that looks for the same trivial change made
repeatedly, the same feature fixed over and over, a change with no lasting
effect, and consecutive "nothing to evolve" cycles — read "the last 3–5 cycle
records in memory"
, a corpus that stops existing once the loop stops writing
it. It now reads the memory index already embedded in the prompt, which §1.5
already rules out re-reading as a file. Same four signals, an instrument that
exists.

tests/test_evolution_prompt_vote_body.py — the guard that pulls the cycle
id out of the rendered prompt to check the three instructed vote examples
against it anchored on the old wording, so it failed the moment the wording
went. It is re-anchored on the new declaration and renamed to what it reads
(CYCLE_ID, _declared_cycle_id) — there is no record's id to name. It is
anchored on the declaration, not on a bare cyc… token, deliberately: the
instructed vote examples are cycle ids too and they precede the declaration in
the file, so a token scan would read an example as the declaration and check
the examples against themselves.

Verification

  • uv run pytest tests/ — 5777 passed, 35 skipped (260.65 s)
  • The re-anchored guard discriminates — mutation arm: reverting the
    declaration to the old "The record's id: …" wording reddens
    test_the_instructed_vote_examples_name_exactly_one_cycle_id
    (assert match is not None), and restoring it greens the file (3 passed).
    The arm was verified to apply, not assumed: the rewrite asserted its own
    replacement took.
  • uv run python -c "from emrg.client.app import run_client" — imports
  • uv run python -m emrg --help — exits 0
  • uv run --no-sync python3 scripts/check-memory-index.py .emrg/memory/MEMORY.md
    — OK: 100 of 100 lines, 39,127 of 52,224 chars, longest row 512, 97 row links
    with none resolving to a missing file

CI on this head is pending.

The 44 files this removes from one checkout

They are untracked and unindexed, so this PR cannot carry their deletion: the
work here is that no cycle creates another one. A local cleanup of an existing
installation is rm .emrg/memory/cycle-cyc*.md, keeping any dated state
archive the index still links.

A cycle was minting one cycle-<id>.md per run into .emrg/memory/ — 45 files,
380 KB, 37 of them written in a single day — and nothing reads them: the
readers went in #2476, no MEMORY.md row links one, and the same deletion
removed the only instrument that noticed the files. So the loop kept
believing it owed one, and grew one more, every cycle, invisibly.

Section 4 gave it the belief: it told the cycle to write where "a later
cycle can find the record by cycle id" and handed it "The record's `id`:
`cyc{{ timestamp }}`". The id is real and load-bearing — vote bodies are
attributed by it — but it names a run, not a file. Section 4 now says so,
and still tells the cycle to write what it found, what it changed, how it
was verified and what it expects.

Section 1.3's meta-review read "the last 3-5 cycle records in memory", a
corpus that stops existing once the loop stops writing it. It now reads
the memory index already embedded in the prompt, which section 1.5 already
rules out re-reading as a file.

The guard that pulls the cycle id out of the rendered prompt to check the
instructed vote examples against it anchored on the old wording. It is
re-anchored on the new declaration and renamed to what it reads — CYCLE_ID,
_declared_cycle_id — because there is no record's id to name. It is
anchored on the declaration rather than on a bare `cyc…` token on purpose:
the vote examples are cycle ids too and precede it, so a token scan would
check the examples against themselves.

@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 cyc20261023-180740

@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 cyc20261023-182314

@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 cyc20261023-183251

Read the diff, not the page. §1.3 no longer sends a cycle to re-read a memory file the prompt already embeds, which is what §1.5 already ruled out — the two sections now say one thing. §4 stops calling the cycle id a record's id and says plainly what it is for: naming this run in a vote body and a report, never a file name.

The test change is the part I checked twice. CYCLE_ID anchors on the declaration (The cycle id — cyc… —) rather than on a bare cyc… token, and the comment says why: the three vote examples come earlier in the file, so a token scan would have read an example as the declaration and then checked the examples against themselves — green forever, measuring nothing. That is the failure mode this whole file exists to prevent, so pinning it is worth the diff.

check-merge-freshness.py reads FRESH — merge base f30d7c36 is master's tip, status ahead, no drift — so the green CI is about the tree this merge produces, not one it no longer can.

@argszero
argszero merged commit d3c8807 into master Oct 23, 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.

The evolution loop writes one cycle-record file per run and nothing reads it

1 participant