agentm memory index - alexherrero/agentm GitHub Wiki
The memory index (V6-11) was the SQLite metadata table that let recall filter by
kind,project,tag, andstatusin one query alongside the vector search. Built 2026-07-06; retired 2026-08-10 with the vector index it sat beside β see the amendment log.--filtersurvives on the corpus walk, which applies the same criteria as it goes. It lifts the queued V6 plan into a tracked design, reconciled to the built index and extended with the fingerprint column + two new memory kinds; parent agentm HLD, sibling to memory system.
Recall today runs a vector search plus a grep over each entry's frontmatter β there is no structured way to ask for "the security entries in project: sherwood, updated this month." V6-11 adds that: a SQLite metadata table beside the existing vector index, joined by row id, so a recall is one SQL WHERE over kind / project / tag / status AND a vector MATCH in the same query. It was the substrate the diagnostics recall ladder and token-audit's session-cost capture both waited on β both blockers are now cleared; each capability's own build is still ahead of it, on its own merits.
The contract for this lived only in the queued V6 plan in the vault, which no tracked design referenced β so four dependents read as buildable while resting on an unwritten substrate. This design lifts that plan into the governed tree, reconciles it to what the index actually is today (a four-column table awaiting additive columns), and folds in the three extensions the AG track added: the fingerprint column, and the session-cost and failure-incident memory kinds.
Markdown stays the source of truth. The index is a derived, device-local cache the recall loop reads; this slice widens its metadata so a query can filter and rank in one pass.
Markdown is the source of truth; the drain walk hydrates the two row-id-joined SQLite tables; hybrid recall is one SQL WHERE + a vector MATCH. Grep stays the graceful fallback; the fingerprint column feeds diagnostics.
The index is real and device-local (the memory system's local-index tier, never synced):
-
vec_index.pyβ aentriesvec0 virtual table (FLOAT[1024]) plus a metadata tableentry_meta(rowid, path, updated_at, indexed_at)β four columns, joined to the vectors byrowid. It has a JSONL embedding queue, an async drain, drift detection (the source ofindexed_at), and a dimension-mismatchrebuild_index. -
recall.pyβ the recall loop: tokenize β vector search β drift-check-and-drop β grep over slug, tags, title, and the first 500 chars β merge (sim Γ 0.85 + keyword Γ 0.05) β top-5. It graceful-degrades to grep-only when sqlite-vec or embeddings are absent.
V6-11 extends this; it does not replace it.
Add columns to the built entry_meta by additive, idempotent migration (an ALTER TABLE ADD COLUMN guarded by a column check, mirroring the existing _migrate_pre_v37 step):
| Column | Source |
|---|---|
kind Β· status Β· slug Β· project Β· created
|
the entry's frontmatter |
tags (JSON array) |
frontmatter |
group_name |
frontmatter group (renamed β group is a SQL keyword) |
fingerprint |
auto-computed by save_entry() via fingerprint.py's compute_fingerprint() β a sha256 hash over the normalized body β for every new entry; an explicit caller value (the diagnostics ladder's semantic incident key) overrides |
Row id stays the join to entries. The rebuild_index contract extends to repopulate these columns from frontmatter on a full-sync, alongside the embeddings; its CREATE TABLE entry_meta must carry the new columns too, or a rebuild would drop them. The design declares which columns are indexed (kind, project, status carry a CREATE INDEX for the filter path). The built table starts at four columns, so the migration is purely additive.
The index is device-local and never synced, so a fresh machine has the synced markdown but no index β it builds one from the vault. The markdown is the source of truth; the index is a derived cache rebuilt locally from it, the same way on a new device, after a wipe, or after a corruption.
The cold build reuses the two built operations. full_sync --rebuild walks the vault (private/ Β· projects/ Β· incubator/) and enqueues every entry; drain then embeds each one and populates both tables β the entries vectors and the entry_meta row, with the V6-11 metadata columns read from each entry's frontmatter in the same pass. The build is resumable: the enqueue is a plain JSONL append and the drain is cursor-backed, so an interrupted first sync continues where it left off and a re-run is idempotent.
The two halves split by dependency: enqueueing needs no sqlite-vec or embedding model (it is an append), so the walk runs anywhere; only the drain needs the local embedding model. Embedding every entry is a one-time per-device cost, and recall graceful-degrades to grep-only until the build finishes β so a fresh agent keeps recalling (without the vector half) while the drain catches up. The fresh index is created with the extended schema, so a cold build produces the full metadata table directly. A dedicated reindex convenience subcommand is anticipated; full_sync --rebuild is that path today.
Add a --filter path to recall.py query: an expression like tag=security AND project=sherwood compiles to one SQL WHERE over the metadata, joined with the vector MATCH, in a single query. This replaces the grep-over-frontmatter pass for the filtered case, while grep stays the graceful fallback when sqlite-vec is absent. The merge weighting is unchanged (sim Γ 0.85 + keyword Γ 0.05 β the live constants; the 0.7 / 0.3 in the comments is stale). Operator surfaces follow: /memory search --tag --project, /memory list --kind --updated-since. Out of scope (from the source plan): materializing entry bodies into SQLite, and LLM-extracted metadata.
kind is already a free-form kebab-case taxonomy (a path segment + a display header), so the two new kinds are reserved values of the existing kind taxonomy:
-
session-costβ vestigial (delivered 2026-07-07). Reserved for token-audit's session-cost capture; the Autonomy arc's observability design has now retargeted that capture off the vault onto a device-local telemetry ledger (PLAN-observability-ledger, both repo halves merged) β nothing writes this kind anymore. The vault entries already written stay as historical record; no data migration was needed. -
failure-incidentβ the kind the diagnostics recall ladder writes. Its write carries a mandatory privacy scrub, because failure context is untrusted and PII-bearing β a persistence-boundary guard the write cannot skip.
The fingerprint is a real column, and as of PLAN-auto-org-dedup-and-lint task 1 (2026-07-18, auto-organization part 3) it has a universal writer, not just a lookup key the diagnostics ladder happens to populate: save_entry() auto-computes a stable sha256 hash over the note's normalized body (fingerprint.py's compute_fingerprint() / normalize_body() β CRLFβLF, per-line strip, whitespace-run collapse, blank-line drop, casefold; punctuation and wikilinks stay significant, deliberately favoring false negatives over false positives) for every newly written note. An explicit caller-supplied value still wins, so the diagnostics recall ladder's semantic incident join key keeps its meaning unchanged; for a failure-incident write, the mandatory PII scrub runs before the hash, so the fingerprint reflects the scrubbed body actually persisted. Best-effort β a fingerprint failure never blocks the write.
The memory system reserves a DerivedMaintenance extension point for exactly this kind of derived index (it points the reader at the storage seam, though the seam text does not yet carry the reservation), and nothing implements it yet β the built vec_index.py is a parallel working implementation. This slice builds on vec_index.py (its drain, drift detection, and rebuild already work) and names DerivedMaintenance as the contract it eventually satisfies, with a re-audit trigger so the reconciliation stays on record.
-
vs memory system β the memory system owns the recall loop, the capture path, the tiers, the local-index tier, and the
DerivedMaintenanceextension point; this design is the indexed-recall slice it marks designed-for (the V6 milestone), specifying the metadata table + the hybrid query, and a futureDerivedMaintenanceimplementer. -
vs storage seam β the seam owns the read/write verbs, the
Locatorguards, and the T1/T2/T3 ownership tiers; this index is a derived device-local cache the recall path reads, beneath that contract. - vs the rest of V6 β V6-11 is the metadata table only. The hybrid-rank engine (V6-3, RRF over BM25 + vector), the typed-entity graph (V6-2), and chunking (V6-10) stay deferred; this slice is pulled forward on its own because its dependents need structured filtering; ranking stays deferred.
-
extends the built index β
vec_index.py(the table + drain + rebuild) andrecall.py(the query path). -
rides the memory system's local-index tier (device-local, never synced); a future
DerivedMaintenanceimplementer (the extension point memory-system reserves). -
feeds diagnostics (the
failure-incidentkind + thefingerprintrecall ladder); thesession-costkind is vestigial (see below) β token-audit no longer consumes it, now thatPLAN-observability-ledgerhas landed. -
composes privacy β the mandatory scrub on a
failure-incidentwrite. - Points up at the agentm HLD Β§Memory; the recall loop + tiers are the memory system.
-
This is a lift. The canonical V6-11 contract lived only in the queued V6 plan in the vault (
_harness/ROADMAP-AgentMemoryV6.md). This design migrates it into the governed tree, reconciled to current truth. Re-read the live source at fold in case it drifted since it was queued β the schema here is reconciled from the queued spec plus the AG-added axes. -
Additive migration only. The built
entry_metais four columns; the migration isALTER TABLE ADD COLUMN(idempotent, guarded), back-filled on the nextfull-sync. A clean-slateCREATE TABLEwould mis-describe current truth and risk a drop-and-recreate. -
At lift (docs): add the
agentm/memory-indexarea to the area-taxonomy; have memory system point to this slice as the V6-11 specification; downgrade the dependents' status language from "designed" to "blocked on V6-11" where they don't already say so (the runner already does).
- Buildability illusion. Until this lands, the diagnostics first-slice deterministic layer, token-audit session-cost capture, the runner health-check report, and the dreaming session-cost review read as independently buildable while resting on an unspecified substrate. The lift closes the spec gap and re-points those dependents.
-
The parallel-implementation tension.
vec_index.pysidesteps the seam's reservedDerivedMaintenance. Building onvec_index.pyis the pragmatic call, but the seam's never-implemented contract rots silently if this is left unsaid β hence the named re-audit trigger. -
failure-incidentis a leak surface. Its write carries untrusted, PII-bearing context; the mandatory privacy scrub is a persistence-boundary guard the write cannot skip. - Source-of-truth drift. The lifted spec was queued 2026-05-27; re-read the live vault plan before folding in case it changed.
-
Re-audit triggers: re-read the live V6 plan at fold; reconcile onto
DerivedMaintenanceif/when the seam path is implemented; flip[PENDING-IMPL]as the table + hybrid query land; revisit when V6-3 (RRF) arrives to consume the table.
- A standalone Wave-B slice. Ship the metadata table + SQL filtering + the fingerprint on their own to unblock diagnostics and token-audit; the source plan's pairing with V6-3 (RRF) is deferred β V6-3 consumes this table later.
-
Additive migration only. Extend the built four-column
entry_metaby guardedALTER TABLE ADD COLUMN, mirroring_migrate_pre_v37; the rebuild path'sCREATE TABLEcarries the new columns too. -
group_namefor the frontmattergroupkey. The baregroupis a SQL keyword. -
The two new kinds are reserved
kindvalues.session-costandfailure-incidentride the existing free-formkind; thefingerprintis the one real new column. -
Build on
vec_index.pynow;DerivedMaintenanceis the eventual contract (re-audit trigger recorded). - Markdown stays the source of truth. The index is a derived, device-local cache; nothing here moves authority into SQLite.
-
Lifted from: the queued V6 plan (
_harness/ROADMAP-AgentMemoryV6.mdβ the V6-11 metadata-table spec) -
Extends (built):
harness/skills/memory/scripts/vec_index.py(the four-columnentry_meta+entries+ drain + rebuild) Β·recall.py(the five-step loop + thesim Γ 0.85 + keyword Γ 0.05merge) Β·fingerprint.py(thefingerprintcolumn's universal writer βcompute_fingerprint()/normalize_body(), wired intosave_entry(); landed viaPLAN-auto-org-dedup-and-linttask 1) -
Composes: memory system (the recall loop + tiers + the local-index tier + the
DerivedMaintenancereservation) Β· storage seam (the read/write verbs + ownership tiers) Β· privacy (the failure-incident scrub) -
Consumers: diagnostics (the
failure-incident+fingerprintrecall ladder) Β· the runner health-check report.session-costis vestigial β see the kinds section above. - Up: agentm HLD Β§Memory Β· memory system (the V6 indexed-recall trajectory)
Newest first. Collapses to one β€2-paragraph entry at finalization; git holds the granular history.
2026-07-18 β fingerprint gets a universal writer (auto-organization part 3, PLAN-auto-org-dedup-and-lint task 1). The column existed since the 2026-07-06 build but had no writer of its own β it stayed NULL unless a caller passed one explicitly, so the schema table and the kinds section above described it as a bare lookup key the diagnostics ladder happened to populate. Task 1 of the dedup-and-lint plan adds harness/skills/memory/scripts/fingerprint.py (compute_fingerprint() / normalize_body() β a sha256 hash over the normalized body, conservative by design: CRLF/whitespace/case folded, punctuation and wikilinks left significant, favoring false negatives) and wires it into save_entry(), so every newly written note now gets a real content-hash fingerprint automatically. An explicit caller-supplied value still wins β the diagnostics ladder's semantic incident join key is unchanged β and for failure-incident the mandatory PII scrub runs before the hash, so the fingerprint reflects the scrubbed body actually written; best-effort, a fingerprint failure never blocks the write. This closes the "real column, no writer" gap described above; the column is no longer aspirational for ordinary entries. Not yet built: the write-time exact-duplicate dedup guard and cluster-aware collapse this fingerprint feeds (auto-organization's dedup section) β those are later tasks in the same plan.
2026-07-07 β session-cost retarget delivered (AA3, PLAN-observability-ledger agentm half). Flips the AA2 entry below from "stops being written once the plan lands" to as-built: both repo halves have merged (crickets' writer/reader repoint, agentm's runner-hosted SQLite aggregator over the resulting event log), so session-cost is now confirmed vestigial rather than merely scheduled to become so. Left as a reserved value (no data migration needed β the vault entries already written stay as historical record) rather than removed from the taxonomy.
2026-07-07 β session-cost kind marked vestigial (AA2). The Autonomy arc's observability design retargets token-audit's session-cost capture off the vault onto a device-local telemetry ledger; this kind stops being written once PLAN-observability-ledger lands the move. Left as a reserved value (no data migration needed β the vault entries already written stay as historical record) rather than removed from the taxonomy.
-
2026-08-10 β retired with the vector stack;
--filtersurvives on the walk. The metadata table this design specifies lived invec_index.py's database, joined to the vectorMATCHby row id β so when the vector stack was removed (see week-1 experiment) the table went with it. What survives: the--filtergrammar and its semantics.parse_filterand_entry_matches_filterare unchanged inrecall.py, and_bm25_search/_grep_searcheach apply the criteria as they walk β the path that was always the fallback when sqlite-vec was absent is now the only implementation. What is gone:_vec_search_filtered's single-query SQL join, which was the optimization this design existed to add, and the eightentry_metacolumns behind it. The one non-filter dependent,dedup_guard.find_vault_duplicate, resolved a fingerprint through the same table and had no other lookup available; it is gone too, recorded in the week-1 amendment log. Whysupersededrather than an edit in place: the objective section's premise β that a structured filter needs a SQL table beside a vector index β is what stopped being true, not a detail inside it.governs:drops to[]rather than moving torecall.py: the surviving grammar lives there, butagentm-memory-system.mdalready governs the wholeharness/skills/memory/scripts/directory, and a retired design should not hold a live file β the same shape recall trace already uses. Re-audit trigger: a corpus large enough that the filter walk stops fitting an interactive budget, which is the condition that would justify an index again β and it would be a fresh one, not this design. -
2026-07-06 β built (AG Wave B leader 3/5).
_migrate_v6_11(additiveALTER TABLE ADD COLUMN, guarded, mirrors the existing_migrate_pre_v37pattern) +_ensure_v6_11_indexes+_extract_meta_from_file(frontmatter β the eight columns,projectderived fromgroup:) ship invec_index.py;upsert_entrypopulates them. The hybrid--filterpath ships inrecall.py(parse_filter/_entry_matches_filter/_vec_search_filtered) β a single SQLWHEREjoined with the vectorMATCH, grep-fallback preserved.session-costandfailure-incidentare recognizedkindvalues; the latter's mandatory privacy scrub ships as a self-containedprivacy_scrub.py(agentm-native regex redaction β composing crickets'privacycapability directly would invert the one-way capability bridge, since agentm must never import crickets code).governs:now points atvec_index.py. Operator-facing CLI subcommands (/memory search --tag --project,/memory list --kind --updated-since) are not built β those are a thin wrapper layer aboverecall.py query --filter, which works end-to-end. Re-audit trigger: reconcile ontoDerivedMaintenancewhen the seam path is implemented (unchanged from the 2026-06-26 entry below); confirm the operator-facing CLI wrapper if/when it's authored. -
2026-07-03 β
drainfails loud on a missing vault instead of returning stale-looking empty stats.vec_index.py'sdrainsubcommand now checksvault.is_dir()before callingdrain_queue()and exits 1 with an error if the vault directory doesn't exist, rather than falling through todrain_queue()'s default{"processed": 0, "skipped": 0, "errors": 0, "remaining": 0}β a snapshot indistinguishable from a legitimately empty queue. This is a deliberate narrow exception to the module's otherwise-consistent graceful-degradation philosophy: read-only walkers likefind_drifted_entriescorrectly stay graceful on a missing vault, butdrainis a mutating operation where "vault vanished mid-run" must be distinguishable from "nothing pending." Found while writingscripts/verify-vec-index.sh's fault-injection check (R1.4 / agentmExperience#0). Re-audit trigger: if another mutatingvec_index.pysubcommand is added, it should get the same existence check. -
2026-06-28 β lock-down sweep (operator review). All standing fixes clean (diagram sized; no mermaid; no ADR mentions; log already newest-first). Confirmed markdown stays the source of truth, the three AG-added axes (the
fingerprintcolumn + thesession-cost/failure-incidentreserved kinds, the latter with the mandatory privacy scrub on write), and the hybrid--filterrecall path. No content change. Locked as a v5βv8 guidepost. -
2026-06-26 β lifted the V6-11 metadata-table plan into the governed tree; approved + launched. The last Bucket-A substrate piece, and a lift: the canonical V6-11 contract lived only in the queued V6 plan in the vault, which no tracked design referenced, so its dependents rested on an unwritten substrate. This design migrates it, reconciled to the built index (the four-column
entry_metainvec_index.py, extended additively), and folds in the three AG-added axes: thefingerprintcolumn, and thesession-cost+failure-incidentreservedkindvalues. It specifies the extended metadata table, the hybrid--filterrecall path (one SQLWHERE+ a vectorMATCH, replacing the grep-over-frontmatter pass, grep kept as the graceful fallback), the build-from-source path (a fresh machine bootstraps the device-local index from the synced markdown viafull_sync --rebuildβdrain, resumable, recall grep-degrades until built), and the mandatory privacy scrub on afailure-incidentwrite. Locked: a standalone Wave-B slice (V6-3 RRF deferred); additive idempotent migration;group_nameover the SQL keyword; the two kinds are reservedkindvalues; build onvec_index.pywithDerivedMaintenancenamed as the eventual contract; markdown stays the source of truth. Re-audit: re-read the live V6 plan at fold; reconcile ontoDerivedMaintenancewhen the seam path is implemented; flip[PENDING-IMPL]as the table + hybrid query land.