You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Date: 2026-09-01 · Verified against:main @ d6c8ac7, release v3.1.0 (run 33458631284) Status: Proposal for maintainer sign-off. Nothing here is implemented. Repo copy:docs/plans/v3.2-prove-it-plan.md on claude/phase-54-toolchain-drift-3k4fer (18d6638) Readable page:https://claude.ai/code/artifact/d559ad58-63e4-4a4d-808a-61f8b199ffda
This issue is the canonical home for the plan so it is visible outside .planning/. Decisions go in the comments; the four open ones are in section 10.
0. Thesis
v3.1 made the project's claims true. v3.2 makes them provable and operable: a real benchmark number committed next to the competitor figures, a quality artifact behind every "Solid" in the README status table, and a daemon someone can run for a week without a terminal open. No new retrieval capability ships unless a number says it is the bottleneck.
Same rules as v3.1, unchanged: execution-evidence (run-dependent requirements cite a committed artifact), reachability (cargo tree -i shows a shipped dependent), task pr-precheck green on every PR.
March roadmap — v3.1 Memory Export/Import (54–56), v3.2 Plugin Installer & OpenCode Converter (57–59), v3.3+ (--for all, --all, Gemini/Codex/Copilot registration)
.planning/ROADMAP.md on the gsd/ branches
Never merged; superseded 2026-03-26. Export/import built. Claude Code registration + plugin metadata built (all CREG/META boxes ticked). OpenCode converter built, now obsolete. Uninstall + Status not started. main has none of it
A parallel memory-orchestrator (orchestrator.rs, fusion.rs, rerank.rs, expand.rs…) — the branch was cut before feat(v3.0): Phase 51 Retrieval Orchestrator #28 landed main's orchestrator. This is a duplicate, not a modification. Never cherry-pick it.
2. Findings
F1 · Release pipeline has no guardrails — blocker
Evidence: today's first v3.1.0 push built acc7294 (Cargo.toml = 2.7.0, no Phase 54–57 code), published "Release 3.1.0", public for 17 minutes. .github/workflows/release.yml:
Get version (line 49) derives the version from the tag name only
no check that GITHUB_SHA is an ancestor of main
no check that workspace.package.version == tag
release: if: always() && !cancelled() (line 181) publishes whatever platforms succeeded; 7 of 13 historical runs were partial or failed
generate_release_notes: true (line 230) produces a PR-title list, not the CHANGELOG entry
The one guard that exists — four binaries per archive, added in #37 — ran for the first time today and held.
F2 · 99 commits of unmerged work — high
Evidence: section 1. The export/import feature is finished and tested (import_round_trip.rs); the Claude Code plugin registration is finished. Neither is on main. An import path is a backfill path (F5).
F3 · The real benchmark has never been run — blocker
Evidence: crates/memory-bench/src/cli.rs:14 (--backend mock|cli), judge.rs:101–126 (--scorer llm-judge reads OPENAI_API_KEY / ANTHROPIC_API_KEY, records model id), main.rs:137–147 (cli backend ingests via memory add and evaluates per question), benchmarks/scripts/download-locomo.sh. Only locomo-smoke.json (4 questions, mock/mock) is committed.
Harness gap for a real run: RunConfig (runner.rs:40) carries one endpoint; run_locomo (main.rs:134) loops conversations against it. The mock path gets a fresh MockStore per conversation; the cli path shares one daemon, so conversation N sees N−1's events — cross-conversation bleed the harness's own caveat names.
F4 · Retrieval quality evidence exists for one layer — high
Evidence: custom-harness fixtures (benchmarks/fixtures/*.toml) carry relevant = [...] and report recall_at_k — but every committed fixture is answerable by token overlap (expected_contains = ["JWT"] with JWT in the source). No fixture requires semantic retrieval. grep -rl "recall\|ndcg\|mrr" crates/memory-vector crates/memory-search crates/e2e-tests → lifecycle code only. memory-topics exposes cluster() and create_topics() (extraction.rs:51,96) with no quality metric anywhere.
F5 · Operational gaps — high
Backfill:IndexingPipeline::process_until_caught_up (memory-indexing/src/pipeline.rs:307) only drains the outbox forward from IndexCheckpoint; there is no path that re-reads events already past the checkpoint. admin rebuild-bm25 (memory-daemon/src/cli.rs:311) is a prune.
Daemonization:commands.rs:574 rejects --background with guidance to use systemd/launchd; nothing generates the unit.
Panic surface: 5 lock().unwrap() sites remain (vector_updater.rs:459, novelty.rs:709,745, sync.rs:43 + 1). A line-based grep counts 266 unwrap()/expect() in daemon+service, but it cannot exclude inline #[cfg(test)] modules (e.g. retrieval.rs:978+ is test code). The real production count is unknown. That is the first task of 61-03.
F6 · Planning source of truth is stale — medium
.planning/PROJECT.md: "Version: v3.0 (In Progress)"; adapter list includes OpenCode (removed in Phase 57); "API-based summarizer wiring" under Deferred though #27 shipped it (commands.rs:394resolve_api_key). Zero GitHub issues means the backlog is invisible outside .planning/.
F7 · Launch is coupled to F3 — medium
The blog post stands alone. The Show HN / r/rust / r/LocalLLaMA drafts are product posts; the positioning doc's own rule forbids the comparison those audiences ask first.
3. Target state (milestone exit)
One committed locomo_llm_judge result on the full dataset, real backend, hardware/model/dataset-SHA recorded, next to the competitor figures with a commensurability note.
Every "Solid" row in the README status table cites a committed quality artifact, or is relabelled.
memory-daemon install-service + admin backfill-index + admin rebuild-toc exist and are covered by e2e/bats; no unwrap() on a request path user input can reach.
PROJECT.md accurate; known gaps are GitHub issues; no orphan gsd/ branches; release pipeline refuses a bad tag.
4. Plan
Owner is agent unless it needs credentials, hardware, or a judgment call. Effort in agent sessions (one session ≈ one merged PR of v3.1 size).
Why: F1. Today's incident is reproducible by anyone with a stale local tag.
Files:.github/workflows/release.yml; new docs/RELEASING.md; CLAUDE.md Release Process section.
Steps:
Add a verify job before build, runs-on: ubuntu-latest, that:
git fetch origin main and fails unless git merge-base --is-ancestor "$GITHUB_SHA" origin/main
reads workspace.package.version with cargo metadata --no-deps --format-version 1 | jq -r '.packages[0].version' (or grep -m1 '^version' Cargo.toml) and fails unless it equals ${GITHUB_REF_NAME#v}; on workflow_dispatch compares to the input
fails unless CHANGELOG.md contains a heading for that version
Replace generate_release_notes: true with body_path: pointing at a file the job extracts from the matching CHANGELOG.md section (awk '/^## \[?'"$VER"'/{f=1;next} /^## /{f=0} f').
docs/RELEASING.md: the explicit-SHA procedure — git tag -a vX.Y.Z <sha> -m "...", git rev-parse vX.Y.Z^{commit} before push, what the guard will refuse and why. Note the zsh interactivecomments trap. Update CLAUDE.md to point at it.
Acceptance:
A tag on a commit not in main fails at verify before any build starts (test with v0.0.0-guardtest on a throwaway branch; delete it)
A tag whose version ≠ Cargo.toml fails at verify
A build-job failure on one platform yields no GitHub release
The release body is the CHANGELOG section verbatim
docs/RELEASING.md exists and CLAUDE.md links it
Verify: the guard-test tag runs above; actionlint clean; YAML parses.
Effort: 1 session · Owner: agent (tag pushes for the test: maintainer)
59-02 · Orphan branch triage
Why: F2. Decide with a conflict map, not a feeling.
Files (read-only this plan): the three gsd/ branches; output docs/plans/march-branch-triage.md.
Steps:
For each of backup.rs, import.rs, import_round_trip.rs, the ingest.rs/query.rs/db.rs/episodes.rs deltas, the proto additions, the memory-cli timeline/output deltas, and converters/claude.rs: git diff origin/main...origin/gsd/phase-58-claude-registration-metadata -- <path> and record: applies clean / conflicts / superseded.
For the proto additions: check message and RPC names against proto/memory.proto on main (no Export*/Import*/Backup* RPCs exist today, so likely clean; the streaming RPC style needs review against tonic version on main).
Explicitly mark the branch's memory-orchestrator and memory-bench directories do not port (parallel implementations).
Write the recommendation: cherry-pick list with commit SHAs, in order, for 61-01 (export/import) and 61-05 (registration); estimated conflict count per file.
After maintainer decision: delete gsd/phase-57-opencode-converter-registration immediately; delete the other two once 61-01 and 61-05 merge.
Acceptance:
docs/plans/march-branch-triage.md lists every ported file with clean/conflict status and the exact git cherry-pick/git checkout <sha> -- <path> sequence
Maintainer decision recorded in the doc (port / rewrite / drop)
PROJECT.md: Current State → v3.1 shipped 2026-09-01; Current Milestone → v3.2 (this plan); adapters list → Claude Code, Codex (Tier 1), Gemini, Copilot (Tier 2); move "API-based summarizer wiring" to Validated with the feat(v3.0): wire API-based summarizer from config (supersedes #26) #27 reference; add "true daemonization" → superseded by install-service (61-02) once decided.
REQUIREMENTS.md: replace the v3.0 block with v3.2 requirement IDs (REL-01..04, BENCH-10..13, QUAL-01..03, OPS-01..05, INST-01..03) mapped 1:1 to the plans below; keep the Future (v3.3+) list.
Open one GitHub issue per known gap, each linking its README row and its plan ID: real LOCOMO run, vector quality, topic quality, backfill, daemonization, TOC rebuild, cross-encoder, uninstall/status. Label v3.2. Link each as a sub-issue of this one.
ROADMAP.md/STATE.md: add v3.2 phases 59–62 in the existing format.
Acceptance:
grep -n "v3.0 (In Progress)\|OpenCode" .planning/PROJECT.md → none
8 open issues labelled v3.2, each with a plan ID
Every requirement ID in REQUIREMENTS.md appears in exactly one plan
A. Spawn-per-conversation.RunConfig gains daemon_bin: Option<PathBuf>, isolation: Isolation::{Shared, DaemonPerConversation}. For each conversation: create a tempfile::tempdir(), spawn memory-daemon start --db-path <tmp> --port <free port>, wait for memory-daemon status healthy, run ingest + evaluate against that endpoint, send stop, drop the dir. Result JSON records "isolation": "per-conversation daemon".
B. Reset RPC. Add AdminReset gated by a --allow-reset daemon flag. Rejected unless A proves infeasible: it adds a destructive RPC to the daemon for the benefit of a benchmark.
Drain wait: replace any sleep with polling GetVectorIndexStatus / a new lightweight GetIndexCheckpoints RPC until BM25 and vector checkpoints ≥ the outbox sequence after ingest; timeout 5 min with a loud error. Record wait time per conversation in the result.
Steps:
Implement A behind --isolation daemon-per-conversation (default for --backend cli; shared remains for local debugging and prints the bleed caveat).
Add the checkpoint poll. If no RPC exposes checkpoints, add GetIndexCheckpoints to proto/memory.proto + memory-service — read only, small.
Extend smoke so CI runs the 1-conversation fixture under --backend cli with a spawned daemon and the mock judge (ci.yml new job bench-cli-smoke, Linux only, after build).
Update docs/benchmarks.md "Run" and "Modes".
Acceptance:
memory-bench locomo --backend cli --scorer mock --dataset benchmarks/fixtures/locomo-smoke.json completes with isolation: per-conversation daemon in the output and a drain_wait_ms per conversation
A unit test asserts two conversations ingested under isolation A do not retrieve each other's turns (mirror of mock_stores_do_not_bleed, runner.rs:346)
CI job bench-cli-smoke green
No std::thread::sleep remains in the cli-backend path
Effort: 1 session · Owner: agent
60-02 · The run
Why: F3, F7. The single most valuable artifact the project can produce.
Prereqs: 60-01 merged; OPENAI_API_KEY (or Anthropic); a machine you are willing to name in the result; --release build.
Steps:
benchmarks/scripts/download-locomo.sh → locomo-data/locomo10.json; record its SHA-256.
Dry run, cost cap: memory-bench locomo --dataset locomo-data --backend cli --scorer llm-judge --limit-questions 200 --output benchmarks/results/locomo-2026-MM-DD-partial.json (add --limit-questions in 60-01 if absent). Check the judge's recorded cost/tokens.
Full run to benchmarks/results/locomo-2026-MM-DD.json. Expect ~2,000 questions; under ~$10 on gpt-4o-mini; 1–2 h wall clock dominated by ingest + drain.
Commit the result. Update docs/benchmarks.md "Committed result" table with hardware, profile (--release), model, temperature 0, dataset SHA, isolation mode, and the per-type breakdown (single-hop / multi-hop / temporal / adversarial) the harness already emits.
Update the positioning doc "Benchmarks: what we can and cannot say" and the Claims Ledger row "Our own committed benchmark artifacts".
Decision gate (from v3.1 56-03, unchanged): publish the number whatever it is; the only thing that can hold it is a methodology defect.
docs/benchmarks.md and the positioning doc cite the file path
Per-type scores present (feeds the Phase 62 gate)
Effort: 1 maintainer session · Owner: maintainer (agent prepares the exact commands and reviews the artifact)
60-03 · Vector and topic quality fixtures
Why: F4. "Solid" needs an artifact.
Files:benchmarks/fixtures/semantic-001.toml (new), benchmarks/fixtures/sessions/*.jsonl (new sessions), crates/memory-bench (a --layers bm25|vector|hybrid switch on the custom harness), crates/memory-topics (a metrics module), crates/e2e-tests/tests/topic_graph_test.rs, README status table, positioning Claims Ledger.
Steps:
Semantic fixture set (≥ 15 tests). Each test's relevant items share meaning but not tokens with the query — "token expiry policy" → session text says "JWT lifetime"; "container orchestration cutover" → "EKS migration". Build the sessions so a BM25-only run scores recall@5 < 0.4 on the set (that is the point) and record it.
Harness switch: memory-bench run --category semantic --layers bm25|vector|hybrid maps to TeleportSearch / VectorTeleport / HybridSearch. Commit three result files.
Topic quality. A labelled fixture of 60–100 short documents in 6–8 known clusters; memory-topics::metrics::{purity, adjusted_rand_index} over cluster() output. Commit benchmarks/results/topics-quality.json.
Status table: vector row cites the hybrid-vs-bm25 delta; topic row moves from "Works · not benchmarked" to "Works · ARI x.xx" or stays "Works" with the number. Positioning Claims Ledger gains both rows.
Acceptance:
benchmarks/results/semantic-{bm25,vector,hybrid}.json committed; hybrid recall@5 > bm25 recall@5 on the semantic set (if not, that is a finding and the README changes accordingly)
topics-quality.json committed with purity and ARI
README rows for vector and topic graph link the artifacts
cargo test -p memory-topics metrics covers purity/ARI on a hand-computed 3-cluster example
Effort: 2 sessions · Owner: agent
Phase 61 — Operate It (5 plans · 6 sessions)
61-01 · Backfill
Why: F5. Every pre-v3.1 store is stuck.
Files:crates/memory-indexing/src/pipeline.rs, checkpoint.rs; crates/memory-daemon/src/cli.rs (AdminCommands::BackfillIndex), commands.rs; crates/memory-storage (event iteration by sequence); docs/UPGRADING.md, README status table. If 59-02 kept memory-service/src/import.rs, reuse its event replay.
Design:memory-daemon admin backfill-index --index bm25|vector|all [--from-sequence N] [--batch 500] [--dry-run]. Runs against a stopped daemon (takes the RocksDB lock) — stopped-only for v3.2 (decision 4). Algorithm: iterate events from --from-sequence (default 0) in batches; for each batch call the IndexUpdater for the chosen index; commit; write IndexCheckpoint = last sequence processed. Idempotent (re-indexing an existing doc is an upsert in Tantivy and HNSW). Resumable by reading the checkpoint on restart. Progress n/total on stderr every batch.
CLI subcommand + wiring; refuse to run if the daemon lock is held, with the message naming memory-daemon stop.
Fixture: a RocksDB store produced by the v3.0 daemon (build 68ab122, ingest 50 events, commit the directory under crates/e2e-tests/fixtures/store-v3.0/).
e2e: open fixture copy → backfill-index --index all → TeleportSearch returns previews for all 50.
Docs: README BM25 row drops the "no backfill" note; UPGRADING.md v3.1 section gains the command.
Acceptance:
e2e above passes; a second backfill-index run reports 0 new documents
--dry-run prints counts and writes nothing (checkpoint unchanged)
Interrupting mid-run (test sends SIGINT after batch 1) and re-running resumes from the checkpoint
README and UPGRADING updated in the same PR
Effort: 2 sessions · Owner: agent
61-02 · Daemon lifecycle via service units
Why: F5. Decision 3.
Files:crates/memory-daemon/src/cli.rs (Commands::{InstallService, UninstallService}), commands.rs, new service.rs; tests/cli/claude-code/*.bats, tests/cli/codex/*.bats; docs/setup/quickstart.md.
Design:memory-daemon install-service [--port] [--db-path] writes ~/Library/LaunchAgents/com.spillwave.memory-daemon.plist (macOS, launchctl bootstrap gui/$UID) or ~/.config/systemd/user/memory-daemon.service (Linux, systemctl --user enable --now). uninstall-service reverses it. Windows: exit non-zero with guidance (out of scope; Tier 2 at best). --background keeps exiting non-zero, now naming install-service.
Acceptance:
bats (both Tier 1 suites): install-service → memory-daemon status healthy within 10 s → uninstall-service → status reports not running; unit file removed
Re-running install-service is idempotent (unit rewritten, no duplicate)
Quickstart shows install-service as the recommended path
README row "Background daemonization" → "Via service unit"
Effort: 1 session · Owner: agent
61-03 · Panic audit
Why: F5. Get the real number, then fix the class that matters.
Files:crates/memory-service/src/{agents,retrieval,federated,episodes,teleport_service,topics,novelty}.rs, crates/memory-daemon/src/clod.rs, crates/memory-indexing/src/vector_updater.rs:459, memory-types/src/sync.rs:43; new crates/e2e-tests/tests/hostile_input_test.rs.
Steps:
Real count: a cargo clippy run with -W clippy::unwrap_used -W clippy::expect_used restricted to non-test code (the lints already skip #[cfg(test)]). Commit the count in the PR description.
Classify each site: (a) provably infallible — annotate with // INFALLIBLE: comment or convert to expect("<invariant>"); (b) fallible on a request path — convert to ? with a Status::internal/invalid_argument; (c) lock poisoning — finish the 5 with parking_lot or unwrap_or_else(PoisonError::into_inner) per the 54-06 policy already chosen.
Enable #[warn(clippy::unwrap_used, clippy::expect_used)] in memory-service and memory-daemon lib roots so regressions are visible (warn, not deny, so tests keep passing).
hostile_input_test.rs: for every RPC in proto/memory.proto, send empty, oversized (1 MiB string), malformed-UTF-8, negative/overflow numeric, and unknown-enum requests via the direct-handler pattern (tonic::Request, per v2.2 decision); after each, a health RPC must still answer.
Acceptance:
Clippy count of unwrap_used + expect_used in production code recorded before/after; class (b) count is 0 after
hostile_input_test.rs covers all 30 RPCs and passes
Lock-poisoning grep returns 0 production sites
Effort: 1 session · Owner: agent
61-04 · Offline TOC rebuild
Why: F5. The stub is honest but the gap is real.
Files:crates/memory-daemon/src/cli.rs:231 (AdminCommands::RebuildToc), commands.rs; crates/memory-toc (the rollup jobs the scheduler runs — reuse, do not reimplement); crates/e2e-tests/tests/pipeline_test.rs.
Design:admin rebuild-toc --from YYYY-MM-DD --to YYYY-MM-DD [--dry-run] runs the same day/week/month/year rollup code the scheduler invokes, over events in range, replacing existing nodes for that range. Stopped-daemon only, same lock rule as 61-01.
Acceptance:
e2e: build TOC via scheduler path → snapshot nodes → delete them → rebuild-toc → nodes byte-equal to the snapshot (ids, ranges, summaries)
--dry-run reports the node count it would write
README row "Offline TOC rebuild" → "Works"
Effort: 1 session · Owner: agent
61-05 · Installer: register, uninstall, status
Why: F2, and the March v3.2's still-valid half.
Files:crates/memory-installer/src/main.rs (Commands::{Uninstall, Status}), converters/claude.rs (port the registration from the branch: known_marketplaces.json, installed_plugins.json, settings.jsonenabledPlugins), .claude-plugin/plugin.json + marketplace.json (port), tests/e2e_converters.rs, tests/cli/claude-code/*.bats.
Steps:
Port CREG-01..06 + META-01..03 per the 59-02 sequence; drop every OpenCode path.
uninstall --agent claude|codex|gemini|copilot: remove registry entries (Claude) and installed files; no-op exit 0 when nothing is installed.
status: table of runtime · installed version · path · registered (Claude only) · "not installed".
Gemini/Codex/Copilot stay convert-only (registration is v3.3+ REG-F01).
Acceptance:
bats: install --agent claude → status shows version+path+registered → Claude Code launched headless lists the plugin → uninstall → status "not installed" → second uninstall exits 0 silently
.claude-plugin/plugin.json version is the single source for the install path (META-03)
Gate: run only if 60-02's per-type breakdown shows retrieval is the limiter — e.g. multi-hop and temporal recall@k high but judge accuracy low means generation, not retrieval; the reverse means rerank might help. The extension point (memory-orchestrator rerank trait, explicit NotImplemented) stays as is until then. If the gate opens: local cross-encoder via Candle in memory-embeddings, wired behind --rerank=cross, measured on the same fixtures, committed before any README change. Do not build ahead of evidence.
Launch (side quest · maintainer)
Now: blog post (process story, no number needed).
After 60-02: repo description + topics (ai-agents, memory, rust, claude-code, local-first), enable Discussions, record the demo, then Show HN / r/rust / r/LocalLLaMA with the number in paragraph one. Each draft in docs/launch/launch-copy.md needs one edit to cite it.
Date: 2026-09-01 · Verified against:
main@d6c8ac7, releasev3.1.0(run 33458631284)Status: Proposal for maintainer sign-off. Nothing here is implemented.
Repo copy:
docs/plans/v3.2-prove-it-plan.mdonclaude/phase-54-toolchain-drift-3k4fer(18d6638)Readable page: https://claude.ai/code/artifact/d559ad58-63e4-4a4d-808a-61f8b199ffda
This issue is the canonical home for the plan so it is visible outside
.planning/. Decisions go in the comments; the four open ones are in section 10.0. Thesis
v3.1 made the project's claims true. v3.2 makes them provable and operable: a real benchmark number committed next to the competitor figures, a quality artifact behind every "Solid" in the README status table, and a daemon someone can run for a week without a terminal open. No new retrieval capability ships unless a number says it is the bottleneck.
Same rules as v3.1, unchanged: execution-evidence (run-dependent requirements cite a committed artifact), reachability (
cargo tree -ishows a shipped dependent),task pr-precheckgreen on every PR.1. Where we are (verified today)
v3.1.0, 2026-09-01 01:45 UTC, 5/5 platformsfind crates -name '*.rs' | xargs cat | wc -ltarget/; Actionsbenchmarks/results/origin/gsd/phase-58-claude-registration-metadatagit rev-list --count origin/main..What was planned before this, and where it went
--for all,--all, Gemini/Codex/Copilot registration).planning/ROADMAP.mdon thegsd/branchesmainhas none of itdocs/superpowers/specs/2026-03-21-v3-competitive-parity-design.mdWhat the March line actually contains, by
git diff --stat origin/main...:crates/memory-service/src/backup.rs(+308),import.rs(+280),tests/import_round_trip.rs(+130), deltas toingest.rs/query.rsproto/memory.proto(+110): the export/import/streaming RPCscrates/memory-storage/src/db.rs(+35),episodes.rs(+41)crates/memory-cli:commands/timeline.rs(+212),output.rs(+265)crates/memory-installer/src/converters/claude.rs(+584 — registration),opencode.rs(+779 — obsolete),tests/e2e_converters.rs(+117)memory-orchestrator(orchestrator.rs,fusion.rs,rerank.rs,expand.rs…) — the branch was cut before feat(v3.0): Phase 51 Retrieval Orchestrator #28 landed main's orchestrator. This is a duplicate, not a modification. Never cherry-pick it.2. Findings
F1 · Release pipeline has no guardrails —
blockerEvidence: today's first
v3.1.0push builtacc7294(Cargo.toml= 2.7.0, no Phase 54–57 code), published "Release 3.1.0", public for 17 minutes..github/workflows/release.yml:Get version(line 49) derives the version from the tag name onlyGITHUB_SHAis an ancestor ofmainworkspace.package.version== tagrelease: if: always() && !cancelled()(line 181) publishes whatever platforms succeeded; 7 of 13 historical runs were partial or failedgenerate_release_notes: true(line 230) produces a PR-title list, not the CHANGELOG entryThe one guard that exists — four binaries per archive, added in #37 — ran for the first time today and held.
F2 · 99 commits of unmerged work —
highEvidence: section 1. The export/import feature is finished and tested (
import_round_trip.rs); the Claude Code plugin registration is finished. Neither is onmain. An import path is a backfill path (F5).F3 · The real benchmark has never been run —
blockerEvidence:
crates/memory-bench/src/cli.rs:14(--backend mock|cli),judge.rs:101–126(--scorer llm-judgereadsOPENAI_API_KEY/ANTHROPIC_API_KEY, records model id),main.rs:137–147(cli backend ingests viamemory addand evaluates per question),benchmarks/scripts/download-locomo.sh. Onlylocomo-smoke.json(4 questions, mock/mock) is committed.Harness gap for a real run:
RunConfig(runner.rs:40) carries oneendpoint;run_locomo(main.rs:134) loops conversations against it. The mock path gets a freshMockStoreper conversation; the cli path shares one daemon, so conversation N sees N−1's events — cross-conversation bleed the harness's own caveat names.F4 · Retrieval quality evidence exists for one layer —
highEvidence: custom-harness fixtures (
benchmarks/fixtures/*.toml) carryrelevant = [...]and reportrecall_at_k— but every committed fixture is answerable by token overlap (expected_contains = ["JWT"]withJWTin the source). No fixture requires semantic retrieval.grep -rl "recall\|ndcg\|mrr" crates/memory-vector crates/memory-search crates/e2e-tests→ lifecycle code only.memory-topicsexposescluster()andcreate_topics()(extraction.rs:51,96) with no quality metric anywhere.F5 · Operational gaps —
highIndexingPipeline::process_until_caught_up(memory-indexing/src/pipeline.rs:307) only drains the outbox forward fromIndexCheckpoint; there is no path that re-reads events already past the checkpoint.admin rebuild-bm25(memory-daemon/src/cli.rs:311) is a prune.commands.rs:574rejects--backgroundwith guidance to use systemd/launchd; nothing generates the unit.AdminCommands::RebuildToc(cli.rs:231) exits non-zero.lock().unwrap()sites remain (vector_updater.rs:459,novelty.rs:709,745,sync.rs:43+ 1). A line-based grep counts 266unwrap()/expect()in daemon+service, but it cannot exclude inline#[cfg(test)]modules (e.g.retrieval.rs:978+is test code). The real production count is unknown. That is the first task of 61-03.F6 · Planning source of truth is stale —
medium.planning/PROJECT.md: "Version: v3.0 (In Progress)"; adapter list includes OpenCode (removed in Phase 57); "API-based summarizer wiring" under Deferred though #27 shipped it (commands.rs:394resolve_api_key). Zero GitHub issues means the backlog is invisible outside.planning/.F7 · Launch is coupled to F3 —
mediumThe blog post stands alone. The Show HN / r/rust / r/LocalLLaMA drafts are product posts; the positioning doc's own rule forbids the comparison those audiences ask first.
3. Target state (milestone exit)
locomo_llm_judgeresult on the full dataset, real backend, hardware/model/dataset-SHA recorded, next to the competitor figures with a commensurability note.memory-daemon install-service+admin backfill-index+admin rebuild-tocexist and are covered by e2e/bats; nounwrap()on a request path user input can reach.PROJECT.mdaccurate; known gaps are GitHub issues; no orphangsd/branches; release pipeline refuses a bad tag.4. Plan
Owner is agent unless it needs credentials, hardware, or a judgment call. Effort in agent sessions (one session ≈ one merged PR of v3.1 size).
Phase 59 — Guardrails and Inventory (3 plans · 3 sessions)
59-01 · Release pipeline checks
Why: F1. Today's incident is reproducible by anyone with a stale local tag.
Files:
.github/workflows/release.yml; newdocs/RELEASING.md;CLAUDE.mdRelease Process section.Steps:
verifyjob beforebuild,runs-on: ubuntu-latest, that:git fetch origin mainand fails unlessgit merge-base --is-ancestor "$GITHUB_SHA" origin/mainworkspace.package.versionwithcargo metadata --no-deps --format-version 1 | jq -r '.packages[0].version'(orgrep -m1 '^version' Cargo.toml) and fails unless it equals${GITHUB_REF_NAME#v}; onworkflow_dispatchcompares to the inputCHANGELOG.mdcontains a heading for that versionbuild: needs: verify.release:— replaceif: always() && !cancelled()withneeds: [verify, build]and noif:. A failed platform fails the release. Keep the four-binary check from chore(v3.1): release prep — version 3.1.0, changelog, working release archives #37.generate_release_notes: truewithbody_path:pointing at a file the job extracts from the matchingCHANGELOG.mdsection (awk '/^## \[?'"$VER"'/{f=1;next} /^## /{f=0} f').docs/RELEASING.md: the explicit-SHA procedure —git tag -a vX.Y.Z <sha> -m "...",git rev-parse vX.Y.Z^{commit}before push, what the guard will refuse and why. Note the zshinteractivecommentstrap. UpdateCLAUDE.mdto point at it.Acceptance:
mainfails atverifybefore any build starts (test withv0.0.0-guardteston a throwaway branch; delete it)Cargo.tomlfails atverifydocs/RELEASING.mdexists andCLAUDE.mdlinks itVerify: the guard-test tag runs above;
actionlintclean; YAML parses.Effort: 1 session · Owner: agent (tag pushes for the test: maintainer)
59-02 · Orphan branch triage
Why: F2. Decide with a conflict map, not a feeling.
Files (read-only this plan): the three
gsd/branches; outputdocs/plans/march-branch-triage.md.Steps:
backup.rs,import.rs,import_round_trip.rs, theingest.rs/query.rs/db.rs/episodes.rsdeltas, the proto additions, thememory-clitimeline/output deltas, andconverters/claude.rs:git diff origin/main...origin/gsd/phase-58-claude-registration-metadata -- <path>and record: applies clean / conflicts / superseded.proto/memory.protoonmain(noExport*/Import*/Backup*RPCs exist today, so likely clean; the streaming RPC style needs review against tonic version onmain).memory-orchestratorandmemory-benchdirectories do not port (parallel implementations).gsd/phase-57-opencode-converter-registrationimmediately; delete the other two once 61-01 and 61-05 merge.Acceptance:
docs/plans/march-branch-triage.mdlists every ported file with clean/conflict status and the exactgit cherry-pick/git checkout <sha> -- <path>sequenceEffort: 1 session · Owner: agent; decision: maintainer
59-03 · Planning truth and a public backlog
Why: F6.
Files:
.planning/PROJECT.md,.planning/ROADMAP.md,.planning/STATE.md,.planning/REQUIREMENTS.md; GitHub issues.Steps:
PROJECT.md: Current State → v3.1 shipped 2026-09-01; Current Milestone → v3.2 (this plan); adapters list → Claude Code, Codex (Tier 1), Gemini, Copilot (Tier 2); move "API-based summarizer wiring" to Validated with the feat(v3.0): wire API-based summarizer from config (supersedes #26) #27 reference; add "true daemonization" → superseded byinstall-service(61-02) once decided.REQUIREMENTS.md: replace the v3.0 block with v3.2 requirement IDs (REL-01..04, BENCH-10..13, QUAL-01..03, OPS-01..05, INST-01..03) mapped 1:1 to the plans below; keep the Future (v3.3+) list.v3.2. Link each as a sub-issue of this one.ROADMAP.md/STATE.md: add v3.2 phases 59–62 in the existing format.Acceptance:
grep -n "v3.0 (In Progress)\|OpenCode" .planning/PROJECT.md→ nonev3.2, each with a plan IDREQUIREMENTS.mdappears in exactly one planEffort: 1 session · Owner: agent
Phase 60 — Real Numbers (3 plans · 4 sessions + 1 maintainer run)
60-01 · Live-backend isolation for LOCOMO
Why: F3. Without isolation the number is contaminated and cannot be committed.
Files:
crates/memory-bench/src/runner.rs(RunConfig),main.rs(run_locomo,evaluate_sample_cli),locomo.rs(ingest_sample_cli),cli.rs;crates/memory-daemon/src/cli.rs(Start args:--port, database path override already exist).Design (pick A; B is fallback):
RunConfiggainsdaemon_bin: Option<PathBuf>,isolation: Isolation::{Shared, DaemonPerConversation}. For each conversation: create atempfile::tempdir(), spawnmemory-daemon start --db-path <tmp> --port <free port>, wait formemory-daemon statushealthy, run ingest + evaluate against that endpoint, sendstop, drop the dir. Result JSON records"isolation": "per-conversation daemon".AdminResetgated by a--allow-resetdaemon flag. Rejected unless A proves infeasible: it adds a destructive RPC to the daemon for the benefit of a benchmark.GetVectorIndexStatus/ a new lightweightGetIndexCheckpointsRPC until BM25 and vector checkpoints ≥ the outbox sequence after ingest; timeout 5 min with a loud error. Record wait time per conversation in the result.Steps:
--isolation daemon-per-conversation(default for--backend cli;sharedremains for local debugging and prints the bleed caveat).GetIndexCheckpointstoproto/memory.proto+memory-service— read only, small.smokeso CI runs the 1-conversation fixture under--backend cliwith a spawned daemon and the mock judge (ci.ymlnew jobbench-cli-smoke, Linux only, afterbuild).docs/benchmarks.md"Run" and "Modes".Acceptance:
memory-bench locomo --backend cli --scorer mock --dataset benchmarks/fixtures/locomo-smoke.jsoncompletes withisolation: per-conversation daemonin the output and adrain_wait_msper conversationmock_stores_do_not_bleed,runner.rs:346)bench-cli-smokegreenstd::thread::sleepremains in the cli-backend pathEffort: 1 session · Owner: agent
60-02 · The run
Why: F3, F7. The single most valuable artifact the project can produce.
Prereqs: 60-01 merged;
OPENAI_API_KEY(or Anthropic); a machine you are willing to name in the result;--releasebuild.Steps:
benchmarks/scripts/download-locomo.sh→locomo-data/locomo10.json; record its SHA-256.memory-bench locomo --dataset locomo-data --backend cli --scorer llm-judge --limit-questions 200 --output benchmarks/results/locomo-2026-MM-DD-partial.json(add--limit-questionsin 60-01 if absent). Check the judge's recorded cost/tokens.benchmarks/results/locomo-2026-MM-DD.json. Expect ~2,000 questions; under ~$10 ongpt-4o-mini; 1–2 h wall clock dominated by ingest + drain.docs/benchmarks.md"Committed result" table with hardware, profile (--release), model, temperature 0, dataset SHA, isolation mode, and the per-type breakdown (single-hop / multi-hop / temporal / adversarial) the harness already emits.Acceptance:
benchmarks/results/locomo-*.jsonwith"metric": "locomo_llm_judge","isolation": "per-conversation daemon", non-nullmodel,hardware,dataset_sha256docs/benchmarks.mdand the positioning doc cite the file pathEffort: 1 maintainer session · Owner: maintainer (agent prepares the exact commands and reviews the artifact)
60-03 · Vector and topic quality fixtures
Why: F4. "Solid" needs an artifact.
Files:
benchmarks/fixtures/semantic-001.toml(new),benchmarks/fixtures/sessions/*.jsonl(new sessions),crates/memory-bench(a--layers bm25|vector|hybridswitch on the custom harness),crates/memory-topics(ametricsmodule),crates/e2e-tests/tests/topic_graph_test.rs, README status table, positioning Claims Ledger.Steps:
relevantitems share meaning but not tokens with the query — "token expiry policy" → session text says "JWT lifetime"; "container orchestration cutover" → "EKS migration". Build the sessions so a BM25-only run scores recall@5 < 0.4 on the set (that is the point) and record it.memory-bench run --category semantic --layers bm25|vector|hybridmaps toTeleportSearch/VectorTeleport/HybridSearch. Commit three result files.memory-topics::metrics::{purity, adjusted_rand_index}overcluster()output. Commitbenchmarks/results/topics-quality.json.Acceptance:
benchmarks/results/semantic-{bm25,vector,hybrid}.jsoncommitted; hybrid recall@5 > bm25 recall@5 on the semantic set (if not, that is a finding and the README changes accordingly)topics-quality.jsoncommitted with purity and ARIcargo test -p memory-topics metricscovers purity/ARI on a hand-computed 3-cluster exampleEffort: 2 sessions · Owner: agent
Phase 61 — Operate It (5 plans · 6 sessions)
61-01 · Backfill
Why: F5. Every pre-v3.1 store is stuck.
Files:
crates/memory-indexing/src/pipeline.rs,checkpoint.rs;crates/memory-daemon/src/cli.rs(AdminCommands::BackfillIndex),commands.rs;crates/memory-storage(event iteration by sequence);docs/UPGRADING.md, README status table. If 59-02 keptmemory-service/src/import.rs, reuse its event replay.Design:
memory-daemon admin backfill-index --index bm25|vector|all [--from-sequence N] [--batch 500] [--dry-run]. Runs against a stopped daemon (takes the RocksDB lock) — stopped-only for v3.2 (decision 4). Algorithm: iterate events from--from-sequence(default 0) in batches; for each batch call theIndexUpdaterfor the chosen index; commit; writeIndexCheckpoint= last sequence processed. Idempotent (re-indexing an existing doc is an upsert in Tantivy and HNSW). Resumable by reading the checkpoint on restart. Progressn/totalon stderr every batch.Steps:
IndexingPipeline::backfill(from: u64, batch: usize, indexes: &[IndexType]) -> Result<BackfillReport>besideprocess_until_caught_up.memory-daemon stop.68ab122, ingest 50 events, commit the directory undercrates/e2e-tests/fixtures/store-v3.0/).backfill-index --index all→TeleportSearchreturns previews for all 50.UPGRADING.mdv3.1 section gains the command.Acceptance:
backfill-indexrun reports 0 new documents--dry-runprints counts and writes nothing (checkpoint unchanged)Effort: 2 sessions · Owner: agent
61-02 · Daemon lifecycle via service units
Why: F5. Decision 3.
Files:
crates/memory-daemon/src/cli.rs(Commands::{InstallService, UninstallService}),commands.rs, newservice.rs;tests/cli/claude-code/*.bats,tests/cli/codex/*.bats;docs/setup/quickstart.md.Design:
memory-daemon install-service [--port] [--db-path]writes~/Library/LaunchAgents/com.spillwave.memory-daemon.plist(macOS,launchctl bootstrap gui/$UID) or~/.config/systemd/user/memory-daemon.service(Linux,systemctl --user enable --now).uninstall-servicereverses it. Windows: exit non-zero with guidance (out of scope; Tier 2 at best).--backgroundkeeps exiting non-zero, now naminginstall-service.Acceptance:
install-service→memory-daemon statushealthy within 10 s →uninstall-service→ status reports not running; unit file removedinstall-serviceis idempotent (unit rewritten, no duplicate)install-serviceas the recommended pathEffort: 1 session · Owner: agent
61-03 · Panic audit
Why: F5. Get the real number, then fix the class that matters.
Files:
crates/memory-service/src/{agents,retrieval,federated,episodes,teleport_service,topics,novelty}.rs,crates/memory-daemon/src/clod.rs,crates/memory-indexing/src/vector_updater.rs:459,memory-types/src/sync.rs:43; newcrates/e2e-tests/tests/hostile_input_test.rs.Steps:
cargo clippyrun with-W clippy::unwrap_used -W clippy::expect_usedrestricted to non-test code (the lints already skip#[cfg(test)]). Commit the count in the PR description.// INFALLIBLE:comment or convert toexpect("<invariant>"); (b) fallible on a request path — convert to?with aStatus::internal/invalid_argument; (c) lock poisoning — finish the 5 withparking_lotorunwrap_or_else(PoisonError::into_inner)per the 54-06 policy already chosen.#[warn(clippy::unwrap_used, clippy::expect_used)]inmemory-serviceandmemory-daemonlib roots so regressions are visible (warn, not deny, so tests keep passing).hostile_input_test.rs: for every RPC inproto/memory.proto, send empty, oversized (1 MiB string), malformed-UTF-8, negative/overflow numeric, and unknown-enum requests via the direct-handler pattern (tonic::Request, per v2.2 decision); after each, a health RPC must still answer.Acceptance:
unwrap_used + expect_usedin production code recorded before/after; class (b) count is 0 afterhostile_input_test.rscovers all 30 RPCs and passesEffort: 1 session · Owner: agent
61-04 · Offline TOC rebuild
Why: F5. The stub is honest but the gap is real.
Files:
crates/memory-daemon/src/cli.rs:231(AdminCommands::RebuildToc),commands.rs;crates/memory-toc(the rollup jobs the scheduler runs — reuse, do not reimplement);crates/e2e-tests/tests/pipeline_test.rs.Design:
admin rebuild-toc --from YYYY-MM-DD --to YYYY-MM-DD [--dry-run]runs the same day/week/month/year rollup code the scheduler invokes, over events in range, replacing existing nodes for that range. Stopped-daemon only, same lock rule as 61-01.Acceptance:
rebuild-toc→ nodes byte-equal to the snapshot (ids, ranges, summaries)--dry-runreports the node count it would writeEffort: 1 session · Owner: agent
61-05 · Installer: register, uninstall, status
Why: F2, and the March v3.2's still-valid half.
Files:
crates/memory-installer/src/main.rs(Commands::{Uninstall, Status}),converters/claude.rs(port the registration from the branch:known_marketplaces.json,installed_plugins.json,settings.jsonenabledPlugins),.claude-plugin/plugin.json+marketplace.json(port),tests/e2e_converters.rs,tests/cli/claude-code/*.bats.Steps:
uninstall --agent claude|codex|gemini|copilot: remove registry entries (Claude) and installed files; no-op exit 0 when nothing is installed.status: table of runtime · installed version · path · registered (Claude only) · "not installed".Acceptance:
install --agent claude→statusshows version+path+registered → Claude Code launched headless lists the plugin →uninstall→status"not installed" → seconduninstallexits 0 silently.claude-plugin/plugin.jsonversion is the single source for the install path (META-03)Effort: 1 session · Owner: agent
Phase 62 — Cross-encoder rerank (conditional · 2 sessions)
Gate: run only if 60-02's per-type breakdown shows retrieval is the limiter — e.g. multi-hop and temporal recall@k high but judge accuracy low means generation, not retrieval; the reverse means rerank might help. The extension point (
memory-orchestratorrerank trait, explicitNotImplemented) stays as is until then. If the gate opens: local cross-encoder via Candle inmemory-embeddings, wired behind--rerank=cross, measured on the same fixtures, committed before any README change. Do not build ahead of evidence.Launch (side quest · maintainer)
ai-agents,memory,rust,claude-code,local-first), enable Discussions, record the demo, then Show HN / r/rust / r/LocalLLaMA with the number in paragraph one. Each draft indocs/launch/launch-copy.mdneeds one edit to cite it.5. Requirement map
mainlocomo_llm_judgefull-dataset resultadmin backfill-indexresumable, idempotentinstall-service/uninstall-servicemacOS+Linuxunwrapon request pathsadmin rebuild-tocrealmemory-installer uninstallmemory-installer status6. Sequencing
7. Risks
--limit-questions 200dry run firstclippy::unwrap_usedwarn is noisy8. Milestone success criteria
locomo_llm_judgeresult, full dataset, real backend, provenance fields non-null (60-02)PROJECT.mdaccurate; 8v3.2issues; nogsd/branches (59-02, 59-03)task pr-precheckgreen on every PR9. Out of scope (stays v3.3+)
REST/HTTP endpoint · Python SDK · memory views UI ·
--for all/--allinstaller flags · Gemini/Codex/Copilot registration · Windows service install · true double-fork daemonization · consolidation hook · cross-project unified memory · per-agent dedup scoping.10. Decisions needed from the maintainer