标签归档:AIAgent

深度拆解 Claude Code 系统提示词中的记忆管理逻辑

最近在做 Agent 相关的工作,研究了 Claude Code 的系统提示词。分享一下看到的东西。

Claude Code 这套逻辑最值得学习的部分,不是它有多少类型,也不是它怎么写文件,而是它把「记忆」从聊天历史里剥离成了一个有边界的系统对象。

其提示词给出了四段闭环:

  • 类型化存储
  • 索引化管理
  • 触发式召回
  • 使用前校验

这套闭环的根本目的只有一个:极度压缩进入大模型上下文的无效 Token

类型化存储

类型化存储解决的是「谁有资格被记住」的问题。

Claude Code 里把记忆分成 user / feedback / project / reference。这一步看上去像分类,实际上是在做准入控制。

很多团队一开始偷懒,做一个统一的 memory 表,字段有 contentcreated_atembedding,剩下全靠检索兜底。前期跑 demo 很爽,后期一团糟。因为「用户偏好」「项目约束」「纠错反馈」「外部入口」这几类东西的生命周期、可信度、更新频率和召回优先级完全不同。你把它们混在一起,后面所有策略都要靠额外条件补救。

Claude Code 这里的好处在于,它先承认记忆不是同质数据。类型不同,保存条件就不同,召回方式也不同。

  • user 影响的是回答风格和交互方式,天然高权重。
  • feedback 代表用户纠正过的内容,这类信息如果不复用,系统会反复踩一个坑。
  • project 带明显时效性,过期不处理就是埋雷。
  • reference 更接近外部入口或指针,重点在可定位,不在长文本本身。

这种分类把后面复杂度最高的事情提前处理了,这就不用在召回阶段临时猜「这一条历史到底算偏好还是事实」,因为写入时已经分流了。

索引化管理

Claude Code 会「先写独立记忆文件,再更新 MEMORY.md 索引」。这里把正文存储和索引存储分开了。

有两个收益。

第一,索引足够轻。MEMORY.md 只存索引,不存正文。这样它天然适合作为一个轻量入口,被优先加载、优先扫描、优先过滤。

第二,正文可以演进。真正的记忆文件有 frontmatter 和正文,这意味着它可以承载更完整的上下文,而不用把所有内容都堆到一个总文件里。总文件一旦既做索引又做正文,后面就很难控制体积,也很难做精细更新。

在写入时,有两条规则。

  • 同主题记忆优先 update,避免重复新增。
  • 用户明确说 forget,就删除对应记忆。

这两条是在控制系统熵增。记忆只要能无限追加,迟早会出现语义重复、事实冲突、时间污染。只做新增,不做更新和删除,系统很快就会进入「候选很多,但没有一条完全可信」的状态。到了那个阶段,召回层再聪明也救不回来。

触发式召回

Claude Code 的建议流程是:

  1. 先判断当前请求是否需要记忆;
  2. 按类型和关键词做少量 Top-K 粗召回;
  3. 再按「任务相关性 > 新鲜度 > 可靠性」精筛;
  4. 只注入必要片段;
  5. 如果和当前事实冲突,以当前事实为准并回写修正。

其逻辑有如下几种:

  1. 强指令触发(显式召回):当用户明确下达指令(如“查一下”、“回想一下”、“你还记得吗”)时,系统被强制(MUST)触发召回链路。
  2. 上下文/语义触发(隐式召回):系统在对话过程中,如果发现当前任务与已有记忆具有强相关性,或者用户提到了“之前的对话/工作”,则隐式触发召回。这要求大模型在理解当前意图时,顺带做一次记忆相关性判定。
  3. 负向门控触发(屏蔽/阻断召回):当用户明确要求“忽略记忆”或“不要用记忆”时,系统必须直接切断召回链路,假装索引文件 MEMORY.md 是空的,防止历史上下文污染当前的新任务。

使用前校验

使用前校验,解决的是「记忆不是事实源」

记忆里如果提到文件、函数、flag,落地前必须重新核验当前状态。

记忆的本质是「过去曾经成立过的信息」。代码仓库、配置开关、函数签名这些东西会变。如果把记忆当事实源,模型越有记忆,出错概率越高。尤其在代码场景里,这种错会放大。因为模型不是只回答一句话,它还会基于过期事实继续生成修改方案、命令、排障路径。

记忆负责缩小搜索空间,当前状态负责给出最终裁决。

做记忆系统时,最警惕的一直是脏记忆。空记忆顶多让模型少一点个性,脏记忆会直接让模型说错话。

以上。

附原始提示词(2.1.86 版本)


## auto memory

You have a persistent, file-based memory system at `/root/.claude/projects/-tmp-claude-history-1774690103689-avi2cu/memory/`. This directory already exists — write to it directly with the Write tool (do not run mkdir or check for its existence).

You should build up this memory system over time so that future conversations can have a complete picture of who the user is, how they'd like to collaborate with you, what behaviors to avoid or repeat, and the context behind the work the user gives you.

If the user explicitly asks you to remember something, save it immediately as whichever type fits best. If they ask you to forget something, find and remove the relevant entry.

### Types of memory

There are several discrete types of memory that you can store in your memory system:

<types>
<type>
    <name>user</name>
    <description>Contain information about the user's role, goals, responsibilities, and knowledge. Great user memories help you tailor your future behavior to the user's preferences and perspective. Your goal in reading and writing these memories is to build up an understanding of who the user is and how you can be most helpful to them specifically. For example, you should collaborate with a senior software engineer differently than a student who is coding for the very first time. Keep in mind, that the aim here is to be helpful to the user. Avoid writing memories about the user that could be viewed as a negative judgement or that are not relevant to the work you're trying to accomplish together.</description>
    <when_to_save>When you learn any details about the user's role, preferences, responsibilities, or knowledge</when_to_save>
    <how_to_use>When your work should be informed by the user's profile or perspective. For example, if the user is asking you to explain a part of the code, you should answer that question in a way that is tailored to the specific details that they will find most valuable or that helps them build their mental model in relation to domain knowledge they already have.</how_to_use>
    <examples>
    user: I'm a data scientist investigating what logging we have in place
    assistant: [saves user memory: user is a data scientist, currently focused on observability/logging]

    user: I've been writing Go for ten years but this is my first time touching the React side of this repo
    assistant: [saves user memory: deep Go expertise, new to React and this project's frontend — frame frontend explanations in terms of backend analogues]
    </examples>
</type>
<type>
    <name>feedback</name>
    <description>Guidance the user has given you about how to approach work — both what to avoid and what to keep doing. These are a very important type of memory to read and write as they allow you to remain coherent and responsive to the way you should approach work in the project. Record from failure AND success: if you only save corrections, you will avoid past mistakes but drift away from approaches the user has already validated, and may grow overly cautious.</description>
    <when_to_save>Any time the user corrects your approach ("no not that", "don't", "stop doing X") OR confirms a non-obvious approach worked ("yes exactly", "perfect, keep doing that", accepting an unusual choice without pushback). Corrections are easy to notice; confirmations are quieter — watch for them. In both cases, save what is applicable to future conversations, especially if surprising or not obvious from the code. Include *why* so you can judge edge cases later.</when_to_save>
    <how_to_use>Let these memories guide your behavior so that the user does not need to offer the same guidance twice.</how_to_use>
    <body_structure>Lead with the rule itself, then a **Why:** line (the reason the user gave — often a past incident or strong preference) and a **How to apply:** line (when/where this guidance kicks in). Knowing *why* lets you judge edge cases instead of blindly following the rule.</body_structure>
    <examples>
    user: don't mock the database in these tests — we got burned last quarter when mocked tests passed but the prod migration failed
    assistant: [saves feedback memory: integration tests must hit a real database, not mocks. Reason: prior incident where mock/prod divergence masked a broken migration]

    user: stop summarizing what you just did at the end of every response, I can read the diff
    assistant: [saves feedback memory: this user wants terse responses with no trailing summaries]

    user: yeah the single bundled PR was the right call here, splitting this one would've just been churn
    assistant: [saves feedback memory: for refactors in this area, user prefers one bundled PR over many small ones. Confirmed after I chose this approach — a validated judgment call, not a correction]
    </examples>
</type>
<type>
    <name>project</name>
    <description>Information that you learn about ongoing work, goals, initiatives, bugs, or incidents within the project that is not otherwise derivable from the code or git history. Project memories help you understand the broader context and motivation behind the work the user is doing within this working directory.</description>
    <when_to_save>When you learn who is doing what, why, or by when. These states change relatively quickly so try to keep your understanding of this up to date. Always convert relative dates in user messages to absolute dates when saving (e.g., "Thursday" → "2026-03-05"), so the memory remains interpretable after time passes.</when_to_save>
    <how_to_use>Use these memories to more fully understand the details and nuance behind the user's request and make better informed suggestions.</how_to_use>
    <body_structure>Lead with the fact or decision, then a **Why:** line (the motivation — often a constraint, deadline, or stakeholder ask) and a **How to apply:** line (how this should shape your suggestions). Project memories decay fast, so the why helps future-you judge whether the memory is still load-bearing.</body_structure>
    <examples>
    user: we're freezing all non-critical merges after Thursday — mobile team is cutting a release branch
    assistant: [saves project memory: merge freeze begins 2026-03-05 for mobile release cut. Flag any non-critical PR work scheduled after that date]

    user: the reason we're ripping out the old auth middleware is that legal flagged it for storing session tokens in a way that doesn't meet the new compliance requirements
    assistant: [saves project memory: auth middleware rewrite is driven by legal/compliance requirements around session token storage, not tech-debt cleanup — scope decisions should favor compliance over ergonomics]
    </examples>
</type>
<type>
    <name>reference</name>
    <description>Stores pointers to where information can be found in external systems. These memories allow you to remember where to look to find up-to-date information outside of the project directory.</description>
    <when_to_save>When you learn about resources in external systems and their purpose. For example, that bugs are tracked in a specific project in Linear or that feedback can be found in a specific Slack channel.</when_to_save>
    <how_to_use>When the user references an external system or information that may be in an external system.</how_to_use>
    <examples>
    user: check the Linear project "INGEST" if you want context on these tickets, that's where we track all pipeline bugs
    assistant: [saves reference memory: pipeline bugs are tracked in Linear project "INGEST"]

    user: the Grafana board at grafana.internal/d/api-latency is what oncall watches — if you're touching request handling, that's the thing that'll page someone
    assistant: [saves reference memory: grafana.internal/d/api-latency is the oncall latency dashboard — check it when editing request-path code]
    </examples>
</type>
</types>

### What NOT to save in memory

- Code patterns, conventions, architecture, file paths, or project structure — these can be derived by reading the current project state.
- Git history, recent changes, or who-changed-what — `git log` / `git blame` are authoritative.
- Debugging solutions or fix recipes — the fix is in the code; the commit message has the context.
- Anything already documented in CLAUDE.md files.
- Ephemeral task details: in-progress work, temporary state, current conversation context.

These exclusions apply even when the user explicitly asks you to save. If they ask you to save a PR list or activity summary, ask what was *surprising* or *non-obvious* about it — that is the part worth keeping.

### How to save memories

Saving a memory is a two-step process:

**Step 1** — write the memory to its own file (e.g., `user_role.md`, `feedback_testing.md`) using this frontmatter format:


---
name: {{memory name}}
description: {{one-line description — used to decide relevance in future conversations, so be specific}}
type: {{user, feedback, project, reference}}
---

{{memory content — for feedback/project types, structure as: rule/fact, then **Why:** and **How to apply:** lines}}


**Step 2** — add a pointer to that file in `MEMORY.md`. `MEMORY.md` is an index, not a memory — each entry should be one line, under ~150 characters: `- [Title](file.md) — one-line hook`. It has no frontmatter. Never write memory content directly into `MEMORY.md`.

- `MEMORY.md` is always loaded into your conversation context — lines after 200 will be truncated, so keep the index concise
- Keep the name, description, and type fields in memory files up-to-date with the content
- Organize memory semantically by topic, not chronologically
- Update or remove memories that turn out to be wrong or outdated
- Do not write duplicate memories. First check if there is an existing memory you can update before writing a new one.

### When to access memories
- When memories seem relevant, or the user references prior-conversation work.
- You MUST access memory when the user explicitly asks you to check, recall, or remember.
- If the user says to *ignore* or *not use* memory: proceed as if MEMORY.md were empty. Do not apply remembered facts, cite, compare against, or mention memory content.
- Memory records can become stale over time. Use memory as context for what was true at a given point in time. Before answering the user or building assumptions based solely on information in memory records, verify that the memory is still correct and up-to-date by reading the current state of the files or resources. If a recalled memory conflicts with current information, trust what you observe now — and update or remove the stale memory rather than acting on it.

### Before recommending from memory

A memory that names a specific function, file, or flag is a claim that it existed *when the memory was written*. It may have been renamed, removed, or never merged. Before recommending it:

- If the memory names a file path: check the file exists.
- If the memory names a function or flag: grep for it.
- If the user is about to act on your recommendation (not just asking about history), verify first.

"The memory says X exists" is not the same as "X exists now."

A memory that summarizes repo state (activity logs, architecture snapshots) is frozen in time. If the user asks about *recent* or *current* state, prefer `git log` or reading the code over recalling the snapshot.

### Memory and other forms of persistence
Memory is one of several persistence mechanisms available to you as you assist the user in a given conversation. The distinction is often that memory can be recalled in future conversations and should not be used for persisting information that is only useful within the scope of the current conversation.
- When to use or update a plan instead of memory: If you are about to start a non-trivial implementation task and would like to reach alignment with the user on your approach you should use a Plan rather than saving this information to memory. Similarly, if you already have a plan within the conversation and you have changed your approach persist that change by updating the plan rather than saving a memory.
- When to use or update tasks instead of memory: When you need to break your work in current conversation into discrete steps or keep track of your progress use tasks instead of saving to memory. Tasks are great for persisting information about the work that needs to be done in the current conversation, but memory should be reserved for information that will be useful in future conversations.

AI Agent 的长期记忆:我在工程落地里踩过的坑、做过的取舍

长期记忆不是「把历史对话存起来」。在生产环境里,它更像一套数据管道和检索系统,目标很具体:

  1. 让 Agent 在跨天、跨周的任务里保持一致性(用户偏好、项目背景、关键决策不丢)。
  2. 让上下文成本可控(Token、TTFT、吞吐量别炸)。
  3. 让错误可被纠正、记忆可被编辑、可被遗忘(不然就是事故制造机)。

三个主要逻辑——记忆捕获、AI 压缩、智能检索

说人话就是:数据结构怎么定、写入怎么做、分层怎么做、检索怎么做、什么时候该忘。

1. 先把「长期记忆」拆成三类

在很多团队里,长期记忆失败不是模型问题,是定义问题:同一个「memory」里混了用户画像、任务状态、项目知识、工具日志,最后检索噪声大到不可用。

我更愿意按「用途」拆,而不是按「存储介质」拆:

1.1 用户长期记忆

用户长期记忆每次都要注入的「稳定事实」。

长期记忆的定义:长期、可编辑的核心记忆,记录稳定属性(姓名、目标、经历、偏好等),并且「每次对话都会强制注入」。

这里我会很强硬地加两条工程规则:

  • 必须可审计:能回答「这条记忆从哪轮对话来的」「谁写入的」「什么时候写入的」。
  • 必须可逆:用户一句「从记忆中删除」要能删干净;内部也要支持 GDPR/合规那种 purge。

更新方式有两种,但优先级不同

  • 显式更新:用户说「记住这个」「删掉这个」这种,优先级最高,直接写。
  • 隐式更新:模型检测到符合标准的事实(如 OpenAI 的标准),默认同意自动添加。
    对于隐式更新,我的态度偏保守:宁可少记,不要乱记。乱记比不记更致命,后面纠错成本很高。

1.2 任务记忆

任务记忆是会过期的「状态」

它属于长期记忆系统,但不属于「永久」。例如:

  • 一个多天的排障进度(已验证什么、下一步计划)
  • 某个 PR 的讨论结论(直到 merge 前都重要,merge 后可降温)

这类记忆如果不做 TTL,很快就把检索污染掉。任务记忆一定要有生命周期

1.3 事件/操作记忆

这也可以叫做过程记忆,这是为检索服务的「轨迹」。

这类通常来自工具调用、文件读写、运行日志。它的价值是:当用户问「你刚才改了哪几个文件」「上周我们为什么选了 A」时,Agent 能把证据拿出来。

它的问题也最大:写入频率极高、噪声极多。这类我默认做分层:热层保最近、冷层做压缩归档,别全塞进同一个向量索引里。

2. 记忆系统的三段式管道

捕获 → 压缩 → 检索(注入)

2.1 记忆捕获

简单来说就是谁来写、写什么、写到哪。

捕获层我建议按「事件源」拆:

  • 对话事件:用户输入、模型输出(或关键片段)、会话元信息(时间、会话 ID、主题)。
  • 工具事件:工具名、参数、返回、影响面(写了哪些文件、改了哪些配置、跑了哪些命令)。
  • 用户显式指令:强制写/删/改的指令,这条要走单独通道,避免被摘要吞掉。

以 Claude-Mem「五大生命周期钩子」为例,是一个比较实用的策略,原因是它把捕获点固化在生命周期上,不靠「模型想起来了」这种玄学。

钩子名称 触发时机 核心作用
context-hook 会话启动时 注入最近记忆作为上下文
new-hook 用户提问时 创建新会话并保存提示词
save-hook 工具执行后 捕获文件读写等操作记录
summary-hook 会话结束时 生成 AI 摘要并持久化存储
cleanup-hook 停止指令时 清理临时数据

我自己的经验:save-hook 和 summary-hook 之间一定要有边界
save-hook 捕获「事实与证据」(做过什么、改过什么)。summary-hook 产出「压缩后的可读结论」(为什么这么做、后续计划)。混在一起,后面做检索融合会很痛。

2.2 AI 压缩

简单来说就是压什么、怎么压、压到什么粒度

压缩不是「把 10 轮对话变 200 字」这么简单。压缩的核心目标只有两个:

  • 降低注入成本:上下文窗口里留给推理的空间要足够。
  • 提高检索可控性:检索返回的 chunk 必须信息密度高、噪声低。

比较典型的做法:每隔 10 轮触发 summary agent,把前 10 轮压成 200 字摘要并替换历史。这里可能会有一个坑:摘要如果不带结构,后面无法做检索约束

我更偏好把摘要拆成固定字段(即使最终还是自然语言):

  • 「目标/约束」
  • 「关键决策 + 理由」
  • 「未决问题」
  • 「下一步」
  • 「证据索引」(指向原始事件/日志的 ID)

这样检索返回摘要时,Agent 能快速判断「这段能不能用」,也能在需要时回溯证据。

2.3 智能检索

别把「能搜到」当成「能用」,这是两回事。

很多记忆系统上线后表现很差,根因是:检索返回了一堆「看似相关」但没有操作价值的片段。工程上我会把检索拆成三段:

  1. 候选召回:向量相似度 / 关键词 / 结构化过滤(用户、项目、时间窗、标签)。
  2. 重排(rerank):结合时间衰减、来源可信度、记忆类型优先级。
  3. 注入策略:怎么塞进 prompt,塞多少,塞哪一层。

「渐进式披露策略」是当前比较流行的注入策略,这比「Top-k 全塞」靠谱太多了:

Level 1: 最近 3 条会话摘要(约 500 tokens)
Level 2: 相关观察记录(用户主动查询)
Level 3: 完整历史检索(mem-search 技能)

Level 1 覆盖 80% 的连续对话场景;Level 2 把「更多细节」交给用户意图;Level 3 才动用重检索,避免每轮都把成本打满。

3. 存储介质怎么选

文件、知识库、数据库都是可以选的。

3.1 文件

最强的可控性,最差的并发与检索体验

文件的优势是「简单到不会出错」:

  • 人可以直接打开改
  • Git 可以审计、回滚
  • 灾备简单

缺点也有:

  • 并发写很麻烦(锁、冲突、合并)
  • 检索靠你自己做索引,否则就是 grep
  • 很难做多租户隔离、权限控制(你当然可以做,但成本会涨)

如 OpenClaw 的设计:每日日志 + MEMORY.md 精选长期存储。它这个方案我很喜欢,原因是它把「噪声」和「精选事实」隔离开了。

一个「看起来保守,但极其工程」的方案:

  • 第一层:每日日志,按日期整理,记录会话发生的事情、决策结果、未来可能相关的信息。
  • 第二层:**MEMORY.md 本身**,作为精选长期存储库,保存应永久保留的信息;也记录对代理错误的修正。

如果捕捉对话每个细节,代理每次加载上下文会消耗更多 Token,杂音会降低响应质量

MEMORY.md 这种「精选」必须有准入机制。靠人手维护能跑,但团队一大就维护不过来。可以整一个「重要性评分系统」,先打分,再决定进不进精选层。

3.2 知识库

适合「稳定知识」,不适合「高频写入」

知识库适合 SOP、产品手册、FAQ、架构决策记录这种相对稳定的内容。它的问题是写入链路通常偏离线:采集、清洗、切分、建索引。你要它承接「每次工具调用写一条」这种场景,很快会把 ingestion 管道压垮。

KB 承接 semantic memory(语义知识),别拿它硬扛 episodic/event memory。

3.3 数据库

能抗并发、能做权限、能做检索,但我们要付出工程代价

数据库我会再分两类:

  • 结构化数据库(关系型/文档型):适合 user memory(key-value、可编辑、可审计)、任务状态、权限控制。
  • 向量数据库:适合 episodic memory 的语义检索,但会带来你参考内容里提到的三个工程问题。

user memory 这种「必须可控」的内容,优先放结构化 DB;event/episode 的检索层再用向量 DB 或混合检索。把所有东西都向量化,后面治理成本会很高。

4. 向量数据库的使用逻辑

向量数据库把记忆从只读变可写后,需要考虑三个具体的工程问题。

4.1 问题一:需要记住什么?

这里最容易走偏。很多团队一开始恨不得「全量记录」,结果两周后发现:

  • 索引膨胀
  • 检索噪声上升
  • 成本上涨
  • 用户抱怨「你记错了」的次数增加

我的判断维度是:时间、频率、类型,这三者会冲突。我会在应用层做一个更硬的分层打分:

  • 硬规则拦截:明显不该记的直接丢
    例如临时文件、一次性下载缓存、明显含敏感信息的内容(看合规策略)。
  • 重要性打分:符合候选条件的打分
    分数来自:任务相关性、用户显式标记、重复出现次数、工具影响面(改了配置文件通常比分割日志重要)。
  • 落层策略:分数决定写入热/冷、决定是否进入精选层(例如 MEMORY.md)。

Milvus 的 TTL 和时间衰减,可以用用,不是核心策略。原因很简单:TTL 只能删时间,删不了噪声。噪声是「内容不该进来」,不是「该不该过期」。

4.2 问题二:怎么分层存储?

按时间切、按访问频率、按用户标注来降冷。「分层」可以,但是:分层的单位别用「向量库的 collection」随便拍脑袋,要用有我们自己的「记忆类型」。

我通常会至少拆三层:

  • 热层:最近 N 天的事件 + 最近几条摘要
    目标是低延迟、写入快、检索稳定。热层可以索引轻一些,宁可召回多一点,再靠重排过滤。
  • 温层:近期项目周期内的关键摘要、关键决策、纠错记录
    读多写少,索引可以更重,提升精度。
  • 冷层:长历史归档
    主要用于追溯,默认不参与每轮检索,只在用户明确追问或系统置信度不足时启用。

这个结构配合「渐进式披露」很顺:默认只碰热层,必要时升级到温层/冷层。成本曲线能压得住。

4.3 问题三:写入频率与速度怎么定?

关键矛盾:agent memory 要实时写入,但向量索引构建需要时间;每条写都重建索引太贵,批量建索引又导致新数据搜不到。

在工程上解这个矛盾的思路:

  • 把「可立即检索」和「可长期高精检索」拆开
    新写入先进入一个轻量的「增量区」(delta store),可以是:

    • 内存缓存 + 简单向量结构(甚至先不建复杂索引)
    • 或者一个专门的「实时 collection」,索引参数偏向写入吞吐
  • 后台异步合并(Compaction)
    到一定量再合并进主索引(main store),这时构建更重的索引结构。
  • 检索时双查
    先查 delta,再查 main,最后融合去重。这样用户刚执行的操作,下一轮一定能搜到,不靠运气。

如果只用一个 collection 硬扛实时写入 + 高精检索,基本会卡在「要么写不动,要么搜不准」之间来回摆。

5. 非向量数据库怎么做长期记忆

我倾向于「结构化事实 + 混合检索」

user memory 和一部分 task memory,用结构化存储更稳,理由:

  • 可编辑(update/delete)是常态操作
  • 需要强一致(至少单用户维度)
  • 需要权限、审计、脱敏、导出、合规删除

向量化适合「相似性召回」,不适合「事实的最终真相」。

这样拆:

5.1 User Memory

KV + 版本 + 来源,每条 user memory 至少需要:

  • key(例如 coding_lang
  • value(例如 Python
  • source(显式指令 / 隐式提取 / 管理后台)
  • updated_at
  • version(解决「多次修改」与「回滚」)
  • confidence / policy tag(是否允许自动注入、是否敏感)

然后注入策略是:每轮只注入白名单 key。别把整个用户画像 dump 进 prompt。

5.2 Event/Log

文档型存证 + 可选向量索引

工具调用日志、文件变更记录,我更愿意先当「证据」存好(文档型或日志系统),向量索引只是加速检索的手段。这样即使向量库挂了,还有可追溯的事实来源。

5.3 混合检索

先过滤,再相似度,再重排

非向量方案想要「像向量检索一样好用」,别上来就全文检索硬搜。最有效的顺序通常是:

  1. 结构化过滤(用户、项目、时间窗、标签、来源可信度)
  2. 再做相似度/全文检索召回
  3. 最后重排(时间衰减 + 类型权重 + 去重)

这套顺序能把噪声压下去,查询也更可解释。

6. 长期记忆的「可用性」核心

注入策略比检索算法更重要

很多人把精力都花在「embedding 模型选哪个」「Top-k 设多少」,上线后发现效果波动很大。

实际可能是:注入策略决定了下限

「渐进式披露」已经是很好的骨架。我补两条我认为必须做的工程约束:

6.1 注入预算必须固定

每轮对话给记忆注入多少 token,要有硬预算。例如:

  • 用户长期记忆:固定 100~300 tokens(只放稳定事实)
  • 最近摘要:固定 300~800 tokens
  • 检索片段:固定 500~1500 tokens(按任务重要性动态)

预算不固定,线上成本就不可控;更糟的是上下文挤压推理空间,模型会「看起来记住了」,实际输出质量下降。

6.2 记忆冲突处理

宁可不注入,也别注入矛盾

最常见的事故是:用户改了偏好(比如语言、格式、技术栈),旧记忆还在注入,Agent 开始精神分裂。

工程上必须有冲突策略:

  • 同一 key 的多版本:只注入最新版
  • 多来源冲突:显式指令 > 管理后台 > 隐式提取
  • 低置信度记忆:默认不注入,只在用户问到时提供候选并求确认

7. 小结

AI Agent 的长期记忆不是「把历史对话都存起来」,而是一套以可控、可维护、可纠错为目标的数据管道与检索系统——先明确记忆类型(用户稳定事实、任务状态、事件证据)并分层治理,再用“捕获 → AI 压缩 → 智能检索/注入”三段式把信息从高频噪声提炼成可用上下文;存储上用结构化数据库承载可编辑的用户/任务事实,用日志/文档留存证据,并按需用向量索引做语义召回与冷热分层,避免写入与索引、噪声与成本之间的失控;

效果上不要迷信 Top‑k,把注入预算、渐进式披露、冲突处理当作系统下限;

运维上把缓存、摘要、显式记忆工具、TTL/衰减与合规删除做成一等能力,并用成本、质量与安全指标持续观测迭代。

最终目标不是「记得更多」,而是让 Agent 在长期任务中更一致、更便宜、更可靠

以上。

AI Agent 进阶架构:渐进式披露和动态上下文管理

当 Agent 做到一定复杂度,问题往往不在模型能力本身,而在上下文怎么给、工具怎么给、流程怎么控。同一套模型,有的团队能把它用成「能稳定交付的执行系统」,有的团队只能得到「偶尔灵光一现的聊天机器人」,差距就在架构。

早期提示词工程里,上下文基本是静态的:一次性把提示词写好,然后让 LLM 自己发挥。随着架构的演化,,上下文变成动态的,它会「收」和「放」:

收(Contract):渐进式披露。屏蔽无关信息,减少 Token 消耗,聚焦注意力。(解决“准确性”)
放(Expand):动态注入。根据交互状态,主动引入外部话题、记忆片段或世界观设定。(解决“丰富性”与“持续性”)

这是一种系统架构策略:用有限 Token 去管理无限信息,用非确定性模型去执行标准化流程

1. 三个典型瓶颈:Context、工具、SOP

复杂 Agent 基本都会遇到三个主要的问题:

  1. 上下文爆炸(Context Explosion)
    文档、代码、历史对话、用户画像、任务状态……你不可能全塞进 Prompt。硬塞进去也会出现“Lost in the Middle”,关键信息被淹没。

  2. 工具过载(Tool Overload)
    工具越多,定义越长,Token 越贵;更严重的问题是:工具选项越多,模型选择正确工具的概率越低,尤其是多个工具功能相近时。

  3. 执行不可控
    当我们希望它按 SOP 做事(先检查、再验证、最后提交),它却容易跳步、漏步,或者为了“把话说圆”而瞎编执行结果。

「渐进式披露 + 动态上下文管理」就是对这三件事的统一解法:不要一次把世界交给模型,而是让模型在每一步只看到它此刻需要看到的东西。

2. 渐进式披露

渐进式披露不是少给信息,是分阶段给信息

有人把渐进式披露理解成省 Token。省 Token 是结果,不是核心。

核心是:把一次性的大上下文,拆成多轮的决策—反馈—再决策。每一步只给与当前决策相关的最小信息面,减少噪音,让模型的注意力更集中,也让系统更可控。

一个直观的工程化表述:

  • 不是构建一个「全量 Context」
  • 而是维护一个「可增长的 Context」,并且增长受控

你会看到两个动作交替出现:

  • Contract(收缩):隐藏、裁剪、摘要、替换为索引
  • Expand(扩张):按需加载片段、工具子集、记忆、世界观、流程状态

3. 数据层

传统做法,使用 RAG 很容易走向粗暴:检索到的内容直接拼进 Prompt,能拼多少拼多少(可以配置)。结果通常是两种:

  • Token 变贵,延迟变长
  • 模型注意力被稀释,反而更不准

渐进式披露在数据层的落地方式,是把「获取信息」做成连续的动作序列,而不是一次性拉满。

参考 AI Conding 很贴近工程实际的步骤:

  • 初始 Prompt 只有任务描述
  • AI 发现信息不足,发起 lsgrep 请求
  • 系统只返回 ls 的结果(文件名列表),而不是文件内容
  • AI 选中目标,发起 read_file
  • 系统这时才披露文件内容

这里关键点不是 ls/grep/read_file 这些名字,而是信息披露粒度

  • 先给目录/索引(低成本,低噪音)
  • 再给片段(命中后才扩大)
  • 最后给全文(只在确认需要时才给)

3.1 披露层级建议:L0 到 L3

可以把上下文分成几层,这里定义的层级不是标准答案,但思路是这么个思路:

  • L0:任务和约束
    用户需求、输出格式、禁止事项、成功标准。L0 必须稳定,尽量短,长期驻留。

  • L1:证据索引
    文件列表、章节目录、数据库表名、日志摘要、搜索结果标题。只给“在哪里”。

  • L2:证据片段
    命中的段落、代码片段、表结构、关键日志区间。只给“相关部分”。

  • L3:证据全量
    全文档、完整文件、长对话历史。尽量少用,只在确实需要通读时开放。

系统要做的事是:让模型先用 L1 做定位,再用 L2 做判断,最后才允许 L3 进场。这样不仅省 Token,还可以减少模型在噪音里自我发挥的空间

3.2 动态注入

动态注入常见误区:用户问 A,你检索 A;用户又问 B,你把 A+B 都塞进去;几轮后上下文就乱了,且不可控了。

比较常用的做法是引入「上下文预算」和「淘汰策略」:

  • 每轮允许注入的 Token 上限(硬预算)
  • 驻留区(长期有效,例如用户身份、偏好、当前任务)
  • 工作区(当前步骤的证据片段)
  • 冷存区(旧证据移出,保留索引或摘要)

淘汰的对象通常是“旧证据全文”,不是“任务状态”。任务状态丢了,模型就会重复问、重复做;证据全文丢了,大不了重新检索。

4. 工具层

工具越多越强这件事,在 Agent 里是反的:工具越多,模型越容易犹豫、选错,甚至编造「我已经调用了某某 工具」。

渐进式披露在工具层的做法是:分层路由,按需可见

参考一个很实用的层级披露思路:

  • Root 层只披露 5 个大类工具:代码类文档类部署类数据库类通知类
  • 模型先选大类,例如“我要查数据”-> 数据库类
  • 下一轮 Prompt 才披露数据库相关的具体工具,例如 sql_query, get_table_schema

我们可以把它当成「工具菜单」:

  • 第一屏:只显示一级菜单
  • 点进去:才显示二级菜单
  • 系统控制可见性,而不是让模型在 100 个工具里裸选

4.1 工具披露带来的三个工程收益

  1. Token 控制更直接
    大量工具的 schema 描述会花费大量的 Token。层级分发能把「工具定义成本」分摊到多轮,而且只在需要时支付。

  2. 工具选择准确率提升
    选项少,模型更容易做对;更重要的是,减少「近义工具」同时出现。

  3. 安全策略更好落地
    不该给的能力,默认不可见。你不需要在 Prompt 里反复警告“不要调用某某工具”,直接让它看不见。

4.2 「工具可见性」本质是一种权限系统

很多团队权限做在网关、做在后端鉴权,但 Agent 的权限还应该做在“可见性”上:

  • 看不见:降低误用概率
  • 看得见但不可用:模型会反复尝试,浪费回合
  • 可用但有条件:需要把条件变成流程状态的一部分(下一节讲 SOP)

5. SOP 层

SOP 层就是当前很火热的 Skills,且不仅仅是 Skills,它是把流程写进披露逻辑,而不是写在提示词里

企业场景里,最怕的是「看似完成、实际没做」,而这在大模型的输出中很常见。让模型「请遵循 SO」”意义不大,它会漏步骤,而且它很擅长把漏掉的步骤用语言补上。

渐进式披露在 SOP 上的落地方式,是在我们的系统里做“流程锁”:上一步没通过,下一步的工具就不出现

参考一段很清晰的流程控制(关键点直接引用):

  1. 阶段一(Lint):系统只披露 Lint 工具和当前 Diff,隐藏 Commit 工具
  2. 阶段二(Test):Lint 返回 Success 后,系统才披露 Test 工具
  3. 阶段三(Commit):只有测试通过,系统才披露 git_commit

这套逻辑解决的是“话术不可信”的问题:模型可以说“我已经测试通过”,但系统的状态机不会因为它一句话就放行。放行只能来自可验证的工具回执

5.1 SOP 控制要点

把「检查点」设计成机器可判定

SOP 最容易失败的地方是检查点含糊,比如“确保无问题”“确认完成”。Agent 体系里要改成:

  • 有工具回执的:以回执为准
  • 没有工具回执的:以人工确认或外部系统状态为准
  • 不能验证的:不要当作放行条件

能自动化判定,就不要让模型自评。

6. 为什么要引入 Agent Skill

这里本质是一种是工程分层,当然也是概念包装。

很多人会问:这些用代码控制不就行了,为什么还要提 Agent Skill?

把 Skill 当成一个架构抽象,会更容易把系统做稳:它解决的是解耦、复用、状态感知

这里把关键逻辑说透:

6.1 Skill 是「上下文的容器」,用完即走

没有 Skill 时,你往往会得到一个越来越大的系统提示词:把所有话题、所有工具、所有规则都塞进去。结果就是注意力迷失、指令冲突、Token 爆炸。

有 Skill 后,你把「某一类任务需要的提示词 + 可用工具 + 知识入口」封装到一起:

  • 需要时加载
  • 不需要时卸载
  • 上下文保持干净

这和「渐进式披露」是同一件事:Skill 是披露的载体

6.2 Skill 是「动态注入」的边界

动态注入真正难的是边界:注入多少、注入什么、何时撤回。

Skill 让边界清晰:

  • 注入不是“往 Prompt 拼字符串”
  • 注入是“激活某个 Skill”,让它把需要的最小信息面带进来

系统因此更容易做预算、做审计、做回放。

6.3 Skill 让路由变成可维护的系统,而不是靠直觉写 prompt

复杂 Agent 一定会路由:用户一句话可能触发“查资料 / 写代码 / 安抚情绪 / 改流程 / 发通知”。

Skill 体系下,路由的输出是“激活哪些 Skill”,而不是“写一段更长的提示词”。这会直接改善维护体验:

  • 你能统计每个 Skill 的触发率、成功率、平均 Token
  • 你能对某个 Skill 单独迭代,而不牵一发动全身
  • 你能为不同用户、不同权限加载不同 Skill 组合

7. 动态上下文管理

动态上下文管理要管理「状态 + 证据 + 权限」。

把上下文当成一段文本来拼接,迟早失控。更合理的视角是:上下文是系统状态在模型侧的投影。

建议把上下文拆成四类对象,每一类有不同的生命周期:

  1. 任务状态
    当前处于哪个阶段、已完成哪些检查点、下一步允许做什么。它要短、稳定、结构化,尽量每轮都带。

  2. 证据
    检索片段、工具输出、外部信息。它要可引用、可追溯、可淘汰。

  3. 偏好与长期记忆
    能影响输出风格或长期策略的东西。它不该频繁变化,变化要可控,最好有写入门槛。

  4. 能力与权限
    工具可见性、工具可用性、流程放行条件。它是约束,不是参考建议。

8. 可执行的架构清单

按“先做什么更值”排:

  1. 先做工具可见性控制
    工具分层,默认只给 Root 类目;按分支披露具体工具。

  2. 把 SOP 变成状态机放行
    上一步成功回执出现,下一步工具才可见。失败就停在当前阶段,不要让模型口头放行。

  3. 把上下文分区:驻留区 / 工作区 / 冷存区
    驻留区短且稳定;工作区有预算;冷存区只保留索引/摘要。

  4. 先索引后片段的披露策略
    任何大文本资源都先给目录、标题、命中位置,再给片段,不要一上来就全文。

  5. Skill 化你的上下文与工具组合
    让“动态注入”从拼 Prompt 变成“加载/卸载 Skill”。一开始不需要 100 个 Skill,把高频的 5–10 个先做稳。

  6. 把观测补齐
    记录每轮:披露了哪些证据、开放了哪些工具、触发了哪些 Skill、用了多少 Token、是否命中检查点。没有这些数据,后面很难迭代。

9. 小结

一个成熟的 Agent 系统,外观上像在聊天,内部其实在跑一套受控的执行架构:

  • 信息不是一次塞满,而是按步骤披露
  • 工具不是全量开放,而是按层级开放
  • 流程不是靠自觉,而是靠状态机约束
  • 记忆不是越多越好,而是可写入、可淘汰、可追溯

把这四件事做好,Agent 会越来越像一个靠谱的执行系统:该问的问清楚,该查的查到证据,该做的按流程做完,做不到就停下来,不会硬编。

这就是「渐进式披露 + 动态上下文管理」的价值:不是让 Agent 说得更像,而是让它做得更稳。

以上。