Skip to content

feat: native cross-session memory with /memory and /remember - #138

Closed
FradSer wants to merge 3 commits into
MoonshotAI:mainfrom
FradSer:feat/native-cross-session-memory
Closed

feat: native cross-session memory with /memory and /remember#138
FradSer wants to merge 3 commits into
MoonshotAI:mainfrom
FradSer:feat/native-cross-session-memory

Conversation

@FradSer

@FradSer FradSer commented May 28, 2026

Copy link
Copy Markdown

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。
  • 两个 scope:
    • ~/.kimi-code/memory/<slug>.md — 用户级(跨项目偏好)
    • <git-root>/.kimi-code/memory/<slug>.md — 项目级(仓库专属事实)
  • 同名 slug 时 project 覆盖 user(与 AGENTS.md / Skills 的 scope 优先级一致)。
  • MEMORY.md 是保留文件名 + render-only:每次渲染 system prompt 时由 loadMemory 从 per-fact 文件即时生成,不落盘,避免双写不一致。
  • Body 上限 4 KB;索引渲染上限 8 KB(超出按 reverse-alpha 截断 User 然后 Project,留 <!-- truncated: N entries omitted --> 哨兵)。
  • 原子写:kaos.writeText 写 tmp → node:fs/promises.rename。崩溃中断不会留下半写状态。

新增 agent 工具:memory

discriminator 字段 operation,与现有 TodoListTool 同形:

view                                          # 返回 system-prompt 中已注入的同一份索引
list  scope?  type?                           # 完整列表(即使索引被截断也能拿全)
read  scope=...  name=...                     # 完整 body + frontmatter
write scope=...  record={name,description,type}  body=...
update scope=...  name=...  record?  body?    # 部分 frontmatter 合并 + body 替换
delete scope=...  name=...

错误类型为 MemoryStoreErrorreason 枚举: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 之间新增 # Memory section,由 {% if KIMI_MEMORY %} 守卫,空时整段省略。Subagent 通过现有的 prepareSystemPromptContext 重渲染机制自动继承索引——无需新增任何 hook。/compact 不影响 memory(memory 在 system prompt 而非 history)。

Plan-mode policy

PlanModeGuardDenyPermissionPolicy 新增分支:plan 模式下 memory 工具的 write/update/delete 返回 kind: 'deny',提示用户 ExitPlanModeview/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 完全镜像 handleInitCommanddeferUserMessages → beginSessionRequest → session.remember → track → finalizeTurn 流程。

用法概览(给评审者参考)

# 让 agent 自己记
/remember 这个仓库用 pnpm,不要 npm 或 yarn
/remember 我喜欢简短的回答、不要 emoji

# 也可以自然对话——agent 看到自己有 Memory 工具,会主动判断要不要写
> 以后调试 React 组件时,先 console.log 再加断点
# 浏览 / 删除
/memory

下次启动 session 时,索引会以这样的形式注入到系统提示(人类可读、可 grep、可版本控制):

## Project (<project>/.kimi-code/memory)
- [project-build-cmd](project-build-cmd.md) (project) — Use pnpm not npm.

## User (~/.kimi-code/memory)
- [no-emoji](no-emoji.md) (feedback) — Never use emojis in output.

需要 body 时 agent 自己调用 Memory.read scope=... name=... 按需加载。

Session API / RPC / SDK

Session.listMemory / deleteMemory / remember 三个方法 + 对应 RPC payload + node-sdk 包装。Wire payload MemoryFactSummary(含 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 渲染。

推荐实践

  • gitignore<project>/.kimi-code/memory/ 默认会进 git。如果想让项目记忆保持个人化,把它加到 project .gitignore。User scope 在 home 下,天然 per-machine。
  • scope 选择:跟随用户走(语言偏好、工具偏好)→ user;跟项目走(构建命令、架构约定)→ project
  • 精化优先用 update:避免出现 coding-style + coding-style-v2 这种近重复。
  • 不要存秘密:tool 会做模式扫描并 warn,但不阻止;密钥 / token / 私钥这类绝对不要写入。
  • Subagent 写入对父 agent 在下一轮才可见(system prompt 每 turn 重渲染)—— 这是设计,不是 bug。

v1 显式不做的事

  • 向量 / 语义搜索(待 per-project memory 数量明显超过 ~100 再考虑)
  • SessionEnd 自动写入(hook 留着,v1 保持可预测)
  • 静态加密(依赖 OS 文件权限,未来再加固)
  • 跨机器同步(用 git 或用户自己的同步工具)
  • LLM 驱动的对话摘要入库(推迟)

Checklist

  • I have read the CONTRIBUTING document.
  • I have linked a related issue, or explained the problem above.
  • I have added tests that prove my feature works.
  • Ran gen-changesets skill, or this PR needs no changeset. (.changeset/add-native-cross-session-memory.md, minor bump 覆盖 @moonshot-ai/agent-core / @moonshot-ai/kimi-code-sdk / @moonshot-ai/kimi-code)
  • Ran gen-docs skill, 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 test4780 passed | 25 skipped | 2 todo(365 个测试文件)
  • pnpm lint → 0 errors(265 个 warning 全部为本 PR 之外的既存 warning)
  • pnpm build → exit 0
  • 已基于 upstream/main 当前 HEAD(16e881e 之后)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 共享 helper
  • packages/agent-core/src/tools/builtin/state/memory.{ts,md}MemoryTool + agent 面向的描述
  • packages/agent-core/src/profile/{types,context,resolve}.ts + default/system.mdKIMI_MEMORY 注入
  • packages/agent-core/src/agent/permission/policies/plan-mode-guard-deny.ts — plan 模式拦截
  • packages/agent-core/src/session/index.ts + rpc.tslistMemory / deleteMemory / remember
  • packages/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}.tsMemoryBrowserApp 状态机
  • apps/kimi-code/src/tui/commands/registry.ts + kimi-tui.ts/memory/remember 注册与 dispatch
  • docs/reference/memory.md — 人类向参考文档
  • .changeset/add-native-cross-session-memory.mdminor bump

FradSer added 3 commits May 28, 2026 12:58
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-bot

changeset-bot Bot commented May 28, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 04871ff

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 4 packages
Name Type
@moonshot-ai/agent-core Minor
@moonshot-ai/kimi-code-sdk Minor
@moonshot-ai/kimi-code Minor
@moonshot-ai/migration-legacy Patch

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

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Comment on lines +248 to +249
await this.kaos.writeText(tmpPath, content);
await rename(tmpPath, finalPath);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge 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 👍 / 👎.

Comment on lines +176 to +181
const projectRoot = join(
await findProjectRoot(this.kaos, this.workspace.workspaceDir),
'.kimi-code',
'memory',
);
return new FileMemoryStore(this.kaos, userRoot, projectRoot);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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 👍 / 👎.

Comment on lines +46 to +47
export function renderMemoryFile(record: MemoryRecord, body: string): string {
return `---\nname: ${record.name}\ndescription: ${record.description}\ntype: ${record.type}\n---\n\n${body.trim()}\n`;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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 %}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge 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 👍 / 👎.

Comment on lines +69 to +70
try {
text = await this.kaos.readText(path);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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 👍 / 👎.

Comment on lines 374 to +375
new b.TodoListTool(this.toolStore),
new b.MemoryTool(kaos, workspace, this.agent.telemetry),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge 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 👍 / 👎.

Comment on lines +444 to +446
return this.props.facts.find(
(f) => f.scope === this.props.selectedScope && f.slug === this.props.selectedSlug,
);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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 👍 / 👎.

@liruifengv

Copy link
Copy Markdown
Collaborator

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.

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.

什么时候支持Memory

2 participants