|
1 | 1 | # LevelCode Sessions — Project Memory & Continuity |
2 | 2 |
|
3 | | -**Status:** **shipped** — M1–M4 complete (§10) · **Scope:** `extensions/levelcode-ai` + the sessions store · **Third of the triad:** [`levelcode-chat-sessions-design.md`](./levelcode-chat-sessions-design.md) stores a chat · [`levelcode-sessions-experience.md`](./levelcode-sessions-experience.md) lets you browse it · **this doc lets the project *remember itself*.** |
| 3 | +**Status:** **shipped** — M1–M4 complete (§10); M5, memory from you, in review (§12) · **Scope:** `extensions/levelcode-ai` + the sessions store · **Third of the triad:** [`levelcode-chat-sessions-design.md`](./levelcode-chat-sessions-design.md) stores a chat · [`levelcode-sessions-experience.md`](./levelcode-sessions-experience.md) lets you browse it · **this doc lets the project *remember itself*.** |
4 | 4 |
|
5 | 5 | > The wish, in the user's words: *"if in one session we tidy the CHANGELOG for v1.0.4, then a new session already knows something about what was achieved — the past experience keeps growing, and it feels like home."* That is the right ambition. It is also the feature most likely to become expensive, stale, creepy, or wrong. This document is mostly about **not** doing those four things while still delivering the magic. |
6 | 6 |
|
@@ -156,6 +156,7 @@ The magic, delivered quietly (never a wall of text): |
156 | 156 | | `sessions.memory.journalRecentDays` | `21` | recency window fed to the always-on digest | |
157 | 157 | | `sessions.memory.confirmFacts` | `false` | require a click before an inferred Fact becomes confirmed | |
158 | 158 | | `sessions.memory.recallTool` | `true` | expose `recall_sessions` to the agent | |
| 159 | +| `sessions.memory.notes` | `true` | expose `remember` / `forget` to the agent (§12) | |
159 | 160 |
|
160 | 161 | --- |
161 | 162 |
|
@@ -189,4 +190,92 @@ The magic, delivered quietly (never a wall of text): |
189 | 190 |
|
190 | 191 | --- |
191 | 192 |
|
| 193 | +## 12. Memory from you (M5) — and Search Sessions |
| 194 | + |
| 195 | +Everything in §3–§10 is memory LevelCode *infers*: the summarizer reads a finished session and writes an |
| 196 | +outcome and at most two facts. Nothing records what the person *says* they want kept, and the most valuable |
| 197 | +kind of memory, a correction ("no, use pnpm") or a confirmed approach, is usually phrased as an order, which |
| 198 | +§7's instruction guard rightly holds back when it comes from a transcript. |
| 199 | + |
| 200 | +### What changes |
| 201 | + |
| 202 | +- **Two agent tools, `remember` and `forget`.** The agent saves a note during the run when the person asks it |
| 203 | + to remember something, corrects how it works, confirms an approach that was not obvious, states a decision |
| 204 | + (with an absolute date), or says where something lives. Each note has a `kind` (`preference`, `project`, |
| 205 | + `reference`), the note `text`, and `said`: the person's own words, copied from their message. |
| 206 | +- **A note is credited to the person by code, not by the model** (`sessionMemory.groundNote`): |
| 207 | + 1. `said` must appear in what the person typed *this run*: their goal (handed over by the host as typed) |
| 208 | + and their ask_user choices and notes. Nothing the agent read counts. |
| 209 | + 2. The note must say no more than those words: at least two thirds of its content words must come from |
| 210 | + them (exact, a shared start of four letters or more, or a five-letter stem). |
| 211 | + |
| 212 | + A credited note is active at once, even phrased as an order, because the person said it. Any other note is |
| 213 | + still kept and listed, as *unconfirmed*: one Confirm click in the Memory tab makes it active. That is the |
| 214 | + same asymmetry as §7: a false negative costs one click, a false positive would put a repository's order into |
| 215 | + every future session. |
| 216 | +- **Notes live in `facts.jsonl`** as observations with `origin: "person"`, `kind`, `said` (secret-scrubbed) |
| 217 | + and `verified`, so they get the existing fold, panel, recall and "not true". Two rules differ from facts: |
| 218 | + repetition never promotes an uncredited note, and the summarizer's supersede pass is never shown a note |
| 219 | + (`supersedableFacts`). Only the person changes it: `remember` again, `forget`, or the panel. |
| 220 | +- **`MEMORY.md`** gets a "From you" section, each line with the person's words beside it. Every line older |
| 221 | + than a day says how old it is ("9 days old"). The always-on file is capped at 25 notes and 15 facts; the |
| 222 | + rest are named as "…and N more; recall_sessions finds them". |
| 223 | +- **`forget`** removes the best match for a query, only when the request is in the person's own words, and |
| 224 | + tells the agent what it removed so it can say so. |
| 225 | +- **Search Sessions…** (Command Palette, and a search icon in the Sessions panel's title bar): every session |
| 226 | + not in the trash, archived ones included, matched on its title and its whole conversation (all words must |
| 227 | + match), newest first, each with the line it matched. A quick pick, not a field in History: History stays |
| 228 | + the short list of recent work it was cut down to on 2026-08-02 (77d4f13), and this reaches what that list no |
| 229 | + longer shows. Transcripts are read at most 150 at a time and cached by file size and modification time. |
| 230 | + |
| 231 | +### Where the shape came from: Claude's desktop app |
| 232 | + |
| 233 | +Read from `/Applications/Claude.app` (2.42304.2) and the Claude Code it downloads (2.1.299) on 2026-10-19: |
| 234 | + |
| 235 | +- **Memory is files the agent writes itself**, one per memory, typed `user` / `feedback` / `project` / |
| 236 | + `reference`, with a one-line pointer in a `MEMORY.md` index that is always loaded and cut off after 200 |
| 237 | + lines. The agent is told to save when the user corrects *or confirms* its approach, to record the why, and |
| 238 | + to convert relative dates. Claude Code keeps the folder beside the project's transcripts |
| 239 | + (`~/.claude/projects/<project>/memory/`); in its Cowork mode the app passes another folder and the |
| 240 | + guidelines through environment variables, and allows writes there. |
| 241 | +- **Recall is per request**: a side model call is shown each memory's type, file name, date and description |
| 242 | + and picks up to five that clearly help the current request, never one it already picked. Each memory it |
| 243 | + brings back carries "This memory is N days old… verify" once it is older than a day. |
| 244 | +- **A background "dream"** periodically rewrites the memory files from recent sessions: merges, updates, |
| 245 | + prunes. |
| 246 | +- **History** is a metadata record per session (title and where the title came from, archived, last activity, |
| 247 | + turns, model) beside an append-only transcript, and transcript search is a plain case-insensitive match |
| 248 | + across sessions, returning one snippet each. |
| 249 | + |
| 250 | +What LevelCode does differently, on purpose: |
| 251 | + |
| 252 | +| | Claude | LevelCode | |
| 253 | +|---|---|---| |
| 254 | +| who decides a memory is the user's | the agent writes the file | the host checks the quoted words against what the person typed | |
| 255 | +| a memory phrased as an order | saved like any other | saved; active only when credited to the person, else one click | |
| 256 | +| recall | a model picks up to 5 per request | the capped always-on digest plus `recall_sessions` on demand | |
| 257 | +| age warning | on recalled memories older than a day | on every digest line older than a day | |
| 258 | +| consolidation | a model "dream" pass over the files | the existing deterministic fold on every seal | |
| 259 | +| history search | substring match, one snippet per session | every word must match, title matches first, one snippet each | |
| 260 | + |
| 261 | +Per-request selection was left out for a reason that is specific to LevelCode: memory reaches the agent in |
| 262 | +the system prompt, which providers cache together with the whole conversation after it. Changing it per |
| 263 | +request would make every new goal re-send the conversation at full price. Claude adds recalled memories to |
| 264 | +the conversation as attachments (its transcripts carry `attachment` records); doing that here touches how a |
| 265 | +goal is stored, titled, compacted and replayed, and is a separate change. |
| 266 | + |
| 267 | +### What was run (2026-10-19, macOS 27.0.1, Node 24) |
| 268 | + |
| 269 | +- `scripts/test-extensions.sh`, including `test/memoryNotes.test.js` (21: the crediting check, the fold, the |
| 270 | + digest, recall states, search over a real store), `test/memoryNotesHost.test.js` (8: the real runAgent with |
| 271 | + a stood-in provider, and the host halves lifted out of `extension.js`), and ten new hostile cases in |
| 272 | + `test/memoryPoisoning.test.js` (each order, quoted against a real goal, stays an inactive note). |
| 273 | +- Fifteen single-defect mutations, each turning a test red. |
| 274 | +- `scripts/memory-editor-check.js` in the dev build with a stand-in provider: 11 checks, from the remember |
| 275 | + call through the Memory tab, the next session's instructions and Search Sessions reopening the conversation. |
| 276 | + |
| 277 | +Not run: a real model deciding when to call `remember` (only a scripted one has), the browser edition, Linux. |
| 278 | + |
| 279 | +--- |
| 280 | + |
192 | 281 | *Store it (spine) · browse it (experience) · **remember it (this doc).** Together: a sessions feature where the project accumulates its own experience — small in every prompt, deep on request, legible and yours, and safe against the repo that would lie to it.* |
0 commit comments