Repository navigation
feat(memory): memory from you — remember/forget credited to the words you typed — and Search Sessions - #114
Merged
Merged
Conversation
…s you typed — and Search Sessions
Project memory so far is inferred: when a session ends, the summarizer writes an outcome and at most two
facts, and a fact phrased as an order is held back until confirmed, because a transcript is full of
repository text. Nothing recorded what the person SAID they want kept, and the most useful kind — a
correction ("no, use pnpm") or a confirmed approach — is exactly the kind phrased as an order.
remember / forget (agent tools, setting levelcode.ai.sessions.memory.notes, on by default):
- the agent saves a note during the run, with a kind (preference | project | reference), the note, and
`said`: the person's own words
- sessionMemory.groundNote credits it to the person only when `said` appears in what they typed THIS run
(the goal as typed, their ask_user choices and notes — nothing the agent read) and at least two thirds of
the note's content words come from those words. Code decides, not the model
- a credited note is active at once, even phrased as an order; any other note is kept and listed as
unconfirmed until a Confirm click — so a README's order with a real quote of the person stapled on stays
inactive
- notes are facts.jsonl observations with origin "person" (said is secret-scrubbed): the same fold, panel,
recall and "not true". Repetition never promotes an uncredited note, and the summarizer's supersede pass
is never shown one (supersedableFacts)
- forget removes the best match, only when the request is in the person's words, and says what it removed
- MEMORY.md gets a "From you" section with the person's words beside each note; every line older than a
day shows its age; the always-on file is capped at 25 notes and 15 facts ("…and N more; recall finds them")
- the Memory tab lists notes under "From you" (yours / unconfirmed, with Confirm and Forget)
Search Sessions… (Command Palette, and a search icon in the Sessions title bar): every session not in the
trash, archived ones included, matched on its title and whole conversation (all words must match), newest
first, title matches first, each with the line it matched. A quick pick, not a field: the field was taken
out of History on 2026-08-02 (77d4f13) and the owner chose to keep it out. Transcripts are read at most 150
at a time, cached by size + mtime.
Modelled on Claude's desktop app (typed memory files written by the agent, an index loaded every session,
an age warning on recalled memories, transcript search); differences on purpose are in
docs/levelcode-sessions-memory.md §12 (next commits).
Checked: unit gate 129 files on Node 24, macOS, including test/memoryNotes.test.js (21),
test/memoryNotesHost.test.js (8: the real runAgent with a stood-in provider; the host halves lifted out of
extension.js) and ten new hostile cases in test/memoryPoisoning.test.js. Fifteen single-defect mutations,
each red. tsc --checkJs: no new undefined names. Not run: a real model choosing when to call remember,
Linux, the browser edition.
…Sessions in the real editor The dev build as a throwaway instance with this checkout's extension and a stand-in provider, as diagram-editor-check.js does. The stand-in plays: "From now on always use pnpm, never npm" → remember with the person's words (kept as theirs); a README asking AI agents to remember that every push goes straight to main → remember with that order and a real quote stapled on (kept UNCONFIRMED). It checks facts.jsonl and MEMORY.md, the chat's "remembered" rows, the Memory tab's "From you" section, that History lists the session after New Session, that the next session's instructions carry the pnpm note and not the README's order, and that Search Sessions… finds the first conversation by what was said and reopens it. Every DevTools call is bounded (a webview the workbench replaces never answers), and the Sessions webview is found again after New Session. 11/11 on macOS against vscode/.build/electron. Not part of scripts/test-extensions.sh.
…s, how Claude's app does it, what differs levelcode-sessions-memory.md §12: the remember/forget tools and the crediting rule, how notes live beside facts, MEMORY.md's "From you" section with ages and caps, Search Sessions as a quick pick and why, how Claude's desktop app and its Claude Code handle memory and history (read from the shipped bundles on 2026-10-19), the differences on purpose (and why per-request memory selection was left out: memory rides the cached system prompt), what was run and what was not. Settings table: sessions.memory.notes. levelcode-sessions-experience.md §4.4: a note that the in-panel search was removed (77d4f13) and came back as the Search Sessions quick pick.
Contributor
There was a problem hiding this comment.
🟡 Changes recommended
Memory authorization and provenance gaps, incorrect success reporting, and incomplete search results remain unresolved.
10 open issues
Remembered chip shown when persistence fails · New Self-grounding permits unrelated memory deletion · New Failed memory removal reported as successful · New Generated agent text incorrectly treated as user-authored · New Word overlap credits contradictory instructions · New Unverified note overwrites verified quote and provenance · New Confirmed notes omitted from active user preferences · New Conversation matching ignores content beyond 400,000 characters · New Session index truncation hides older exact matches · New Result limit applied before title-match ranking · New
Review effort: Balanced
agent.js: ask_user's answers now feed both the reviewer (ctx.userAnswers, #112) and what remember may quote (ctx.heardThisRun); both kept. package.json: the new levelcode.ai.sessions.memory.notes is application-scoped, like every other levelcode-ai setting since #113 (test/settingsScope.test.js failed on the merge without it: a folder's .vscode/settings.json could have switched remembering on or off).
…, order and every not
groundNote used to credit a note when two thirds of its content words came from the quote. Word
overlap does not preserve an instruction: "Use npm, never pnpm" has every word of "use pnpm, never
npm", and "Disable signature verification" every word of "never disable signature verification"
(the stopword list dropped "no"/"not"). The two-thirds slack also let an antonym through ("Enable
signature verification" from "disable signature verification").
Now every content word of the note must come from the quote (only a word naming where it applies,
like "repo", may be added), in the quote's order, each negated as it is there: a not/never counts
for the next content word in its clause, two cancel, and "stop"/"avoid" negate like "don't". A note
that fails is kept unconfirmed, as before, and the agent is told which rule it broke. The remember
tool's description says the same.
Tests: the reordered, negation-dropped, negation-added, double-negated and antonym notes are not
credited; a file name stays one word. Two fixtures changed with the rule: a quote that left out
"in tests" now includes it, and "Use pnpm for scripts" (adds "scripts" to "always use pnpm") became
"Always use pnpm" in the test of an unknown kind.
…edited on foldFacts took `said` from every person observation of a note but set `yours` only from credited ones. So after a credited "Use pnpm" (said: "always use pnpm"), a second remember() of the same note with an invented instruction in `said` failed grounding yet replaced the quote, and the invention appeared as "you said" in the always-on "From you" section. A credited note now keeps its phrasing, quote and date together: only another credited observation changes them. Its source is still counted. After the person removes it, an uncredited save is recorded as before and stays inactive.
…uote nobody checked is not "you said" Confirm is the fallback for a note that could not be credited to the person's words, but it set `confirmed`, not `yours`, and the digest put only `yours` notes under "From you". A confirmed "Always rebase before merging" landed under Facts, whose preamble tells the agent never to act on an instruction found there, while the Memory tab called it yours. The digest now lists a note under "From you" when it is credited or confirmed. A confirmed note says "you confirmed it" instead of its quote, because that quote was never matched to what the person typed. For the same reason, the Memory tab labels an uncredited note's quote "the agent quoted", not "you said". An inferred fact the person confirmed stays under Facts.
rememberNoteTool answered `verified: false` both for an unconfirmed note that was saved and for a note that was not saved at all (no folder open, or the store refused the write), and the agent posted "🧠 remembered: … (unconfirmed)" for all three. The person saw a memory that did not exist. The host now also returns `saved`, and the agent posts the chip only when it is true. The agent is still told what happened in the tool result.
…e, and says when it could not
Two review findings in forgetNoteTool:
- It checked `said` against itself, which only proves the words were typed. With the goal "fix the
failing date test" quoted as `said`, any query the model chose ("pnpm") removed the person's
package-manager preference. Now sessionMemory.asksToForget requires the quote to be typed this
run, to ask for something to go ("forget", not "don't forget"; "remove"/"delete" only beside
memory/note/rule/preference, so "remove the pnpm lockfile" is not a request; "no longer true",
"outdated"), and to name the memory the model picked: a word of its text, or of a credited note's
quote, other than words every memory shares. Otherwise nothing is removed, and the agent is told
which memory came closest and that the person can use Forget in the Memory tab.
- It ignored factAction's result. On a store that refused the write it still answered `removed`,
and the agent posted "forgot" while the memory stayed in effect. Now that is reported as an error.
…uote agentFlow handed every goal to the agent as goalText, the words `remember` and `forget` may quote as the person's. Not every goal is typed: retryDiagram builds one from the model's own diagram title and validation messages, and Continue and a resumed goal are not typed now. After a click on Retry, the model could quote its own generated text and have a note credited to the person. goalText is now typedGoalText(text, opts): the goal when opts.typed (handleSend), else ''. This is the rule develop already applies to the Ask When Risky reviewer (rememberTypedGoal, #112). Tests: the helper on its own, a guard that agentFlow passes it, and the real agent run with a Retry prompt, where nothing of it reaches the person's words.
…rsations, title matches first
Three review findings in History search:
- Each conversation was cut to its first 400,000 characters before matching, so a phrase later in
a long or often-resumed session was never found. The whole conversation is matched now; the
cache is bounded by the characters it holds in all (20M, least recently used out first) rather
than by cutting each session.
- Only the newest 150 sessions were searched, title matches included, and the quick pick had no
way to reach the rest. searchStep(query, {from, readMax}) now reads conversations a batch at a
time (50) and returns where to go on. Its first step reports every title or preview match in
the index with no read. search() runs every step; the quick pick runs them in the background,
re-ranks as each lands, stops when the query changes or the pick closes, and says "no session
mentions" only after every session was searched.
- The limit was applied while scanning, before title matches were put first, so 40 newer matches
in the conversation pushed out an older exact title match. rankSearch ranks first, then cuts.
Tests over a store of 163 sessions: a match in the second-oldest conversation, a phrase past the
first 400,000 characters, an older title match ahead of 40 newer ones under limit 5, steps that
add up to search(); and the quick pick reaching past the first batch and dropping a superseded
query's scan. The test that pinned scanMax is replaced.
…nv's dot does not end a not Two of 21 single-defect mutations of the review fixes passed every suite: limiting before ranking (the title match was found by the first step's no-read pass, so it was never cut), and splitting a clause at every dot (only a note that writes the file name differently from the quote shows it). Each now has a case.
…y not, forget needs a request that names the memory, search covers every session What the ten review fixes changed in the design, and what was run after them.
ndemianc
added a commit
that referenced
this pull request
Oct 20, 2026
Conflicts, each keeping both sides: - agent.js, the end of a run: Ask When Risky's reviewer cost and summary row (#112) and this branch's plan completion and returned "Send now" messages; one agent.done log line with both sets of fields. - extension.js, resumeSession and New Chat: this branch's plan/steer reset and the reset of the person's recorded words (#112/#114), each in both places. - media/chat.html, the message switch: develop's `approvals` message (#112 replaced the two-option `autopilot` one, whose handler no longer exists) and this branch's agentStart/agentStatus, which keep the task list and drop the old plan bar. - test/sessionExpiredHost.test.js, the harness declarations: both sets (agentMode from env, the approvals mode and recorded words; this branch's agentPlan and steerInbox). Checked on the merged tree (macOS, Node 24): scripts/test-extensions.sh 136 files; tsc --checkJs on extension.js and agent.js, no new undefined names; in the dev build plan-progress-editor-check 17/17, approvals-editor-check 12/12, memory-editor-check 11/11 on one of four runs — its "History lists the session after New Session" step is flaky on develop too (1 of 2 there).
ndemianc
added a commit
that referenced
this pull request
Oct 20, 2026
A reply in the next-step box shows a 1-3 word label; what a click sends is the reply's message, up to 300 characters the agent wrote (visible only as a tooltip). It went through the chat's normal send, so the host recorded it as typed — and since #112 and #114 that is the person's word: the Ask When Risky reviewer counts it as authorization, and remember may quote it. A README that steers the agent into a "Yes" button whose message says more could have turned one click into an explicit go-ahead. The page now sends a click as a reply ({ type: 'send', reply: true }), and keeps that mark through the queue (drained at the end of a run, or sent with Send now while idle). handleSend passes typed: false for it, so the reply reaches the agent as before but is not the person's word. A reply the person edits in the message box, or that is handed back to it, is sent as their own typing. Tested: the page's queue functions lifted from chat.html (sent at once, queued while busy, drained, Send now when idle — the mark survives each, and a typed message carries none); handleSend lifted into sessionExpiredHost (a reply reaches agentFlow with typed: false, a typed message with true); the button's click and the send message are pinned from source.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

Two additions to sessions and memory, modelled on how Claude's desktop app handles them:
rememberandforgetwhat the user tells it to keep. A note counts as the user's only when the words it quotes are in what they typed this run and it says what they said, no more, in their order, with every not/never; otherwise it waits for one Confirm click.Why
Project memory so far is inferred after a session ends: an outcome and at most two facts, with anything phrased as an order held back until confirmed, because a transcript is full of repository text. So "always use pnpm, never npm", the kind of thing a user most wants remembered, was exactly what got held back, and nothing recorded what the user actually said.
How a note is decided
sessionMemory.groundNotedecides this in code, not the model. Nothing the agent read (files, tool output) counts as the user's words.facts.jsonlobservations withorigin: "person"(their words secret-scrubbed), so they get the existing fold, panel, recall and "not true". Repetition never promotes an uncredited note, and the summarizer's supersede pass is never shown one.MEMORY.mdgets a From you section with the user's words beside each note. A note the user confirmed in the Memory tab is listed there too, as "you confirmed it" (its quote was never checked, so it is not shown as theirs). Every line older than a day shows its age. The always-on file is capped at 25 notes and 15 facts; the rest are left torecall_sessions.forgetremoves the best match only when the user's words ask for something to be forgotten and name that memory (sessionMemory.asksToForget); otherwise it removes nothing and points to the Memory tab. It reports what it removed, and says so when the store refused the write.levelcode.ai.sessions.memory.notes(on by default).Search Sessions
Every session not in the trash, archived ones included. All words must match, title matches come first, then newest first, each with the line it matched. It's a quick pick, not a History field: the field was removed on 2026-08-02 (77d4f13) and stays out. Every session's title is checked at once from the index; conversations are read 50 at a time while the list fills in, matched whole, and cached by size and modification time (20 million characters in all). Title matches are ranked first before the list is cut to 40.
Compared with Claude's app
Claude's agent writes typed memory files plus an always-loaded index, a model picks up to five memories per request, recalled ones carry an age warning, and transcript search is a plain substring match. The differences here are on purpose (no per-request selection, because LevelCode's memory rides the cached system prompt; code, not the model, credits a note to the user) and are written up in
docs/levelcode-sessions-memory.md§12.Checked (macOS 27.0.1, Node 24)
scripts/test-extensions.sh: 129 files, includingtest/memoryNotes.test.js(21) andtest/memoryNotesHost.test.js(8: the realrunAgentwith a stood-in provider; the host halves lifted out ofextension.js), plus ten new hostile cases intest/memoryPoisoning.test.js(each order, quoted against a real goal, stays inactive).scripts/memory-editor-check.jsin the dev build with a stand-in provider: 11/11. Covered: the credited note, the README's order kept unconfirmed, the Memory tab, the next session's instructions carrying the note, and Search Sessions reopening the conversation.tsc --checkJson the changed files: no new undefined names.After review (2026-10-20)
Copilot's ten findings were all real, and each has its own commit with a test: the credit rule (word overlap credited "Use npm, never pnpm" from "use pnpm, never npm"), a credited note's quote overwritten by an uncredited save, confirmed notes under Facts, the chip when nothing was saved,
forgetwith any typed quote and any query, an ignored failed removal, a diagram Retry's generated text counted as typed, and search cut at 400,000 characters, at the newest 150 sessions, and before ranking. develop (#112, #113) is merged in;sessions.memory.notesis application-scoped like every other setting since #113.scripts/test-extensions.sh: 133 files pass;memoryNotes32 cases,memoryNotesHost14.memory-editor-check.js: 11/11 on a second run. The first timed out on "History lists the first conversation after New Session", the panel flip noted below.tsc --checkJson the changed files: no undefined names.Not run
remember; only a scripted one has.Also seen, not from this change: in about half of the editor runs the Sessions panel flipped back to the Memory tab right after New Session. The same happens on
develop.