Skip to content

feat: v9 brain modes — session focus, Ebbinghaus decay, dream consolidation, mid-turn recall - #1

Merged
cbuntingde merged 8 commits into
mainfrom
feat-v9-brain-modes
Aug 5, 2026
Merged

feat: v9 brain modes — session focus, Ebbinghaus decay, dream consolidation, mid-turn recall#1
cbuntingde merged 8 commits into
mainfrom
feat-v9-brain-modes

Conversation

@cbuntingde

Copy link
Copy Markdown
Owner

Summary

Turns kimi-memory 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. Adds a single new MCP tool (memory_reset_project) for the re-clone wipe case, and a new PostToolUse hook for mid-turn recall.

Version bump 0.4.00.5.0. Schema SCHEMA_VERSION = 9.

Brain mode (the four behavioural upgrades)

  • Continuous retrieval — every UserPromptSubmit recall now unions prompt tokens, working-memory slots, the session-focus title, and recent file paths from conversation_events. The top 3 hits are round-robined across memory types so a single type doesn't crowd out the rest.
  • Mid-turn recall — new PostToolUse hook 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.
  • 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 / stability). memory_reinforce grows 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.
  • Cross-session narrativeSessionStart lists the last 3 sessions for the project (oldest → newest) with each session's focus title and body snippet.

Session focus — "where we left off"

  • Stop hook writes a working-typed memory titled Last 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 from conversation_events.summary. Disable via KIMI_MEMORY_DISABLE_SESSION_FOCUS=1.
  • SessionStart and UserPromptSubmit surface a [focus] line so the next session has the thread to pick up.

Background consolidation ("dream pass")

Every SessionStart clusters active memories by embedding cosine (≥ 0.75) and tag overlap (≥ 2 shared tags), and for each cluster of ≥ 3 siblings with no existing conclusion child writes one synthesised conclusion. Idempotent via memory_synthesizes coverage check. Opt out with KIMI_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). The project_key is a hash of the canonical path, so a re-clone is otherwise indistinguishable from the original. confirm: false is a dry run. Global DB and other project DBs are never touched. The hook layer surfaces a [stale-memory] line on SessionStart and UserPromptSubmit when directory birthtime is newer than first_seen_at. CLI gets a matching reset-project subcommand.

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 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 add stability_days and last_rehearsed_at to memories. 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.jsdetectReclone branches, resetProject isolation, MCP round-trip dry-run + apply, end-to-end re-clone wipe.
  • tests/23-session-focus.test.js — capture, dedupe within session, expires_at enforcement, focus line shape, integration with SessionStart / UserPromptSubmit.

247 pass, 1 skipped, 0 fail.

Why this approach fits

The plugin already had the primitives — conclusion typing, 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 the KIMI_MEMORY_AUTO_EXTRACT / KIMI_MEMORY_SECRET_SCAN pattern.

The persist refactor is a precondition for several of these — schema v9 needs ALTER TABLE memories ADD COLUMN stability_days to live alongside the other migrations, and the previous five-file split made it awkward to keep the table openers in sync.

Checklist

  • Tests added/updated — three new test files, all green
  • package.json version bumped (0.4.0 → 0.5.0)
  • kimi.plugin.json version + longDescription updated
  • README documents new env vars, new tool, and the brain-mode section
  • skills/kimi-memory/SKILL.md updated
  • No secrets, no .env touched
  • No new top-level dependencies
  • CI green (watching after opening)

…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.
@cbuntingde
cbuntingde merged commit 5ce1881 into main Aug 5, 2026
2 checks passed
@cbuntingde
cbuntingde deleted the feat-v9-brain-modes branch August 5, 2026 10:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant