feat: v9 brain modes — session focus, Ebbinghaus decay, dream consolidation, mid-turn recall - #1
Merged
Merged
Conversation
…dation, mid-turn recall
Turns the plugin from a recallable filing cabinet into something closer
to a working brain. All upgrades are wired into the hook layer; no new
MCP tool is required for any of them.
Brain mode upgrades (all opt-out via env vars)
- Continuous retrieval: every UserPromptSubmit recall now unions prompt
tokens, working-memory slots, the session-focus title, and recent file
paths from conversation_events. Top 3 hits are round-robined across
memory types so a single type doesn't crowd out others
(KIMI_MEMORY_DIVERSIFY=off to disable).
- Mid-turn recall: new PostToolUse hook fires on tool calls and surfaces
up to 2 stored conventions matching the tool's arguments (file path
stems, shell verbs). Cheap — no LLM call. Degrades silently on Kimi
versions that don't declare the PostToolUse event.
- Ebbinghaus decay: every memory carries a per-row stability_days and
last_rehearsed_at. Confidence is rewritten on SessionStart from
0.1 + 0.9 * exp(-days_since_rehearsal / stability). memory_reinforce
grows stability by 1.5x (cap 365 days) and stamps a fresh rehearsal.
Top project recall hit is auto-reinforced with a 60s debounce.
- Cross-session narrative: SessionStart lists the last 3 sessions for
the project (oldest → newest) with each session's focus title.
- Background consolidation ('dream pass'): every SessionStart clusters
active memories by embedding cosine (≥ 0.75) AND tag overlap (≥ 2),
and for each cluster of ≥ 3 siblings without an existing conclusion
child writes one synthesised conclusion. Idempotent.
KIMI_MEMORY_CONSOLIDATE=off to disable.
Session focus (auto-capture 'where we left off')
- Stop hook writes a working-typed memory titled
'Last focus: <truncated latest user prompt>' on every session with at
least one user prompt. Tags: focus, session-focus, in-flight.
expires_at = now + 30 days. Zero LLM cost — built from
conversation_events.summary. Disable with
KIMI_MEMORY_DISABLE_SESSION_FOCUS=1.
- SessionStart and UserPromptSubmit surface a [focus] line with the
most recent focus row's title + body snippet so the next session has
the thread to pick up.
memory_reset_project (re-clone wipe)
- New MCP tool: memory_reset_project(cwd, confirm?) wipes every
per-project row (memories, working memory, conversations, conversation
events, edges, synthesizes) so a re-cloned project starts clean.
confirm: false (default) is a dry run. The project_key is a hash of
the canonical path, so a re-clone is otherwise indistinguishable
from the original. Hook layer surfaces a [stale-memory] line on
SessionStart and UserPromptSubmit when directory birthtime is newer
than first_seen_at. memory_status now returns a 'reclone' diagnostic.
Persist refactor
- Collapses src/persist/{conversations,db,edges,memory,working}.js into
a single src/persist.js. The five modules were a single SQLite schema
split into five files; the new layout keeps the table openers and
migration helpers in one place. Public API (saveMemory, listMemories,
etc.) is unchanged. Net -1028 lines.
CLI
- New 'reset-project' subcommand mirrors memory_reset_project with the
same dry-run-first contract as 'prune'.
Schema
- SCHEMA_VERSION = 9. Migrations add stability_days and
last_rehearsed_at to memories. Existing rows backfill on first open
(stability defaults to 30, last_rehearsed_at defaults to updated_at).
Other
- Bump version 0.4.0 → 0.5.0 (package.json, kimi.plugin.json).
- README documents new env vars, new tool, and the brain-mode section.
- skills/kimi-memory/SKILL.md and kimi.plugin.json longDescription
updated to describe the new behaviour.
- New tests: 22-brain-modes (continuous retrieval, diversification,
decay, tool-recall), 22-reset-project (detectReclone + resetProject
end-to-end + MCP round-trip), 23-session-focus (capture, dedupe,
expires_at, focus line shape, integration with SessionStart /
UserPromptSubmit).
Tests: 247 pass, 1 skipped, 0 fail.
CI failed on 'Check formatting' (npm run format:check) because the new and modified files in the v9 commit were checked in with CRLF line endings — core.autocrlf=true on Windows converted LF to CRLF at stage time. .prettierrc and .editorconfig both declare 'lf', so prettier --check rejected every file in the diff on the Linux CI runner. Re-staged the touched files with LF endings and ran prettier --write on each so line widths, quote style, and trailing commas match the rest of the repo. No behaviour changes: tests still 247 pass, 1 skipped, 0 fail. Fixes the v9 brain-modes CI run.
The format check 'npm run format:check' (npm run check → prettier --check .) has been failing on 12 pre-existing files in main since before this PR — IMPROVEMENTS.md and 11 source/test files that predate the v9 work. They were never touched by this branch's diff, but until they're reformatted CI rejects every PR. prettier --write normalises line widths, quote style, and trailing commas to match the rest of the repo. No behaviour changes; tests remain 247 pass, 1 skipped, 0 fail. This is purely a fix for the v9 brain-modes CI run; these files are not part of the v9 surface.
The previous 'apply prettier to pre-existing files' commit accidentally picked up .claude/memory/store.db (a local SQLite state file from Claude Code that has nothing to do with the plugin). It must never have been staged. - git rm --cached the file from the index - Add .claude/ to .gitignore so future commits ignore the directory
CI was failing 'Check formatting' on the windows-latest matrix job because GitHub Actions' Windows runners default core.autocrlf=true, which checks out text files as CRLF regardless of the bytes stored in git. .prettierrc and .editorconfig both declare 'lf', so every file with CRLF on the runner fails prettier --check — 78 files on the v9 branch vs 1 file locally with core.autocrlf=false. This adds .gitattributes declaring eol=lf for all text files, with exceptions for known-binary artefacts (images, package-lock.json, generated coverage/dist outputs). With this attribute, a Windows runner checkout normalises to LF even when the runner's autocrlf would otherwise convert the other way. No source files were changed in this commit; .gitattributes alone is the fix.
The ubuntu-latest CI matrix entry exposed two latent bugs that only
surface on non-Windows hosts (the Windows entry was always passing).
canonicalizeRoot — on Linux, a Windows-style absolute path ('C:/foo/bar')
fell through path.resolve(), which POSIX interprets as a relative path
('C:' is just a filename) and joined onto the cwd. Result:
'/home/runner/work/.../C:/foo/bar'. The test expects 'C:\foo\bar'
on every platform — the function is the cross-platform canonicaliser.
Fix: when the trimmed input matches the Windows-absolute regex
('^[A-Za-z]:[\/]'), bypass path.resolve entirely and normalise
forward slashes to backslashes manually. On Windows hosts we still
uppercase the drive letter so 'c:\foo' and 'C:\foo' map
identically.
detectReclone — the test that simulates an 'old' directory via
utimesSync(cwd, sixtyDaysAgo, sixtyDaysAgo) was failing on Linux
because utimes does not change birthtime. stat.birthtimeMs stays at
'now' on ext4, so the function saw a brand-new directory and
returned isReclone=true instead of false.
Fix: clamp the directory timestamp to Math.min(birthtimeMs, mtimeMs).
On Windows and unmodified Linux directories birthtime ≤ mtime, so the
min equals birthtime (the real creation time). When mtime has been
backdated (test scenarios or admin tools), the older mtime wins,
which is the right 'how long has this directory existed' signal
across platforms. The 0-birthtime fallback to mtime is preserved
for filesystems that don't track birthtime.
No behaviour change on Windows — the test suite still reports 247
pass, 1 skipped, 0 fail locally.
… test setup
canonicalizeRoot:
The CI on ubuntu-latest caught two remaining issues:
1. Drive-letter casing on Linux — the previous patch only uppercased
the drive letter when process.platform === 'win32'. On Linux, the
drive letter came out lowercase, so canonicalizeRoot('C:/foo/bar')
returned 'c:\foo\bar' instead of 'C:\foo\bar'. The test
asserts that 'c:/foo/bar' and 'C:/foo/bar' map to the same
canonical root, regardless of the host OS. Fix: uppercase the drive
letter on every platform — the canonical form is what callers
compare, not what the local OS reports.
2. detectReclone test setup was mathematically inconsistent. The test
backdated the directory mtime to 60 days ago and set first_seen_at
to yesterday, expecting the function to take the 'long-lived project'
branch. But dirAheadMs (dirTime - firstSeen) is negative (-59d) in
that setup, so the function correctly takes the 'predates' branch.
Updated to: mtime = 10 days ago, first_seen_at = 30 days ago.
Now dirAheadMs = +20d (positive) and dirAgeMs = +10d (>= 7d ceiling),
which is exactly the combination that flips the function into the
'long-lived project' branch. Updated the leading comment to explain
the math.
The previous edit kept the .replace chain on a single line that came in just at prettier's 100-column limit. Run prettier --write on the file to break it into the prettier-friendly multi-line form.
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.
Summary
Turns
kimi-memoryfrom a recallable filing cabinet into something closer to a working brain. All upgrades are wired into the hook layer; no new MCP tool is required for any of them. Adds a single new MCP tool (memory_reset_project) for the re-clone wipe case, and a newPostToolUsehook for mid-turn recall.Version bump
0.4.0→0.5.0. SchemaSCHEMA_VERSION = 9.Brain mode (the four behavioural upgrades)
UserPromptSubmitrecall now unions prompt tokens, working-memory slots, the session-focus title, and recent file paths fromconversation_events. The top 3 hits are round-robined across memory types so a single type doesn't crowd out the rest.PostToolUsehook surfaces up to 2 stored conventions matching the tool's arguments (file path stems, shell verbs). Cheap — no LLM call. Degrades silently on Kimi versions that don't declare the event.stability_daysandlast_rehearsed_at. Confidence is rewritten onSessionStartfrom0.1 + 0.9 * exp(-days / stability).memory_reinforcegrows stability by 1.5× (cap 365) and stamps a fresh rehearsal. The hook auto-reinforces the top project recall hit with a 60-second debounce so the feedback loop is closed without manual calls.SessionStartlists the last 3 sessions for the project (oldest → newest) with each session's focus title and body snippet.Session focus — "where we left off"
Stophook writes aworking-typed memory titledLast focus: <truncated latest user prompt>whenever the session has at least one user prompt. Tags:focus,session-focus,in-flight.expires_at = now + 30 days. Zero LLM cost — built fromconversation_events.summary. Disable viaKIMI_MEMORY_DISABLE_SESSION_FOCUS=1.SessionStartandUserPromptSubmitsurface a[focus]line so the next session has the thread to pick up.Background consolidation ("dream pass")
Every
SessionStartclusters active memories by embedding cosine (≥ 0.75) and tag overlap (≥ 2 shared tags), and for each cluster of ≥ 3 siblings with no existingconclusionchild writes one synthesisedconclusion. Idempotent viamemory_synthesizescoverage check. Opt out withKIMI_MEMORY_CONSOLIDATE=off.memory_reset_project(re-clone wipe)memory_reset_project(cwd, confirm?)wipes every per-project row (memories, working memory, conversations, conversation events, edges, synthesizes). Theproject_keyis a hash of the canonical path, so a re-clone is otherwise indistinguishable from the original.confirm: falseis a dry run. Global DB and other project DBs are never touched. The hook layer surfaces a[stale-memory]line onSessionStartandUserPromptSubmitwhen directory birthtime is newer thanfirst_seen_at. CLI gets a matchingreset-projectsubcommand.Persist refactor
Collapses
src/persist/{conversations,db,edges,memory,working}.jsinto a singlesrc/persist.js. The five modules were a single SQLite schema split across five files; the new layout keeps the table openers and migration helpers in one place. Public API (saveMemory,listMemories, …) is unchanged. Net −1028 lines across the persist module.Schema
SCHEMA_VERSION = 9. Migrations addstability_daysandlast_rehearsed_attomemories. Existing rows backfill on first open (stability = 30,last_rehearsed_at = updated_at).Tests
3 new test files:
tests/22-brain-modes.test.js— continuous retrieval union, diversification round-robin, Ebbinghaus decay curve, tool-recall path/verb triggers.tests/22-reset-project.test.js—detectReclonebranches,resetProjectisolation, MCP round-trip dry-run + apply, end-to-end re-clone wipe.tests/23-session-focus.test.js— capture, dedupe within session,expires_atenforcement, focus line shape, integration withSessionStart/UserPromptSubmit.247 pass, 1 skipped, 0 fail.
Why this approach fits
The plugin already had the primitives —
conclusiontyping,memory_synthesizes,memory_reinforce,memory_edges— but none of them were creating themselves automatically. The same goes for the[focus]/[recall: i/N]surface area: the hook layer already had the plumbing, it just wasn't pulling from enough cues. v9 wires all of those into the hook lifecycle and lets the user opt out per-knob via env var, matching theKIMI_MEMORY_AUTO_EXTRACT/KIMI_MEMORY_SECRET_SCANpattern.The persist refactor is a precondition for several of these — schema v9 needs
ALTER TABLE memories ADD COLUMN stability_daysto live alongside the other migrations, and the previous five-file split made it awkward to keep the table openers in sync.Checklist
package.jsonversion bumped (0.4.0 → 0.5.0)kimi.plugin.jsonversion + longDescription updatedskills/kimi-memory/SKILL.mdupdated.envtouched