Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions docs/levelcode-sessions-experience.md
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,11 @@ It expands (`--mo-base`) into a third zone, so nothing is lost, only deferred to

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

> **As built:** the search field was taken out of the panel on 2026-08-02 (77d4f13), when History became a
> list of the 30 most recent sessions. Search came back as **LevelCode: AI: Search Sessions…**, a quick pick
> over every session's title and conversation — see `levelcode-sessions-memory.md` §12. The design below is
> the original plan.

- **One field, three indexes:** title (weighted highest), files-edited, commands-run. Match highlights show *why* a session matched ("matched `refund.rb`").
- **Scope switch:** `This project` (default) ↔ `All projects` — because sometimes you remember the work, not the repo.
- **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?").
Expand Down
122 changes: 121 additions & 1 deletion docs/levelcode-sessions-memory.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# LevelCode Sessions — Project Memory & Continuity

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

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

Expand Down Expand Up @@ -156,6 +156,7 @@ The magic, delivered quietly (never a wall of text):
| `sessions.memory.journalRecentDays` | `21` | recency window fed to the always-on digest |
| `sessions.memory.confirmFacts` | `false` | require a click before an inferred Fact becomes confirmed |
| `sessions.memory.recallTool` | `true` | expose `recall_sessions` to the agent |
| `sessions.memory.notes` | `true` | expose `remember` / `forget` to the agent (§12) |

---

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

---

## 12. Memory from you (M5) — and Search Sessions

Everything in §3–§10 is memory LevelCode *infers*: the summarizer reads a finished session and writes an
outcome and at most two facts. Nothing records what the person *says* they want kept, and the most valuable
kind of memory, a correction ("no, use pnpm") or a confirmed approach, is usually phrased as an order, which
§7's instruction guard rightly holds back when it comes from a transcript.

### What changes

- **Two agent tools, `remember` and `forget`.** The agent saves a note during the run when the person asks it
to remember something, corrects how it works, confirms an approach that was not obvious, states a decision
(with an absolute date), or says where something lives. Each note has a `kind` (`preference`, `project`,
`reference`), the note `text`, and `said`: the person's own words, copied from their message.
- **A note is credited to the person by code, not by the model** (`sessionMemory.groundNote`):
1. `said` must appear in what the person typed *this run*: their goal, only when they typed it (not a
diagram Retry, Continue or a resumed goal), and their ask_user choices and notes. Nothing the agent read
counts.
2. The note must say what those words say and no more: every content word of the note comes from them
(exact, a shared start of four letters or more, or a five-letter stem), except a word naming where it
applies ("repo", "project"); in their order; and each negated as it is in the quote (a not/never counts
for the next content word in its clause, two cancel, "stop" and "avoid" count as one). Word overlap is
not enough: "Use npm, never pnpm" has every word of "use pnpm, never npm".

A credited note is active at once, even phrased as an order, because the person said it. Any other note is
still kept and listed, as *unconfirmed*: one Confirm click in the Memory tab makes it active. That is the
same asymmetry as §7: a false negative costs one click, a false positive would put a repository's order into
every future session.
- **Notes live in `facts.jsonl`** as observations with `origin: "person"`, `kind`, `said` (secret-scrubbed)
and `verified`, so they get the existing fold, panel, recall and "not true". Two rules differ from facts:
repetition never promotes an uncredited note, and the summarizer's supersede pass is never shown a note
(`supersedableFacts`). Only the person changes it: `remember` again, `forget`, or the panel.
- **`MEMORY.md`** gets a "From you" section, each line with the person's words beside it. Every line older
than a day says how old it is ("9 days old"). The always-on file is capped at 25 notes and 15 facts; the
rest are named as "…and N more; recall_sessions finds them". A note the person confirmed in the Memory tab
is listed there too, marked "you confirmed it": its quote was never matched to what they typed, so it is not
shown as theirs (the Memory tab calls such a quote "the agent quoted").
- **A credited note keeps the words it was credited on.** A later `remember` of the same note that cannot be
credited does not change its text, its quote or its date.
- **`forget`** removes the best match for a query only when the person's words (`said`, typed this run) ask for
something to be forgotten ("forget", not "don't forget"; "remove" or "delete" only beside memory, note,
rule or preference; "no longer true", "outdated") and name that memory: a word of its text, or of a
credited note's quote, other than words every memory shares (`sessionMemory.asksToForget`). Otherwise it
removes nothing and says which memory came closest; the Memory tab's Forget is the way out. It tells the
agent what it removed, and says so when the store refused the write.
- **Search Sessions…** (Command Palette, and a search icon in the Sessions panel's title bar): every session
not in the trash, archived ones included, matched on its title and its whole conversation (all words must
match), newest first, each with the line it matched. A quick pick, not a field in History: History stays
the short list of recent work it was cut down to on 2026-08-02 (77d4f13), and this reaches what that list no
longer shows. Titles come from the index, so every session's is checked at once; conversations are read 50
at a time (`searchStep`) while the list fills in, matched whole, and cached by file size and modification
time up to 20 million characters in all. Title matches are ranked first before the list is cut to 40.

### Where the shape came from: Claude's desktop app

Read from `/Applications/Claude.app` (2.42304.2) and the Claude Code it downloads (2.1.299) on 2026-10-19:

- **Memory is files the agent writes itself**, one per memory, typed `user` / `feedback` / `project` /
`reference`, with a one-line pointer in a `MEMORY.md` index that is always loaded and cut off after 200
lines. The agent is told to save when the user corrects *or confirms* its approach, to record the why, and
to convert relative dates. Claude Code keeps the folder beside the project's transcripts
(`~/.claude/projects/<project>/memory/`); in its Cowork mode the app passes another folder and the
guidelines through environment variables, and allows writes there.
- **Recall is per request**: a side model call is shown each memory's type, file name, date and description
and picks up to five that clearly help the current request, never one it already picked. Each memory it
brings back carries "This memory is N days old… verify" once it is older than a day.
- **A background "dream"** periodically rewrites the memory files from recent sessions: merges, updates,
prunes.
- **History** is a metadata record per session (title and where the title came from, archived, last activity,
turns, model) beside an append-only transcript, and transcript search is a plain case-insensitive match
across sessions, returning one snippet each.

What LevelCode does differently, on purpose:

| | Claude | LevelCode |
|---|---|---|
| who decides a memory is the user's | the agent writes the file | the host checks the quoted words against what the person typed |
| a memory phrased as an order | saved like any other | saved; active only when credited to the person, else one click |
| recall | a model picks up to 5 per request | the capped always-on digest plus `recall_sessions` on demand |
| age warning | on recalled memories older than a day | on every digest line older than a day |
| consolidation | a model "dream" pass over the files | the existing deterministic fold on every seal |
| history search | substring match, one snippet per session | every word must match, title matches first, one snippet each |

Per-request selection was left out for a reason that is specific to LevelCode: memory reaches the agent in
the system prompt, which providers cache together with the whole conversation after it. Changing it per
request would make every new goal re-send the conversation at full price. Claude adds recalled memories to
the conversation as attachments (its transcripts carry `attachment` records); doing that here touches how a
goal is stored, titled, compacted and replayed, and is a separate change.

### What was run (2026-10-19, macOS 27.0.1, Node 24)

- `scripts/test-extensions.sh`, including `test/memoryNotes.test.js` (21: the crediting check, the fold, the
digest, recall states, search over a real store), `test/memoryNotesHost.test.js` (8: the real runAgent with
a stood-in provider, and the host halves lifted out of `extension.js`), and ten new hostile cases in
`test/memoryPoisoning.test.js` (each order, quoted against a real goal, stays an inactive note).
- Fifteen single-defect mutations, each turning a test red.
- `scripts/memory-editor-check.js` in the dev build with a stand-in provider: 11 checks, from the remember
call through the Memory tab, the next session's instructions and Search Sessions reopening the conversation.

Not run: a real model deciding when to call `remember` (only a scripted one has), the browser edition, Linux.

### After review (2026-10-20, macOS, Node 24)

The PR review found ten problems, all real. Each fix is its own commit: credit rule (order and negation, every
word from the quote), a credited note's words kept together, confirmed notes under "From you", the chip only
when saved, `forget` tied to a request that names the memory, a failed removal reported, typed goals only,
and Search Sessions over every session and whole conversations, ranked before the cut.

- `memoryNotes.test.js` now has 32 cases and `memoryNotesHost.test.js` 14; `scripts/test-extensions.sh`: 133
files pass after merging develop (#112, #113).
- 21 single-defect mutations of the fixes, run on a copy with the network refused: all 21 caught (two needed a
test added).
- `memory-editor-check.js`: 11 of 11 on a second run. On the first, "History lists the first conversation
after New Session" timed out (its 15-second wait for the rebuilt panel); nothing in these fixes touches
History.
- Still not run: a real model, the browser edition, Linux. The stem rule (a shared five-letter start) can still
pair two different words ("transaction" and "translate"); order and negation narrow what that can change.

---

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