Skip to content

Commit 2824011

Browse files
authored
Merge pull request #114 from levelcodeai/feat/memory-from-you
feat(memory): memory from you — remember/forget credited to the words you typed — and Search Sessions
2 parents b01537c + 3cd4b1a commit 2824011

12 files changed

Lines changed: 1597 additions & 23 deletions

‎docs/levelcode-sessions-experience.md‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,11 @@ It expands (`--mo-base`) into a third zone, so nothing is lost, only deferred to
183183

184184
### 4.4 Search & filter (in the panel)
185185

186+
> **As built:** the search field was taken out of the panel on 2026-08-02 (77d4f13), when History became a
187+
> list of the 30 most recent sessions. Search came back as **LevelCode: AI: Search Sessions…**, a quick pick
188+
> over every session's title and conversation — see `levelcode-sessions-memory.md` §12. The design below is
189+
> the original plan.
190+
186191
- **One field, three indexes:** title (weighted highest), files-edited, commands-run. Match highlights show *why* a session matched ("matched `refund.rb`").
187192
- **Scope switch:** `This project` (default) ↔ `All projects` — because sometimes you remember the work, not the repo.
188193
- **Filter chips** (toggle, combine): `★ pinned`, `⟳ has edits`, `⚠ interrupted`, `by model ▾`, `date ▾`. Chips animate in/out and the list reflows at `--mo-base`. Active filters are summarized in the empty-result state ("No interrupted sessions in the last 7 days — clear filters?").

‎docs/levelcode-sessions-memory.md‎

Lines changed: 121 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# LevelCode Sessions — Project Memory & Continuity
22

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*.**
44

55
> 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.
66
@@ -156,6 +156,7 @@ The magic, delivered quietly (never a wall of text):
156156
| `sessions.memory.journalRecentDays` | `21` | recency window fed to the always-on digest |
157157
| `sessions.memory.confirmFacts` | `false` | require a click before an inferred Fact becomes confirmed |
158158
| `sessions.memory.recallTool` | `true` | expose `recall_sessions` to the agent |
159+
| `sessions.memory.notes` | `true` | expose `remember` / `forget` to the agent (§12) |
159160

160161
---
161162

@@ -189,4 +190,123 @@ The magic, delivered quietly (never a wall of text):
189190

190191
---
191192

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, only when they typed it (not a
208+
diagram Retry, Continue or a resumed goal), and their ask_user choices and notes. Nothing the agent read
209+
counts.
210+
2. The note must say what those words say and no more: every content word of the note comes from them
211+
(exact, a shared start of four letters or more, or a five-letter stem), except a word naming where it
212+
applies ("repo", "project"); in their order; and each negated as it is in the quote (a not/never counts
213+
for the next content word in its clause, two cancel, "stop" and "avoid" count as one). Word overlap is
214+
not enough: "Use npm, never pnpm" has every word of "use pnpm, never npm".
215+
216+
A credited note is active at once, even phrased as an order, because the person said it. Any other note is
217+
still kept and listed, as *unconfirmed*: one Confirm click in the Memory tab makes it active. That is the
218+
same asymmetry as §7: a false negative costs one click, a false positive would put a repository's order into
219+
every future session.
220+
- **Notes live in `facts.jsonl`** as observations with `origin: "person"`, `kind`, `said` (secret-scrubbed)
221+
and `verified`, so they get the existing fold, panel, recall and "not true". Two rules differ from facts:
222+
repetition never promotes an uncredited note, and the summarizer's supersede pass is never shown a note
223+
(`supersedableFacts`). Only the person changes it: `remember` again, `forget`, or the panel.
224+
- **`MEMORY.md`** gets a "From you" section, each line with the person's words beside it. Every line older
225+
than a day says how old it is ("9 days old"). The always-on file is capped at 25 notes and 15 facts; the
226+
rest are named as "…and N more; recall_sessions finds them". A note the person confirmed in the Memory tab
227+
is listed there too, marked "you confirmed it": its quote was never matched to what they typed, so it is not
228+
shown as theirs (the Memory tab calls such a quote "the agent quoted").
229+
- **A credited note keeps the words it was credited on.** A later `remember` of the same note that cannot be
230+
credited does not change its text, its quote or its date.
231+
- **`forget`** removes the best match for a query only when the person's words (`said`, typed this run) ask for
232+
something to be forgotten ("forget", not "don't forget"; "remove" or "delete" only beside memory, note,
233+
rule or preference; "no longer true", "outdated") and name that memory: a word of its text, or of a
234+
credited note's quote, other than words every memory shares (`sessionMemory.asksToForget`). Otherwise it
235+
removes nothing and says which memory came closest; the Memory tab's Forget is the way out. It tells the
236+
agent what it removed, and says so when the store refused the write.
237+
- **Search Sessions…** (Command Palette, and a search icon in the Sessions panel's title bar): every session
238+
not in the trash, archived ones included, matched on its title and its whole conversation (all words must
239+
match), newest first, each with the line it matched. A quick pick, not a field in History: History stays
240+
the short list of recent work it was cut down to on 2026-08-02 (77d4f13), and this reaches what that list no
241+
longer shows. Titles come from the index, so every session's is checked at once; conversations are read 50
242+
at a time (`searchStep`) while the list fills in, matched whole, and cached by file size and modification
243+
time up to 20 million characters in all. Title matches are ranked first before the list is cut to 40.
244+
245+
### Where the shape came from: Claude's desktop app
246+
247+
Read from `/Applications/Claude.app` (2.42304.2) and the Claude Code it downloads (2.1.299) on 2026-10-19:
248+
249+
- **Memory is files the agent writes itself**, one per memory, typed `user` / `feedback` / `project` /
250+
`reference`, with a one-line pointer in a `MEMORY.md` index that is always loaded and cut off after 200
251+
lines. The agent is told to save when the user corrects *or confirms* its approach, to record the why, and
252+
to convert relative dates. Claude Code keeps the folder beside the project's transcripts
253+
(`~/.claude/projects/<project>/memory/`); in its Cowork mode the app passes another folder and the
254+
guidelines through environment variables, and allows writes there.
255+
- **Recall is per request**: a side model call is shown each memory's type, file name, date and description
256+
and picks up to five that clearly help the current request, never one it already picked. Each memory it
257+
brings back carries "This memory is N days old… verify" once it is older than a day.
258+
- **A background "dream"** periodically rewrites the memory files from recent sessions: merges, updates,
259+
prunes.
260+
- **History** is a metadata record per session (title and where the title came from, archived, last activity,
261+
turns, model) beside an append-only transcript, and transcript search is a plain case-insensitive match
262+
across sessions, returning one snippet each.
263+
264+
What LevelCode does differently, on purpose:
265+
266+
| | Claude | LevelCode |
267+
|---|---|---|
268+
| who decides a memory is the user's | the agent writes the file | the host checks the quoted words against what the person typed |
269+
| a memory phrased as an order | saved like any other | saved; active only when credited to the person, else one click |
270+
| recall | a model picks up to 5 per request | the capped always-on digest plus `recall_sessions` on demand |
271+
| age warning | on recalled memories older than a day | on every digest line older than a day |
272+
| consolidation | a model "dream" pass over the files | the existing deterministic fold on every seal |
273+
| history search | substring match, one snippet per session | every word must match, title matches first, one snippet each |
274+
275+
Per-request selection was left out for a reason that is specific to LevelCode: memory reaches the agent in
276+
the system prompt, which providers cache together with the whole conversation after it. Changing it per
277+
request would make every new goal re-send the conversation at full price. Claude adds recalled memories to
278+
the conversation as attachments (its transcripts carry `attachment` records); doing that here touches how a
279+
goal is stored, titled, compacted and replayed, and is a separate change.
280+
281+
### What was run (2026-10-19, macOS 27.0.1, Node 24)
282+
283+
- `scripts/test-extensions.sh`, including `test/memoryNotes.test.js` (21: the crediting check, the fold, the
284+
digest, recall states, search over a real store), `test/memoryNotesHost.test.js` (8: the real runAgent with
285+
a stood-in provider, and the host halves lifted out of `extension.js`), and ten new hostile cases in
286+
`test/memoryPoisoning.test.js` (each order, quoted against a real goal, stays an inactive note).
287+
- Fifteen single-defect mutations, each turning a test red.
288+
- `scripts/memory-editor-check.js` in the dev build with a stand-in provider: 11 checks, from the remember
289+
call through the Memory tab, the next session's instructions and Search Sessions reopening the conversation.
290+
291+
Not run: a real model deciding when to call `remember` (only a scripted one has), the browser edition, Linux.
292+
293+
### After review (2026-10-20, macOS, Node 24)
294+
295+
The PR review found ten problems, all real. Each fix is its own commit: credit rule (order and negation, every
296+
word from the quote), a credited note's words kept together, confirmed notes under "From you", the chip only
297+
when saved, `forget` tied to a request that names the memory, a failed removal reported, typed goals only,
298+
and Search Sessions over every session and whole conversations, ranked before the cut.
299+
300+
- `memoryNotes.test.js` now has 32 cases and `memoryNotesHost.test.js` 14; `scripts/test-extensions.sh`: 133
301+
files pass after merging develop (#112, #113).
302+
- 21 single-defect mutations of the fixes, run on a copy with the network refused: all 21 caught (two needed a
303+
test added).
304+
- `memory-editor-check.js`: 11 of 11 on a second run. On the first, "History lists the first conversation
305+
after New Session" timed out (its 15-second wait for the rebuilt panel); nothing in these fixes touches
306+
History.
307+
- Still not run: a real model, the browser edition, Linux. The stem rule (a shared five-letter start) can still
308+
pair two different words ("transaction" and "translate"); order and negation narrow what that can change.
309+
310+
---
311+
192312
*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

Comments
 (0)