feat(reindex): two-tier index — engine, write-time budgets, lane routing, skill - #56
Merged
Merged
Conversation
…ing, skill The always-loaded index bloats where it is written, not where it is cleaned. `MEMORY.md` is one of only two files that cost context at session start, yet the pointer budget was documented in a monthly curate pass while capture runs every session — so the index was out of compliance almost always. The restructure trigger compounded it by counting lines while the budget that actually blows is bytes: a store can sit at 60% of the line trigger and 110% of the byte budget. This lands the measurement engine, moves both budgets to the write path, makes the index two-tier by default, and adds the skill that performs the carve. - memory_reindex.py: new engine behind `okfmem reindex`. `--report` gives auto-loaded bytes vs ceiling, a per-section byte breakdown (so a single dominant block is visible), and per-index pointer counts, with on-disk pages labelled non-context. `--verify` walks every MEMORY*.md in both link syntaxes, attributes each dangling pointer to the index it came from, and exits non-zero. `--budget-check` counts per-pointer length in characters, not bytes. - okfmem-save: the 150-char pointer budget and an 8192-byte STATE.md ceiling are applied when the pointer is written; new pointers route to a lane index by default, with the root reserved for cross-lane and `type: feedback` pages. - okfmem status / okfmem-curate: the restructure trigger is MEMORY.md bytes over a stated ceiling, not a line count; the remedy is a lane split rather than tighter hooks, and split pointers are never re-flattened. Curate now reports auto-loaded bytes and tokens per session instead of file counts, and states plainly that deleting pages is not a context optimisation. - /okfmem-reindex: new skill — measure, cluster (including cross-cutting lanes), propose behind a hard approval stop, execute, verify. Two defects the review pass caught, both silent-wrong-answer: - Archive and graduate dropped pointers only from the root MEMORY.md. With pointers routed into lane indexes, the unattended consolidation Stop hook stranded a dangling pointer on every archived page, breaking the very `--verify` gate the new skills depend on. Both now enumerate every index, and graduate's rollback snapshots each one it writes. - The previous checker parsed one file and one link syntax, reporting hundreds of false orphans while a genuinely dangling pointer inside a lane index stayed invisible — wrong in the unsafe direction. The shell verification it shipped with used a BSD-incompatible `sed` that failed open on macOS; it is gone, not patched. Closes #52 Closes #53 Closes #54 Closes #55
s-annam
force-pushed
the
feat/54-reindex-two-tier-index
branch
from
July 31, 2026 23:44
502f7b8 to
c92e4f7
Compare
This was referenced Aug 1, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
The always-loaded index bloats where it is written, not where it is cleaned.
MEMORY.mdis one of only two files that cost context at session start, yet the pointer budget lived in a monthly curate pass while capture runs every session. The restructure trigger compounded it by counting lines when the budget that actually blows is bytes — a store can sit at 60% of the line trigger and 110% of the byte budget.This lands the measurement engine, moves both budgets to the write path, makes the index two-tier by default, and adds the skill that performs the carve.
memory_reindex.pyengine behind anokfmem reindexdispatcher route.--reportgives auto-loaded bytes vs ceiling, a per-section byte breakdown (so a single dominant block is visible — total file size alone can't tell you whether the remedy is tighter hooks or a lane split), per-index pointer counts, and on-disk pages explicitly labelled non-context.--verifywalks everyMEMORY*.mdin both link syntaxes, attributes each dangling pointer to the index it came from, and exits non-zero.--budget-checkcounts per-pointer length in characters, not bytes. Verified against a real store: 0 dangling / 0 orphans where the old checker reported 614 false positives.okfmem-save;STATE.mdgains a stated 8192-byte ceiling;okfmem-curatereports auto-loaded bytes and tokens-per-session instead of file counts, and states plainly that deleting pages is not a context optimisation (pages on disk cost zero context).okfmem-saveroutes new pointers to a lane index by default, with the root reserved for genuinely cross-lane andtype: feedbackpages. The restructure trigger becomesMEMORY.mdbytes over a ceiling rather than a line count, surfaced by bothokfmem statusand curate Phase 2. The remedy is a lane split, never tighter hooks; split pointers are never re-flattened./okfmem-reindexskill: five phases (measure via the engine, cluster lanes including cross-cutting ones, propose behind a hard approval stop, execute the carve, verify via the engine). Auto-wired byokfmem init's existing skill glob, inside the existingapply_configconsent gate.Closes #52
Closes #53
Closes #54
Closes #55
Test plan
python3 scripts/check-leaks.pyexits 0 (69 tracked files)python3 -m pytest tests/— 314 passed, 1 skipped (baseline before this branch: 292/1)ruff check .reviewed — only the 2 pre-existingE702s inplugins/memory_search.pyokfmem status,okfmem reindex --report,okfmem reindex --verify,okfmem init --dry-runall run cleanverify— CI gateAdversarial review
Two bounded review rounds ran against the accumulated diff before this PR existed, each reproducing findings end-to-end rather than reasoning about the code. Six blocking defects were found and fixed; every fix has a fail-before proof.
Round 1 — 3 blocking, all fixed:
memory_consolidate.pyandmemory_graduate.pydropped pointers only from the rootMEMORY.md. Composed with Two-tier index: route pointers to lane indexes, make MEMORY.md a map of content #53's lane routing, the unattended consolidation Stop hook left a dangling pointer behind on every archived page — silently breaking the very--verifygate both new skills depend on, with no user action. Reproduced:--verifyclean before,dangling: 1and exit 1 after a single consolidate run. Both now enumerate every index; graduate's rollback snapshots each index it writes (verified by injecting a mid-loop failure and confirming full restoration — a partial rollback would be worse than the original bug).awkone-liner blind to bare-form pointers (the form lane indexes actually use) and counting bytes on macOS, so em-dash pointers over-reported. It printed0/2where the truth was1/4. Replaced by the engine's--budget-check; no shell survives in that path.inventory.pycrashed under the managed-copy install. A new module-level import resolved four dirnames up, which does not hold under thecopytreefallback (Windows without symlink privilege). Produced a bareModuleNotFoundErrortraceback and no output. Now probes 4-up →$OKFMEM_ENGINE→~/okfmemand fails with one actionable line, never a traceback.Plus one finding promoted from nit to blocking:
--verifycounted retiredck_*.mdsnapshots as orphans, failing on 10 of 14 real projects for a known-benign reason. A gate that fails on most real stores is a gate nobody turns on. Now skipped via the siblings' exact convention (10 → 6, the remainder genuine).Round 2 — 3 blocking, all fixed:
Path.write_text, which translates to CRLF on Windows. Both would have failed only on thewindows-latestmatrix leg. Fixed by pinning the fixture newline and usingwrite_byteswhere the byte total is the point.project_inventorystill filtered pages root-only, so everyMEMORY-<lane>.mdcounted as a durable page —okfmem statusover-reporting on precisely the stores Two-tier index: route pointers to lane indexes, make MEMORY.md a map of content #53 tells users to create (635 vs 628 on a real store). Now delegates to the engine'spage_files(), with a regression test confirmed to fail6 == 2against the old filter.Round 3 — caught by CI, not by the loop. The first push failed the
windows-latestleg on a third instance of the same hand-typed-POSIX-literal class:tests/test_consolidate_index_drop.pyasserted"archive projects/demo/old-page.md"against output the code builds withos.path.relpath, which renders backslashes on Windows. Fixed by deriving the expected value fromos.path.join, and the whole diff was then swept for the rest of the class (the only other slashed literals are a1/1ratio in prose and the engine's markdown-link parsing, where/is correct on every OS). Worth recording: two review rounds plus a targeted audit still missed one, and the OS matrix is what found it.Surviving nits, deliberately not iterated on (recorded for the reviewer's eye):
INLINE_LINK_REdoes not skip inline code spans, so a.mdlink inside backticks parses as a pointer;classify_targetis case-sensitive on.MD; a bareokfmeminvocation survives inokfmem-save/SKILL.mdmatching that file's pre-existing house style;--budget(byte ceiling) and--budget-check(char budget) read as related but are not. Two pre-existing items were found and left for separate issues: curate Phase 1'sSLUG=$(pwd -P | sed 's|/|-|g'), whichencode_rootsupersedes, andplugins/memory_distill.pyreading only the root index.The round-2 fixes were applied and verified mechanically (fail-before proofs, the CRLF simulation, and a read-only check against a real store) but were not put through a third independent review round — the loop's cap is two.
Provenance
verifyEvery model string above is self-reported by the agent that ran that stage; the orchestrator requested an alias and cannot infer what it resolved to.