# M1.3 — GRU-Mem prompt template | Field | Value | |---|---| | Phase | M1 — Gated loop at L1 | | Size | M — 1–3 days | | Status | ✅ Done | | Flags | — | | Spec | inlined below | | Blocks | M1.2 | ## Goal Assemble the memory-agent prompt exactly as the paper specifies, because the model's ability to emit parseable gates depends on the format it was aligned to. ## Files | Action | Path | |---|---| | Create | `crates/mem-core/src/prompt.rs` — `PromptBuilder` struct | | Modify | `crates/mem-core/src/lib.rs` — add `pub mod prompt;` | | Create | `templates/gru-mem.txt` — the prompt template (verbatim from paper Fig 10a) | | Create | `fixtures/expected/prompt-t1.txt` — golden file for turn 1 | | Create | `fixtures/expected/prompt-tn.txt` — golden file for turn N | | Create | `tests/it_prompt.rs` — integration tests (workspace root) | ## Dependencies **None new.** No template engine — the prompt has 3 substitutions (`{prompt}`, `{memory}`, `{chunk}`). Use `str::replace()` or `format!()`. Adding `tera` for 3 variables is overengineering. ## Existing code to reuse - `Chunk` from `domain.rs` — render its `records` vec - `Role` from `domain.rs` — map to `[User]`, `[Assistant]`, `[ToolResult]`, `[System]` labels - `Query` from `query.rs` (M1.2) — read `query.question` for the `{prompt}` substitution - `TokenCounter` from `mem-chunk` — check assembled prompt fits budget ## Facts (inlined — no spec read needed) Paper Figure 10a, reproduced verbatim — this is the contract, not a starting point to improvise on: ``` You are presented with a problem, a section of an article that may contain the answer to the problem, and a previous memory. Please read the provided section carefully. You should reason about whether the new section contains useful information about the problem, and then update the memory with the new information that helps to answer the problem. Be sure to retain all relevant details from the previous memory while adding any new, useful information. You should also carefully judge whether you have collected enough information to answer the problem. You should reason about whether the new section contains useful information, what to update, and what to do next first between and . If the new section contains useful information about the problem, you should first generate yes. After that, update the new memory between and . If the new section does not contain useful information about the problem, you should first generate no. After that, you should keep the previous memory unchanged between and . In the end, if you haven't collected enough information for the problem, return continue. ONLY when enough information is collected, return end. {prompt} {memory}
{chunk}
``` Substitutions for this system: `{prompt}` = the standing question, `{memory}` = `M_{t-1}` or the literal `No previous memory` at `t=1` (the paper's own case studies show that exact string), `{chunk}` = the rendered chunk. Budget, against the 32768 cap: ``` system + template ~400 question ~100 memory <=1024 chunk <=5000 response 2048 ------ ~8600 headroom is comfortable ``` Chunk rendering: each record as `[role] text`, records separated by a blank line. Role labels matter — the model uses them to tell a tool result from a decision. ## Steps 1. `PromptBuilder` in `mem-core` producing `(system, user)`. 2. Template verbatim as above. Any deviation gets a comment saying why. 3. `t=1` renders `No previous memory` — not empty, not `null`. 4. Render chunk records as `[role] text`, blank-line separated. 5. Assert the assembled prompt fits the budget before sending; over budget is an error naming the component that overflowed, not a truncation. 6. `mem prompt --project P --query Q --chunk N` prints the exact prompt, for eyeballing what the model actually sees. ## Acceptance - Assembled prompt matches a committed golden file byte for byte. - `t=1` contains `No previous memory`. - Over-budget assembly errors and names the offending component. ## Verify **Harness:** golden-file comparison. The prompt is a contract; a diff in it is a change to the contract. **Integration test** — `tests/it_prompt.rs`: 1. `a1_golden_t1` — first turn against `expected/prompt-t1.txt`, exact match. 2. `a2_golden_tn` — turn with a prior memory against `expected/prompt-tn.txt`. 3. `a3_no_previous_memory_literal` — assert the exact string at `t=1`. 4. `a4_all_tags_present` — ``, ``, `
` each appear exactly once. 5. `a5_role_labels_rendered` — a chunk with all four roles renders all four labels. 6. `a6_over_budget_errors` — a 20000-token chunk errors, message contains `section`. 7. `a7_budget_headroom` — for the real fixture corpus, assert every assembled prompt is under 32768 minus 2048. **Command:** `cargo test --test it_prompt` **False pass:** - Asserting the prompt "contains" the question. A template that dropped the `` instructions still contains it, and the model then emits prose the parser cannot read. Golden-file equality is the assertion that holds. - Skipping assertion 7 by testing only small fixtures. Budget overflow appears at p95 chunk size, not at the median. ## Traps - Improving the wording. The 3B model's gate reliability comes from this exact format; a cleaner rewrite is an unmeasured change to the one thing M1.8 gates on. - Rendering an empty `` at `t=1`. The paper's traces show `No previous memory`, and an empty tag reads to the model as "memory exists and is empty", which is a different claim. --- Background: [DESIGN.md](../DESIGN.md) — Standing queries · paper Fig 10a