# poimen-memory — task board 64 tasks — 54 build tasks plus **10 composition gates**, one per phase. One file per task, **self-contained**: inlined design facts, executable steps, acceptance criteria, a `Verify` section written for someone who did not build the thing, and the traps worth naming. Reading `DESIGN.md` is not required to do a task — it is linked as background only. Each `Verify` section names the harness, the integration test with numbered assertions, the command to run, and the **false pass** — the shape of test that goes green while the feature is broken. Treat the false-pass list as part of the acceptance criteria, not commentary. ## Ordering — declared, never derived **Phase order is the list below. Task ids are opaque and frozen.** `M0.3` is `M0.3` forever, in whatever phase it currently sits, because its artifacts and cross-references key on that id. New tasks take new ids rather than renumbering neighbours. This is the `StepId` rule applied to the board itself — a board that renumbers to reorder has the bug it warns its own users about. No phase starts until its predecessor's gate is green. The `gate` task at the end of each phase **is** that gate: it proves the phase's parts compose and that its swappable parts are genuinely swappable. Every build task is verified alone; the gate verifies the properties no single task owns. ## Two rules this board exists to protect **1. The JSONL log is authoritative; the vault and the vector index are projections.** Anything that cannot be dropped and rebuilt byte-identically from the log has hidden inputs, and that is a bug. `M2.8` is the gate that enforces it. **2. The update gate must discriminate, not summarize.** Agent transcripts are ~43% tool results and mostly evidence-free. A gate that accepts most chunks is an expensive summarizer that will reproduce the memory-explosion failure the whole design exists to avoid. `M1.8` is the gate that enforces it, and **update-rate is the single number to watch.** ## Verification practice — script first, source second A task is verified by running its command and reading the output, then opening the source. Reviewing the diff first is how an assertion that was quietly dropped still gets called done: the code looks right, and nothing proves the test ran. Numbered assertion N in a `Verify` section is test fn `aN_`. The numbering is the contract — a test fn that does not exist should report missing rather than be silently absent from a green summary. `cargo test` reporting `ok` with zero tests run is not a pass. ## Progress **Source of truth is the `Status` field in each task file.** The tables below mirror it; a status changed here and not there is a lie. Legend: ⬜ not started · 🟡 in progress · ✅ done · ⛔ blocked | # | Phase | Ids | Tasks | ✅ | 🟡 | ⬜ | Gate | |---|---|---|---|---|---|---|---| | 1 | Read-only spine | M0.x | 8 | 8 | 0 | 0 | ✅ M0.8 | | 2 | Gated loop at L1 | M1.x | 8 | 8 | 0 | 0 | ✅ M1.8 | | 3 | Projections | M2.x | 8 | 5 | 0 | 3 | ✅ M2.8 (M2.1, M2.3, M2.4, M2.5 ✅) | | 4 | L2 synthesis + retrieval | M3.x | 4 | 4 | 0 | 0 | ✅ M3.4 | | 4.5 | Distributed API Layer | M3.5.x | 9 | 6 | 0 | 3 | ⬜ M3.5.8 | | 5 | Skills | M4.x | 3 | 0 | 1 | 2 | ⬜ M4.3 | | 5.5 | Reference corpora | M3.6.x | 6 | 1 | 0 | 5 | ⬜ M3.6.6 | | 5.6 | Tool context | M3.7.x | 6 | 0 | 2 | 4 | ⬜ M3.7.6 | | 6 | Post-training | M5.x | 6 | 0 | 0 | 6 | ⬜ M5.6 | | 7 | agent-manager migration | M6.x | 6 | 0 | 0 | 6 | ⬜ M6.6 | | | **Total** | | **64** | **40** | **3** | **21** | 4/10 green | **Where the line is — 2026-08-26.** M0, M1, M3 complete (20/20 tasks, gates green). M3.5 API layer 6/9 done (M3.5.1–6 implemented, M3.5.7 rate-limiting and M3.5.8 gate blocked, M3.5.9 git-context not started). M3.6.1 DocCorpusSource ✅ (14 tests). Significant early work for M3.7 and M4: `mem-core/src/lesson.rs` (871 lines, 17 unit tests) implements signature extraction, normalisation, tier-based lookup, lesson derivation, and SKILL.md rendering — M3.7.7, M3.7.5, M4.1 are 🟡. `mem-cli/src/lessons_cmd.rs` (223 lines) provides working `mem capture|resolve|lookup|materialize`. `M2.2` (CNPG manifest), `M5.4` (vLLM+LoRA), `M3.5.7–9` (rate-limiting, git refs), and `M6.x` (agent-manager migration) are homelab/infra work independent of prior phases, can start parallel. **M6 is a different repo, not a dependency of M0-M5.** It migrates `github.com/Riotpiaole/agent-manager`'s session store (a separate Go CLI tool, unrelated to this project's own memory system) from local sqlite to its own dedicated CNPG cluster. It rides in this board because it's homelab work happening alongside M2.2/M5.4, and because the two projects' Postgres schemas landing in the same cluster around the same time need to look like siblings, not strangers — see M6.6's convention-consistency check. ## 1 — Read-only spine · M0.x No model calls anywhere in this phase. The point is to prove the corpus parses and chunks sanely before spending inference on it. | Task | Title | Size | Flags | Status | |---|---|---|---|---| | [M0.1](M0.1-cargo-workspace.md) | Cargo workspace + crate skeletons | S | — | ✅ | | [M0.2](M0.2-domain-types.md) | Domain types and sha256 identity | S | — | ✅ | | [M0.3](M0.3-recordsource-and-chunkpolicy.md) | `RecordSource` trait + `ChunkPolicy` | M | — | ✅ | | [M0.4](M0.4-tokenizer-sizing.md) | Tokenizer-backed chunk sizing | M | — | ✅ | | [M0.5](M0.5-pi-session-adapter.md) | pi session adapter | M | — | ✅ | | [M0.6](M0.6-claude-transcript-adapter.md) | Claude transcript adapter | S | — | ✅ | | [M0.7](M0.7-ingest-dry-run.md) | `mem ingest --dry-run` | S | — | ✅ | | [M0.8](M0.8-m0-gate.md) | **M0 composition gate** | M | gate | ✅ | ## 2 — Gated loop at L1 · M1.x | Task | Title | Size | Flags | Status | |---|---|---|---|---| | [M1.1](M1.1-llm-chat-client.md) | `mem-llm` chat client | M | — | ✅ | | [M1.2](M1.2-standing-query-loader.md) | Standing-query YAML loader | M | — | ✅ | | [M1.3](M1.3-prompt-template.md) | GRU-Mem prompt template | M | — | ✅ | | [M1.4](M1.4-gate-response-parser.md) | Gate-response parser | M | — | ✅ | | [M1.5](M1.5-gated-loop.md) | The gated loop | L | — | ✅ | | [M1.6](M1.6-jsonl-event-log.md) | JSONL event log writer | M | — | ✅ | | [M1.7](M1.7-ingest-end-to-end.md) | `mem ingest` end to end | M | — | ✅ | | [M1.8](M1.8-m1-gate.md) | **M1 composition gate** | M | gate | ✅ | ## 3 — Projections · M2.x | Task | Title | Size | Flags | Status | |---|---|---|---|---| | [M2.1](M2.1-embeddings-client.md) | Embeddings client | S | — | ⬜ | | [M2.2](M2.2-memory-db-manifest.md) | CNPG `memory-db` + pgvector | M | homelab | ⬜ | | [M2.3](M2.3-schema-and-migrations.md) | Schema + sqlx migrations | M | — | ⬜ | | [M2.4](M2.4-pgvector-repo.md) | pgvector repository | M | — | ⬜ | | [M2.5](M2.5-obsidian-projector.md) | Obsidian projector | M | — | ⬜ | | [M2.6](M2.6-rebuild-from-log.md) | `mem rebuild --from-log` | M | — | ⬜ | | [M2.7](M2.7-verify-edges.md) | `mem verify` — edge closure | S | — | ⬜ | | [M2.8](M2.8-m2-gate.md) | **M2 composition gate** | M | gate | ⬜ | ## 4 — L2 synthesis and retrieval · M3.x | Task | Title | Size | Flags | Status | |---|---|---|---|---| | [M3.1](M3.1-l2-synthesis.md) | L2 synthesis pass | M | — | ✅ | | [M3.2](M3.2-rerank-client.md) | Rerank client | S | — | ✅ | | [M3.3](M3.3-mem-query.md) | `mem query` with provenance | M | — | ✅ | | [M3.4](M3.4-m3-gate.md) | **M3 composition gate** | M | gate | ✅ | ## 4.5 — Distributed API Layer · M3.5.x Homelab frontend integration: HTTP facade via `api.riotpiao.com`. Runs in parallel with M4 and M5 after M3.4 green. | Task | Title | Size | Flags | Status | |---|---|---|---|---| | [M3.5.1](M3.5.1-http-server.md) | HTTP server + router, Kong auth, metrics | M | — | ✅ | | [M3.5.2](M3.5.2-ingest-endpoint.md) | POST /ingest async queue | M | — | ✅ | | [M3.5.3](M3.5.3-query-endpoint.md) | GET /query HNSW+rerank | M | — | ✅ | | [M3.5.4](M3.5.4-query-federation.md) | Query federation | M | — | ✅ | | [M3.5.5](M3.5.5-skills-endpoint.md) | GET /skills endpoint | M | — | ✅ | | [M3.5.6](M3.5.6-projects-endpoint.md) | GET /projects endpoint | S | — | ✅ | | [M3.5.7](M3.5.7-rate-limiting.md) | Rate limiting | M | — | ⬜ | | [M3.5.8](M3.5.8-m3.5-gate.md) | **M3.5 composition gate** | M | gate | ⬜ | | [M3.5.9](M3.5.9-git-aware-references.md) | Git-aware references: lookup by code location | M | — | ⬜ | ## 5 — Skills · M4.x | Task | Title | Size | Flags | Status | |---|---|---|---|---| | [M4.1](M4.1-skill-draft.md) | `mem skill draft` | M | — | 🟡 `render_skill()` in `lesson.rs`, `mem materialize` in CLI | | [M4.2](M4.2-derived-filter.md) | `derived: true` ingest filter | M | — | ⬜ | | [M4.3](M4.3-m4-gate.md) | **M4 composition gate** | M | gate | ⬜ | ## 5.5 — Reference corpora · M3.6.x Documentation the local models are weak at — `kubectl`, `tea` — made retrievable as level **R**: embedded and indexed, never evidence. Ids are `M3.6.x` and stay `M3.6.x`; the phase sits here rather than at 4.6 because [M3.6.4](M3.6.4-reference-cycle-guard.md) extends M4.2's matcher instead of duplicating it, and because skills are the better answer to the same problem and should exist first. **The load-bearing property is a negative one.** Adding a corpus must not change update-rate, must not change default query output, and must not put an R node in any provenance chain. R bypasses the recurrence structurally — `run_loop` needs a `Query` and a corpus has none — not by a flag. [M3.6.6](M3.6.6-m3.6-gate.md) asserts M1.8's numbers are *unchanged*, not merely still-passing, because documentation fed to the gate would lower update-rate and make M1.8 easier to clear while the memory got worse. | Task | Title | Size | Flags | Status | |---|---|---|---|---| | [M3.6.1](M3.6.1-doc-corpus-source.md) | `DocCorpusSource` + heading chunking | M | — | ✅ | | [M3.6.2](M3.6.2-level-r-storage.md) | Level R: log, index, vault, rebuild parity | M | — | ⬜ | | [M3.6.3](M3.6.3-mem-ref-cli.md) | `mem ref` — replace-on-change corpus management | M | — | ⬜ | | [M3.6.4](M3.6.4-reference-cycle-guard.md) | Reference text cannot re-enter as evidence | M | — | ⬜ | | [M3.6.5](M3.6.5-query-levels-and-floor.md) | Query: filter-then-recall, R opt-in, floor | M | — | ⬜ | | [M3.6.6](M3.6.6-m3.6-gate.md) | **M3.6 composition gate** | M | gate | ⬜ | ## 5.6 — Tool context · M3.7.x Answers *"what do we already know about this failure, tool or task"* over HTTP. Consumers are `pi`, curl, or an MCP call — nothing here executes a tool, and nothing here serves a tool catalog, because every caller already holds its own MCP schemas. **Three tiers, cheapest first.** An exact hash hit on a normalised failure signature means *this happened here before*; a symptom-vector match means *something similar did*; the R corpus means *nobody here has hit this, read the docs*. The tier is a field in the response, because those three answers must not arrive in the same register. **The two tasks that make it work are the least obvious ones.** [M3.7.7](M3.7.7-signature-extraction.md) decides whether tier 1 ever fires — if normalisation leaves a timestamp in, the same failure never hashes twice and the system silently degrades to vector search. [M3.7.8](M3.7.8-symptom-projection.md) closes the gap between memories written as answers and queries that arrive as stack traces. Both fail invisibly, which is why [M3.7.6](M3.7.6-m3.7-gate.md) ablates them rather than trusting an end-to-end green. Ids are `M3.7.x` and frozen. `M3.7.1` and `M3.7.2` were a tool-catalog surface, deleted before implementation once the consumer was settled; their ids are retired rather than reused. | Task | Title | Size | Flags | Status | |---|---|---|---|---| | [M3.7.3](M3.7.3-skill-matching.md) | `GET /memory/skills?task=` — match a subset | M | — | ⬜ | | [M3.7.4](M3.7.4-context-endpoint.md) | `/memory/context` — three-tier lookup | M | — | ⬜ | | [M3.7.5](M3.7.5-tool-failure-learning.md) | `tool-failures` standing query | M | — | 🟡 `derive_lessons()` + `tool_of_cmd()` in `lesson.rs`, `mem resolve` in CLI | | [M3.7.6](M3.7.6-m3.7-gate.md) | **M3.7 composition gate** | M | gate | ⬜ | | [M3.7.7](M3.7.7-signature-extraction.md) | Failure signature extraction + normalisation | M | — | 🟡 `extract()` + `normalise()` in `lesson.rs` (10 unit tests passing) | | [M3.7.8](M3.7.8-symptom-projection.md) | Symptom projection at ingest | M | — | ⬜ | ## 6 — Post-training · M5.x Python, separate from the Rust workspace. The boundary is the JSONL log. | Task | Title | Size | Flags | Status | |---|---|---|---|---| | [M5.1](M5.1-evidence-labeler.md) | `mem label` — evidence labeler | M | — | ⬜ | | [M5.2](M5.2-labeler-calibration.md) | Labeler calibration | M | — | ⬜ | | [M5.3](M5.3-training-corpus-export.md) | Training corpus export | M | — | ⬜ | | [M5.4](M5.4-vllm-lora-serving.md) | vLLM + `--enable-lora` | L | homelab | ⬜ | | [M5.5](M5.5-verl-training-loop.md) | verl training loop | L | — | ⬜ | | [M5.6](M5.6-m5-gate.md) | **M5 composition gate** | L | gate | ⬜ | ## 7 — agent-manager migration · M6.x Separate repo (`github.com/Riotpiaole/agent-manager`, fork branch `add-headless-spawn`), separate cluster resource, no Rust/GRU-Mem dependency. Moves its session store off local sqlite onto a dedicated CNPG Postgres, reachable from the Mac client through a dedicated nginx route — durability-of-location, not a multi-host requirement. | Task | Title | Size | Flags | Status | |---|---|---|---|---| | [M6.1](M6.1-agent-manager-db-manifest.md) | CNPG `agent-manager-db` manifest | M | homelab | ⬜ | | [M6.2](M6.2-schema-port.md) | Postgres schema for agent-manager sessions | M | — | ⬜ | | [M6.3](M6.3-store-query-port.md) | store.go query port to Postgres | L | — | ⬜ | | [M6.4](M6.4-nginx-stream-routing.md) | nginx TCP routing to `agent-manager-db` | S | homelab | ⬜ | | [M6.5](M6.5-credentials-secret.md) | Postgres credentials for the Mac client | S | homelab | ⬜ | | [M6.6](M6.6-m6-gate.md) | **M6 composition gate** | M | gate | ⬜ | --- Background: [DESIGN.md](../DESIGN.md) · GRU-Mem, arXiv 2602.10560 · `internal/store/store.go` (agent-manager, `add-headless-spawn` branch)