feat: native cross-session memory with /memory and /remember - #138
feat: native cross-session memory with /memory and /remember#138FradSer wants to merge 3 commits into
Conversation
Add design folder for native cross-session memory support with file-backed
Markdown storage, layered user/project scopes, a Memory builtin tool, and
a /memory slash command for user-side curation.
Contents:
- _index.md Context, glossary, requirements (FR/NFR), rationale
- architecture.md Component map, data structures, tool spec, atomic
writes, system-prompt integration, edge cases
- bdd-specs.md Gherkin scenarios for storage layering, tool ops,
/memory and /remember, injection, security
- best-practices.md Security, performance, code quality, testing, pitfalls
- evaluation-design-round-1.md Evaluator verdict: PASS (5/5)
Also seeds docs/retros/checklists/design-v1.md for future design rounds.
Decomposes the design at docs/plans/2026-05-27-native-memory-design/ into 21 executable tasks: 1 setup + 9 BDD-paired feature pairs (test+impl) + 1 config/changeset. Covers all 45 BDD scenarios across 9 features: - Storage with layered scopes (8) - Agent writes via the Memory tool (6) - Agent reads via the Memory tool (6) - Agent updates and deletes via the Memory tool (5) - System-prompt injection (4) - Survives /compact and session restart (3) - Security and path safety (5) - /memory slash command TUI curation (6) - Telemetry (2) Plan reflection sub-agents (BDD coverage, dependency graph, task completeness) PASS after fixing: missing dep 009-impl -> 002-impl; excess dep 010-impl -> 004-impl; embedded Gherkin in 9 impl files. Also seeds docs/retros/checklists/plan-v1.md for future plan rounds.
Add a file-backed Markdown memory subsystem to kimi-code. Per-fact .md
bodies live under `~/.kimi-code/memory/` (user scope) and
`<project-root>/.kimi-code/memory/` (project scope). A rendered
MEMORY.md-style index is injected into the system prompt under a new
`# Memory` section (capped at 8 KB); per-fact body content is capped at
4 KB.
New surfaces:
- **Memory builtin tool** (`packages/agent-core/src/tools/builtin/state/memory.ts`)
with operation discriminator `view | list | read | write | update |
delete`. Atomic tmp-rename writes; slug regex `^[a-z0-9](?:[a-z0-9-]{0,62}[a-z0-9])?$`;
symlink refusal; secret-pattern warn-only scan.
- **`/memory` slash command** — full-screen TUI browser for curation
(list grouped by scope, body preview, explicit-confirm delete, scope
filter). Shadowed user-scope entries annotated when overridden by
project scope.
- **`/remember <text>` slash command** — routes the text through the
agent (subagent spawn mirrors `Session.generateAgentsMd`) so frontmatter
fields (slug / description / type / scope) are LLM-derived.
- **System-prompt injection** via new `KIMI_MEMORY` template var, sitting
between `# Project Information` and `# Skills`. Re-rendered each turn,
surviving `/compact` and visible to subagents on next spawn.
- **Plan-mode policy** extension blocks write/update/delete; read ops pass.
- **Telemetry events** `memory_write / memory_update / memory_delete` (no
body in payload) and `memory_index_truncated` (drop count).
- **Session API + RPC + SDK**: `listMemory`, `deleteMemory`, `remember`.
Storage shape:
- Per-fact body files only on disk. `MEMORY.md` filename is reserved
(skipped by the loader) and the index is rendered on demand — eliminates
the dual-write hazard between index and bodies.
- Project entries override User entries on slug collision in the rendered
index; the user-scope file remains on disk and addressable.
Tests: 4532 passing across 354 files (full repo). New tests cover the 45
BDD scenarios in the design folder plus telemetry / resilience /
plan-mode / security cases. Existing AGENTS.md and Skill loading verified
unchanged.
Plan execution artifacts live under `docs/plans/2026-05-27-native-memory-plan/`
(sprint contracts, handoff summaries, per-batch evaluations) for audit.
The design folder `docs/plans/2026-05-27-native-memory-design/` was
committed earlier.
Out of scope (deferred): vector / semantic search; auto-write on
SessionEnd; encryption at rest; cross-machine sync; LLM-driven session
summarization.
🦋 Changeset detectedLatest commit: 04871ff The changes in this PR will be included in the next version bump. This PR includes changesets to release 4 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 04871ff617
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| await this.kaos.writeText(tmpPath, content); | ||
| await rename(tmpPath, finalPath); |
There was a problem hiding this comment.
Keep memory writes inside the Kaos filesystem
In sessions backed by a non-local Kaos (the repo has SSHKaos/container-style environments), this.kaos.writeText(tmpPath, ...) writes the temp file in that environment, but rename(tmpPath, finalPath) is Node's local filesystem rename. For SSH paths this will fail or operate on an unrelated local path, so Memory.write/update cannot persist memory even though the rest of the agent file tools work through Kaos. Use a Kaos-level operation or another remote-safe write strategy instead of mixing remote writes with local fs calls.
Useful? React with 👍 / 👎.
| const projectRoot = join( | ||
| await findProjectRoot(this.kaos, this.workspace.workspaceDir), | ||
| '.kimi-code', | ||
| 'memory', | ||
| ); | ||
| return new FileMemoryStore(this.kaos, userRoot, projectRoot); |
There was a problem hiding this comment.
Align project-scope writes with prompt loading
When the workspace is not inside a Git repo, findProjectRoot returns the current directory, so this still creates a project memory store and lets Memory.write or /remember save project facts under <cwd>/.kimi-code/memory. However loadMemory only injects project memories when hasGitDir(projectRoot) is true, so those successfully written facts are listed/deleted by the UI but never appear in future system prompts. Either reject/redirect project-scope writes outside Git repos or have loading use the same root policy.
Useful? React with 👍 / 👎.
| export function renderMemoryFile(record: MemoryRecord, body: string): string { | ||
| return `---\nname: ${record.name}\ndescription: ${record.description}\ntype: ${record.type}\n---\n\n${body.trim()}\n`; |
There was a problem hiding this comment.
Escape descriptions before writing frontmatter
A memory record description is accepted as any non-empty string up to 240 chars, but this line interpolates it directly into YAML frontmatter. If the model/user supplies a newline, ---, or other YAML-significant text in record.description, the write succeeds while the stored file can later fail parseMemoryFile or parse different metadata, making the fact disappear from list/view/prompt injection. Enforce the documented single-line invariant and/or serialize the frontmatter with proper YAML quoting.
Useful? React with 👍 / 👎.
| When working on files in subdirectories, always check whether those directories contain their own `AGENTS.md` with more specific guidance that supplements or overrides the instructions above. You may also check `README`/`README.md` files for more information about the project. | ||
|
|
||
| If you modified any files/styles/structures/configurations/workflows/... mentioned in `AGENTS.md` files, you MUST update the corresponding `AGENTS.md` files to keep them up-to-date. | ||
| {% if KIMI_MEMORY %} |
There was a problem hiding this comment.
Forward memory into actual agent prompts
This new template section depends on KIMI_MEMORY, but the real Agent.useProfile() path does not pass context.memoryIndex into profile.systemPrompt(...) (it only forwards cwd/listing/AGENTS.md/skills). As a result, sessions created or resumed through Session.bootstrapAgentProfile load memory from disk but render this variable empty, so persisted facts never reach the model despite the feature appearing to work in the direct renderer tests.
Useful? React with 👍 / 👎.
| try { | ||
| text = await this.kaos.readText(path); |
There was a problem hiding this comment.
Refuse symlinks when listing memory
The read operation explicitly lstat-checks and rejects symlinks, but listMemory//memory reaches this code path and reads every *.md entry without the same check; loadMemory has the same pattern for prompt injection. In a repository containing .kimi-code/memory/foo.md as a symlink to another readable file with valid memory frontmatter, the browser/RPC can expose that target's body and the loader can inject it as an in-scope project memory, bypassing the symlink protection added for read.
Useful? React with 👍 / 👎.
| new b.TodoListTool(this.toolStore), | ||
| new b.MemoryTool(kaos, workspace, this.agent.telemetry), |
There was a problem hiding this comment.
Enable Memory in the active profiles
Registering the builtin here does not make it callable: ToolManager.setActiveTools() only exposes tools named by the active profile, and the checked default agent and coder profiles do not include memory. The new system prompt and /remember flow both tell agents to call the Memory tool, but normal sessions and the coder subagent spawned by /remember will not receive that tool at all, so memory cannot be written or curated until the profiles include it.
Useful? React with 👍 / 👎.
| return this.props.facts.find( | ||
| (f) => f.scope === this.props.selectedScope && f.slug === this.props.selectedSlug, | ||
| ); |
There was a problem hiding this comment.
Keep selection inside the active memory filter
After the user cycles the browser filter, the current selection is not reconciled with the filtered rows, and selectedFact() still searches all facts. For example, selecting a user fact and switching to the project filter leaves the detail pane and D delete action targeting that hidden user fact while the list shows only project rows. Recompute/clear the selection against visibleFacts whenever the scope filter changes so detail and destructive actions cannot apply to an item outside the current view.
Useful? React with 👍 / 👎.
|
Thank you for your interest in contributing to Kimi Code. For new features, please create an issue or discuss it in an existing issue. We are not currently accepting pull requests for new features. |
Related Issue
Resolve #21
Problem
kimi-code 目前没有跨会话的语义记忆能力。Agent 在每个 session 内能看到
AGENTS.md(静态、人类编辑)和 Skills(静态指令),会话级历史以 JSONL 形式持久化但下个 session 不会再读取。这意味着用户每次启动新会话都需要把"我喜欢 pnpm 不用 npm""这个项目用 Biome 2 空格缩进"之类的偏好重新告诉 agent。社区 issue #21 ("什么时候支持Memory") 自 2024 起就在等待这能力。
What changed
新增一个文件式的 Markdown 记忆子系统,参考了 Anthropic Memory Tool (
memory_20250818) 的客户端文件原语 + Claude Code 的MEMORY.md索引指针模式,并完全本地化、零额外二进制依赖。存储模型
.md文件,YAML frontmatter (name/description/type: user|feedback|project|reference) + Markdown body。~/.kimi-code/memory/<slug>.md— 用户级(跨项目偏好)<git-root>/.kimi-code/memory/<slug>.md— 项目级(仓库专属事实)MEMORY.md是保留文件名 + render-only:每次渲染 system prompt 时由loadMemory从 per-fact 文件即时生成,不落盘,避免双写不一致。<!-- truncated: N entries omitted -->哨兵)。kaos.writeText写 tmp →node:fs/promises.rename。崩溃中断不会留下半写状态。新增 agent 工具:
memorydiscriminator 字段
operation,与现有TodoListTool同形:错误类型为
MemoryStoreError,reason枚举:EXISTS / NOT_FOUND / BODY_TOO_LARGE / INVALID_SLUG / PATH_OUTSIDE_SCOPE / SYMLINK_REFUSED。slug 正则
^[a-z0-9](?:[a-z0-9-]{0,62}[a-z0-9])?$,在 zod schema 层拦截;symlink 通过kaos.stat({ followSymlinks: false })直接拒绝;secret 模式(sk-…、gh[pousr]_…、AKIA…、PEM 私钥头、xox[baprs]-…)只 warn 不阻断,wire log 只记录类别名不记原文。系统提示注入
packages/agent-core/src/profile/default/system.md在# Project Information和# Skills之间新增# Memorysection,由{% if KIMI_MEMORY %}守卫,空时整段省略。Subagent 通过现有的prepareSystemPromptContext重渲染机制自动继承索引——无需新增任何 hook。/compact不影响 memory(memory 在 system prompt 而非 history)。Plan-mode policy
PlanModeGuardDenyPermissionPolicy新增分支:plan 模式下memory工具的write/update/delete返回kind: 'deny',提示用户ExitPlanMode;view/list/read直通。新增两个 slash command
/memory—— 全屏 TUI 浏览器(list / 详情 / 删除确认 / scope 过滤)。键位:↑/↓导航、Enter展开/折叠 body 预览、d删除(二次确认)、s切换 scope 过滤(all/user/project)、Esc/q退出。User-scope 被同名 project 覆盖时标记为 "shadowed by project"。只读浏览 + 删除,不能在 UI 里新增——写入统一走 agent 路径,保证 frontmatter 由 LLM 生成、风格一致。/remember <text>—— 把文本通过 subagent(仿Session.generateAgentsMd的 spawn 模式)让 agent 自己挑name/description/type/scope,再调用memory工具operation: 'write'写入。前端 TUI 永远不直接动 memory 文件。handleRememberCommand完全镜像handleInitCommand的deferUserMessages → beginSessionRequest → session.remember → track → finalizeTurn流程。用法概览(给评审者参考)
# 浏览 / 删除 /memory下次启动 session 时,索引会以这样的形式注入到系统提示(人类可读、可 grep、可版本控制):
需要 body 时 agent 自己调用
Memory.read scope=... name=...按需加载。Session API / RPC / SDK
Session.listMemory/deleteMemory/remember三个方法 + 对应 RPC payload + node-sdk 包装。Wire payloadMemoryFactSummary(含 body,受 4 KB cap 约束,避免 TUI 预览需要二次 RPC)与领域类型MemoryEntry分离。shadowed标志由SessionAPIImpl.listMemory服务端计算并随载荷返回。Telemetry
Agent.telemetry: TelemetryClient通道发射memory_write/memory_update/memory_delete(payload{ scope, slug },绝不含 body)以及memory_index_truncated(payload{ droppedCount })。所有track()调用 try/catch 包裹,fire-and-forget——遥测后端故障不会影响工具结果或 prompt 渲染。推荐实践
<project>/.kimi-code/memory/默认会进 git。如果想让项目记忆保持个人化,把它加到 project.gitignore。User scope 在 home 下,天然 per-machine。user;跟项目走(构建命令、架构约定)→project。update:避免出现coding-style+coding-style-v2这种近重复。v1 显式不做的事
Checklist
gen-changesetsskill, or this PR needs no changeset. (.changeset/add-native-cross-session-memory.md,minorbump 覆盖@moonshot-ai/agent-core/@moonshot-ai/kimi-code-sdk/@moonshot-ai/kimi-code)gen-docsskill, or this PR needs no doc update. (docs/reference/memory.md新增;packages/agent-core/src/tools/builtin/state/memory.md是 agent 看到的工具描述)验证状态
pnpm typecheck→ exit 0(7 个包全绿)pnpm test→ 4780 passed | 25 skipped | 2 todo(365 个测试文件)pnpm lint→ 0 errors(265 个 warning 全部为本 PR 之外的既存 warning)pnpm build→ exit 016e881e之后)rebase,permission policy 重构(feat: rework permission decision policies #26)已适配到PlanModeGuardDenyPermissionPolicy的新{ kind: 'deny', message }返回形式。主要新增 / 修改
packages/agent-core/src/memory/*— 类型、slug 校验、format、store、loader、find-project-root 共享 helperpackages/agent-core/src/tools/builtin/state/memory.{ts,md}—MemoryTool+ agent 面向的描述packages/agent-core/src/profile/{types,context,resolve}.ts+default/system.md—KIMI_MEMORY注入packages/agent-core/src/agent/permission/policies/plan-mode-guard-deny.ts— plan 模式拦截packages/agent-core/src/session/index.ts+rpc.ts—listMemory/deleteMemory/rememberpackages/agent-core/src/rpc/{core-api,core-impl}.ts— RPC 入口packages/node-sdk/src/{rpc,session,types}.ts— SDK 包装apps/kimi-code/src/tui/memory/{browser,state}.ts—MemoryBrowserApp状态机apps/kimi-code/src/tui/commands/registry.ts+kimi-tui.ts—/memory和/remember注册与 dispatchdocs/reference/memory.md— 人类向参考文档.changeset/add-native-cross-session-memory.md—minorbump