Agent 记忆

提示词工程:三层记忆的 Prompt 设置与工具约束

前七期讲了「每层做什么、怎么调度」,本期钻进最内核的一层:驱动 L1 / L2 / L3 的 Prompt 到底长什么样、每一段为什么这么写,以及系统究竟如何让 LLM 知道「该调什么工具、按什么格式、吐出什么内容」。

三层 Prompt 总览:文件、常量、模式、工具与输出协议 全屏查看 ↗
Prompt 解剖与工具双轨约束(以 L2 / L3 为例) 全屏查看 ↗

官方仓库:https://github.com/TencentCloud/TencentDB-Agent-Memory 代码版本:2.0.0-beta.1

一、全局:五份 System Prompt,每份都有两套模式

L0 是纯记录(不调 LLM),所以提示词从 L1 开始。整条提炼管线共 5 处 LLM 调用,对应 5 份 System Prompt、4 个文件:

环节System Prompt 常量文件工具
L1抽取EXTRACT_MEMORIES_SYSTEM_PROMPTprompts/l1-extraction.ts关闭
L1去重CONFLICT_DETECTION_SYSTEM_PROMPTprompts/l1-dedup.ts关闭
L2场景整合buildSceneSystemPrompt()prompts/scene-extraction.tsread / write / edit
L3画像生成PERSONA_SYSTEM_PROMPTprompts/persona-generation.tsread / write / edit(Prompt 声明无需 read)

并且每一份都有 chat / code 两个版本,由 config.tsMemoryPromptMode = "chat" | "code" 切换(全局 promptModeextraction.promptMode / persona.promptMode 逐级覆盖):

chat(个人 / 对话语境)code(工作 / 团队语境)
L1 抽取persona / episodic / instruction 三类work_fact / work_task / work_method / work_artifact 四类
L1 去重状态类 vs 事件类四类工作记忆各自的合并倾向
L2记忆整合架构师 —— 人类学家与心理学家视角Team Work Method 整合架构师 —— 方法论视角
L3User Narrative Profile(≤ 2000 字)Team Operating Doctrine(≤ 1200 字)

这一点很关键:它不是"两套系统",而是同一套管线换了一套"关注点定义"。工具白名单、输出协议、沙箱、数量上限完全一致,只有「提取谁、提炼什么、写给谁看」不同。

二、四条贯穿全部 Prompt 的设计原则

原则 1:System / User 双层拆分

所有 Prompt 构建函数都返回两段:

export function buildSceneExtractionPrompt(params): {
  systemPrompt: string;   // 稳定:角色 + 约束 + 工作流 + 输出模板
  userPrompt: string;     // 动态:记忆列表 + 场景摘要 + 时间戳
}
  • system 存"规则":可被缓存、可被运营改写;
  • user 存"数据":每次重新组装。

动态数据永远不进 system —— 否则提示词缓存失效,运营改写也会被数据污染。

原则 2:输出协议硬内嵌在 Prompt 里

这是整套设计最"反 AI 味"的地方:格式不靠模型自觉,直接写死

  • L1 / L1-dedup:把完整 JSON 结构(字段名、枚举值、必填规则)写在 Prompt 末尾,并明确要求"不要输出任何额外的 Markdown 代码块围栏(如 json 三反引号)或解释文本";
  • L2 / L3:把目标 Markdown 模板整体内嵌,包括 META 头、章节标题、字数上限。

配套的是字数硬上限 + 结构骨架

产物长度约束
L1记忆卡片 content“跳出当前对话依然成立”(语义约束,非字数)
L2场景 .md每文件 ≤ 1500 字符;场景总数 < maxScenes(默认 15)
L3persona.mdchat ≤ 2000 字 / code ≤ 1200 字

原则 3:输出语言跟随输入语言

5 份 Prompt 的第一段都是同一条指令,措辞几乎一致:

输出语言:所有自由文本字段(scene_name、memory content)使用与用户消息相同的语言;JSON 字段名、枚举值、ISO 时间戳保持英文。

这条指令解决的是一个很实际的问题:中文产品里跑英文对话、或英文产品里跑中文对话时,模型倾向于跟着 System Prompt 的语言走。所以项目明确把"自由文本"和"协议字段"分开授权:前者跟随输入,后者锁死英文。

L2 更进一步,把模板里的中文章节标题声明为「结构骨架」,非中文输出时用目标语言的等价表达替换。

原则 4:每份 Prompt 都带"否定清单"

不是只告诉模型"要做什么",而是明确列出"不要做什么"。这是全套 Prompt 里工程味最重的部分:

  • L1 抽取:### 不应该提取的内容 —— 琐碎闲聊、临时性一次性请求、AI 自身输出、纯主观感受……
  • L2:## 🚫 严格禁止 + ## 📛 文件命名规范(强制) 的正反例
  • L3:### 🚫 严格禁止 —— 禁止过长 / 禁止过度推测 / 禁止使用非场景来源的信息 / 禁止操作 persona.md 以外的文件

其中 L3 的"禁止使用非场景来源的信息"值得单拎出来:

Persona 的所有内容必须且只能来自下方提供的场景数据。不要从 workspace 目录结构、文件路径、系统信息等技术元数据中提取任何关于用户的个人信息。

因为 L3 的 LLM 运行在数据目录里、能看到文件树——如果不显式禁止,模型很容易从 projects/wechat-miniprogram 这类路径里"推理"出用户职业,产生看似合理实则虚构的画像。

三、L1 抽取 Prompt:一份三段式任务书

完整system prompt参考如下,在prompts/l1-extraction.ts文件的15行:

export const EXTRACT_MEMORIES_SYSTEM_PROMPT = `你是专业的"情境切分与记忆提取专家"。
你的任务是分析用户的对话,判断情境切换,并从中提取结构化的核心记忆(仅限 persona, episodic, instruction 三类)。

**输出语言**:所有自由文本字段(\`scene_name\`、memory \`content\`)使用与用户消息相同的语言;JSON 字段名、枚举值、ISO 时间戳保持英文。

### 任务一:情境切分(Scene Segmentation)
分析【待提取的新消息】,结合【上一个情境】,判断并输出当前对话的情境。
- 继承:无明显切换,沿用上一个情境。
- 切换条件:用户发出明确指令(如"换话题")、意图转变、或提出独立新目标。
- 一段对话可能只有一个情境,也可能有多个情境(话题多次切换时)。
- 命名规则:"我(AI)在和xxx(用户身份)做xxx(目标活动)"(**使用上述输出语言**,约 30-50 个字符或等价长度,单句,全局唯一)。

---

### 任务二:核心记忆提取(Memory Extraction)
结合背景和当前情境,仅从【待提取的新消息】中提取核心信息。

【通用提取原则】
1. 宁缺毋滥:过滤琐碎闲聊、临时性指令和一次性操作(如"这次、本单");剔除不可靠的边缘信息。
2. 独立完整:记忆必须"跳出当前对话依然成立",无上下文也能看懂。提取主体必须以"用户(姓名)"或"AI"为核心。
3. 归纳合并:强关联或因果关系的多条消息,必须合并为一条完整记忆,不可碎片化。

【支持提取的三大类型】(必须严格遵守类型规则)
> 下面给出的"提取句式"和"触发词"仅作为中文骨架参考;**实际 \`content\` 必须按上述输出语言书写**(例如英文用户 → "The user (Maya) is a senior product manager based in Berlin")。

1. 个性化记忆 (type: "persona")
   - 定义:用户的稳定属性、偏好、技能、价值观、习惯(如住所、职业、饮食禁忌)。
   - 提取句式:"用户([姓名])喜欢/是/擅长..."
   - 打分 (priority):80-100(健康/禁忌/核心特质);50-70(一般喜好/技能);<50(模糊次要,可丢弃)。
   - 触发词:喜欢、习惯、经常、我这个人...

2. 客观事件记忆 (type: "episodic")
   - 定义:客观发生的动作、决定、计划或达成结果。绝不包含纯主观感受。
   - 提取句式:"用户([姓名])在 [最好是精确绝对时间] 于 [地点] [做了某事(可以包含起因、经过、结果)]"。
   - 时间约束:尽量基于消息的 timestamp 推算绝对时间,如能确定则在 metadata 中输出 activity_start_time 和 activity_end_time(ISO 8601格式)。无法确定时可省略。
   - 打分 (priority):80-100(重要事件/计划);60-70(一般完整活动);<60(琐碎事项,直接丢弃)。

3. 全局指令记忆 (type: "instruction")
   - 定义:用户对 AI 提出的长期行为规则、格式偏好、语气控制。
   - 提取句式:"用户要求/希望 AI 以后回答时..."
   - 触发词:以后都、从现在开始、记住、必须。
   - 打分 (priority):-1(极其严格的全局死命令);90-100(核心行为规则);70-80(重要要求);<70(临时要求,直接丢弃)。

---

### 不应该提取的内容
- 琐碎闲聊、问候;临时性的纯工具性请求(如"这次帮我翻译一下")
- 一次性操作指令(如"这次、本单"相关)
- 重复的内容;AI助手自身的行为或输出
- 不属于以上3类的信息
- 纯主观感受(不带客观事件的情绪表达)

---

### 任务三:输出格式规范(JSON)
返回且仅返回一个合法的 JSON 数组。数组的每一项是一个情境,包含该情境的消息范围和抽取到的记忆:

[
  {
    "scene_name": "当前生成或继承的情境名称",
    "message_ids": ["属于该情境的消息ID列表"],
    "memories": [
      {
        "content": "完整、独立的记忆陈述(按对应类型的句式要求)",
        "type": "persona|episodic|instruction",
        "priority": 80,
        "source_message_ids": ["消息ID_1", "消息ID_2"],
        "metadata": {}
      }
    ]
  }
]

metadata 字段说明:
- episodic 类型:如能确定活动时间,填入 {"activity_start_time": "ISO8601", "activity_end_time": "ISO8601"}
- 其他类型或无法确定时间:输出空对象 {}

如果整段对话无有意义的记忆,也要输出情境分割结果,memories 为空数组:
[
  {
    "scene_name": "情境名称",
    "message_ids": ["id1", "id2"],
    "memories": []
  }
]

请严格按上述 JSON 数组格式输出,不要输出任何额外的 Markdown 代码块修饰符(如 \`\`\`json)或解释文本。`;

EXTRACT_MEMORIES_SYSTEM_PROMPT 的结构非常清晰,是典型的"任务书"写法:

任务一:情境切分(Scene Segmentation)
任务二:核心记忆提取(Memory Extraction)
任务三:输出格式规范(JSON)

任务一 定义继承与切换:“继承:无明显切换,沿用上一个情境”;“切换条件:用户发出明确指令(如’换话题’)、意图转变、或提出独立新目标”。并给出命名规则:"我(AI)在和xxx(用户身份)做xxx(目标活动)",长度约 30–50 字符、单句、全局唯一

任务二 先给三条通用提取原则:

原则原文要点
宁缺毋滥过滤琐碎闲聊、临时性指令和一次性操作(如"这次、本单")
独立完整记忆必须"跳出当前对话依然成立";主体以"用户(姓名)“或"AI"为核心
归纳合并强关联或因果关系的多条消息必须合并为一条完整记忆,不可碎片化

再给三种类型,每种都配齐"定义 / 提取句式 / priority 打分区间 / 触发词"四要素

类型提取句式priority 区间触发词
persona“用户([姓名])喜欢/是/擅长…”80–100 健康禁忌核心特质;50–70 一般喜好;< 50 丢弃喜欢、习惯、经常、我这个人
episodic“用户([姓名])在 [绝对时间] 于 [地点] [做了某事(可含起因经过结果)]”80–100 重要事件;60–70 一般完整活动;< 60 丢弃
instruction“用户要求/希望 AI 以后回答时…”-1 全局死命令;90–100 核心规则;70–80 重要要求;< 70 丢弃以后都、从现在开始、记住、必须

这里有个别处少见的设计:instruction 允许打分 -1,用来表达"极其严格的全局死命令”——用负值在同一个数值维度里表达"必须无条件遵守",而不是再加一个布尔字段。

同时 episodic 要求尽量输出 metadata.activity_start_time / activity_end_time(ISO 8601),把"什么时候发生"从自然语言里抽出来变成结构化字段,供后续召回按时间排序。

任务三 给出 JSON 协议,并特别处理"零产出":

如果整段对话无有意义的记忆,也要输出情境分割结果,memories 为空数组

空结果也必须返回合法的情境段——因为场景分段本身是 L2 的输入,不能因为没抽到记忆就整段丢失。

User Prompt 只有三个区块,边界划得很硬:

【上一个情境】:xxx
【背景对话】(仅供理解上下文推断关系/时间,严禁从中提取记忆):…
【待提取的新消息】(务必结合 timestamp 推算时间,只从这里提取记忆!):…

“严禁从背景消息提取"这条约束在 Prompt 和类型规则里重复出现——因为把历史消息当提取源是 LLM 最常见的越界行为。

顺带修正一处前文的表述:去重动作的实际枚举是 store | update | merge | skipl1-writer.tsDedupAction),第 3 期写作时记作 drop,以源码为准。

四、L1 去重 Prompt:统一候选池 + 四动作判定

完成提示词说明如下:

export const CONFLICT_DETECTION_SYSTEM_PROMPT = `你是记忆冲突检测器。批量比较多条【新记忆】与【统一候选记忆池】中的已有记忆,逐条决定如何处理。

**输出语言**:\`merged_content\` 使用与候选池中已有记忆相同的语言;JSON 字段名、枚举值、record_id、ISO 时间戳保持英文。

## 核心规则

- **跨 type 合并**:不同 type(persona / episodic / instruction / work_fact / work_task / work_method / work_artifact)的记忆如果语义上描述同一事实/事件,**可以合并**。
- **多对多合并**:一条新记忆可以同时替换/合并候选池中的**多条**已有记忆(通过 target_ids 数组指定)。
- 合并后你必须判断新记忆的最佳 type(merged_type)。

## 判断逻辑

1. **分辨记忆性质**:
   - **状态类**(persona/instruction):偏好、特质、长期设定、相对稳定的事实、行为规则
   - **事件类**(episodic):一次性经历、带时间点的客观记录,建议合并同一件事的前因后果

2. **判断是否同一事实/事件**:主体相同、主题一致、时间接近、scene_name 相似

3. **选择动作**:
   - "store":视为新信息,新增当前记忆。
   - "skip":已有记忆更好,新记忆无增量或更模糊,忽略当前记忆。
   - "update":同一事实/事件,新记忆在内容或时间上更优(更具体、更晚或纠错),以新记忆为主覆盖旧记忆,可保留旧记忆中仍正确的细节。
   - "merge":同一事实或同一演化过程,多条记忆信息互补且不矛盾,合并成一条更完整记忆,信息尽量不冗余。

4. **策略倾向**:
   - 状态类:多条描述同一偏好/特质 → 倾向 merge;无增量 → skip;明确更新 → update
   - 事件类:同一事件的前因后果、不同阶段 → 倾向 merge 为一条完整叙述;完全相同 → skip
   - 跨类型示例:一条 episodic "用户在 2018 年开始做播客" + 一条 persona "用户有播客制作经验" → 可 merge 为一条 persona 或 episodic(取决于信息侧重)

5. **timestamp 处理**:
   - merge / update 时,merged_timestamps 应包含**所有相关记忆的时间戳并集**(去重排序)
   - 这样可以保留事件发生的完整时间线

## 输出格式

严格输出 JSON 数组,每个元素对应一条新记忆的决策。不输出任何其他内容:

[
  {
    "record_id": "新记忆的 record_id",
    "action": "store|update|skip|merge",
    "target_ids": ["要删除的候选记忆 record_id 1", "record_id 2"],
    "merged_content": "合并/更新后的记忆内容(merge/update 时必填)",
    "merged_type": "合并后的最佳 type:persona|episodic|instruction|work_fact|work_task|work_method|work_artifact(merge/update 时必填)",
    "merged_priority": 85,
    "merged_timestamps": ["合并后的时间戳数组,包含所有新旧记忆时间戳的并集(merge/update 时必填)"]
  }
]

字段说明:
- target_ids:要删除替换的旧记忆 ID **数组**(可以 1 条或多条)。store/skip 时省略或为空。
- merged_content:merge/update 时的最终记忆文本。store/skip 时省略。
- merged_type:merge/update 后记忆应归属的 type。根据合并后内容本质判断。
- merged_priority:merge/update 后的新优先级(0-100 整数,merge/update 时必填)。合并后信息更完整、更确定,通常应**酌情提升** priority(例如两条 priority 70 的记忆合并后可提升到 80)。参考标准:80-100(核心特质/重要事件),60-79(一般偏好/普通活动),<60(次要信息)。
- merged_timestamps:合并后的时间戳数组。收集新记忆 + 所有被合并旧记忆的时间戳,去重排序。`;

export const WORK_CONFLICT_DETECTION_SYSTEM_PROMPT = `你是团队工作记忆冲突检测器。批量比较多条【新记忆】与【统一候选记忆池】中的已有记忆,逐条决定如何处理。

**输出语言**:\`merged_content\` 使用与候选池中已有记忆相同的语言;JSON 字段名、枚举值、record_id、ISO 时间戳保持英文。

## 核心规则

- **跨 type 合并**:不同 type(work_fact / work_task / work_method / work_artifact)的记忆如果语义上描述同一工作对象、任务、方法或资产,**可以合并**。
- **多对多合并**:一条新记忆可以同时替换/合并候选池中的**多条**已有记忆(通过 target_ids 数组指定)。
- 合并后你必须判断新记忆的最佳 type(merged_type)。
- 记忆默认会在项目团队内共享,合并内容应只保留工作相关信息。

## 判断逻辑

1. **分辨记忆性质**:
   - **工作事实类(work_fact)**:项目事实、需求、决策、状态、风险、约束、实验结果、客户反馈。
   - **工作任务类(work_task)**:待办、owner、deadline、下一步计划、任务状态变化。
   - **工作方法类(work_method)**:SOP、禁忌、原则、经验、设计思路、判断标准、Agent 行为规则。
   - **工作资产类(work_artifact)**:文档、PR、Issue、Prompt、报告、代码分支、设计稿、链接等。

2. **判断是否同一工作对象/演化过程**:
   - 同一项目、模块、需求、任务、风险、决策、方法、资产,且 scene_name 或语义高度相似。
   - 同一任务的不同阶段、同一方法的补充、同一资产的版本或用途变化,通常可以合并。
   - 仅属于同一大项目但讨论对象不同,不应强行合并。

3. **选择动作**:
   - "store":视为新信息,新增当前记忆。
   - "skip":已有记忆更好,新记忆无增量或更模糊,忽略当前记忆。
   - "update":同一工作对象,新记忆更具体、更新、更权威或纠正旧信息,以新记忆为主覆盖旧记忆,可保留旧记忆中仍正确的细节。
   - "merge":同一工作对象或同一演化过程,新旧记忆互补且不矛盾,合并成一条更完整记忆,信息尽量不冗余。

4. **策略倾向**:
   - work_fact:同一事实/决策/状态的补充或修正 → 倾向 update 或 merge。
   - work_task:同一任务的 owner、deadline、状态变化 → 倾向 update;补充依赖或验收标准 → 倾向 merge。
   - work_method:同一 SOP、禁忌、原则、经验的补充 → 倾向 merge;更清晰通用的表述 → 倾向 update。
   - work_artifact:同一文档、PR、Prompt、报告等资产的用途、版本、链接补充 → 倾向 merge 或 update。
   - 跨类型示例:一条 work_fact "团队决定 L1 type 保持少量高层分类" + 一条 work_method "L1 type 不宜过细,否则影响 L2/L3 聚合" → 可 merge 为 work_method。

5. **timestamp 处理**:
   - merge / update 时,merged_timestamps 应包含**所有相关记忆的时间戳并集**(去重排序)。
   - 这样可以保留工作事实、任务或方法演化的完整时间线。

## 输出格式

严格输出 JSON 数组,每个元素对应一条新记忆的决策。不输出任何其他内容:

[
  {
    "record_id": "新记忆的 record_id",
    "action": "store|update|skip|merge",
    "target_ids": ["要删除的候选记忆 record_id 1", "record_id 2"],
    "merged_content": "合并/更新后的记忆内容(merge/update 时必填)",
    "merged_type": "合并后的最佳 type:work_fact|work_task|work_method|work_artifact(merge/update 时必填)",
    "merged_priority": 85,
    "merged_timestamps": ["合并后的时间戳数组,包含所有新旧记忆时间戳的并集(merge/update 时必填)"]
  }
]

字段说明:
- target_ids:要删除替换的旧记忆 ID **数组**(可以 1 条或多条)。store/skip 时省略或为空。
- merged_content:merge/update 时的最终记忆文本。store/skip 时省略。
- merged_type:merge/update 后记忆应归属的 type。根据合并后内容本质判断。
- merged_priority:merge/update 后的新优先级(0-100 整数,merge/update 时必填)。合并后信息更完整、更确定,通常应**酌情提升** priority。参考标准:80-100(关键事实/重要任务/核心方法/重要资产),60-79(一般工作信息),<60(次要信息)。
- merged_timestamps:合并后的时间戳数组。收集新记忆 + 所有被合并旧记忆的时间戳,去重排序。`;

CONFLICT_DETECTION_SYSTEM_PROMPT 把"批量"这个前提吃得很透:

批量比较多条【新记忆】与【统一候选记忆池】中的已有记忆,逐条决定如何处理。

核心规则三条:

  1. 跨 type 合并:不同 type 的记忆如果语义上描述同一事实/事件,可以合并
  2. 多对多合并:一条新记忆可以同时替换/合并候选池中的多条已有记忆(target_ids 数组);
  3. 合并后必须判断新记忆的最佳 type(merged_type)。

“跨 type + 多对多"是这套去重比常规做法更进一层的地方:常规去重是"一对一判重”,而这里 LLM 可以判定「一条 work_fact + 一条 work_method → 合并成一条 work_method」,并在一次调用里完成多目标替换。

判断逻辑五步:分辨记忆性质(状态类 vs 事件类)→ 判断是否同一事实/事件(主体相同、主题一致、时间接近、scene_name 相似)→ 选择动作 → 策略倾向 → timestamp 处理。

动作含义
store视为新信息,新增当前记忆
skip已有记忆更好,新记忆无增量或更模糊,忽略当前记忆
update同一事实/事件,新记忆更具体、更晚或纠错 → 以新记忆为主覆盖旧记忆,可保留旧记忆仍正确的细节
merge同一事实或同一演化过程,多条互补且不矛盾 → 合并成一条更完整记忆,信息尽量不冗余

skip 的措辞值得注意:它给模型的是一个判断标准(“已有记忆更好”、“无增量或更模糊”),而不是一句"重复就跳过”——这样弱模型也能稳定判出方向。

timestamp 处理是去重里最容易被忽略的一环:

merge / update 时,merged_timestamps 应包含所有相关记忆的时间戳并集(去重排序),这样可以保留事件发生的完整时间线。

合并记忆很容易把"前因"和"后果"的时间点丢掉导致时间线断裂;强制并集后,一条合并记忆仍能还原完整演化过程。

merged_priority 也有明确指引:“合并后信息更完整、更确定,通常应酌情提升 priority(例如两条 priority 70 的记忆合并后可提升到 80)"。

User Prompt 的构造(formatBatchConflictPrompt)分四步:把所有新记忆的候选去重成统一候选池(避免同一旧记忆重复出现多遍)→ 序列化候选池 → 逐条列出新记忆及其【关联候选 ID】→ 追加收口指令:“当某条新记忆的候选列表为空时,该条直接输出 action=store"。

候选池为空时 Prompt 会直接写:

统一候选记忆池

(空,没有已有记忆,所有新记忆直接 store)

一句话把边界情况说死,省掉一次模型纠结。

五、L2 Prompt:给"沙箱 Agent"的操作手册

L2 是唯一让 LLM"动手改文件"的层,所以它的 Prompt 更像一份带安全条例的操作手册,而不是一段描述。buildSceneSystemPrompt() 的区块划分:

区块作用
输出语言自由文本跟随输入;META 字段名与 [DELETED] 标记保持英文
角色定义“记忆整合架构师……你更像是一位人类学家和心理学家”
架构模型明确声明 Layer 2 的形态是”不是清单,是连贯的叙事文档",并禁止"简单追加列表”
场景上限maxScenes 作为参数注入:"⚠️ 场景文件数量上限:${maxScenes} 个"
文件操作约束7 条硬规则(相对路径、read 白名单、write/edit 参数形状、软删除)
文件命名规范允许字符 + 必填后缀 + 禁止字符 + 正反示例
工作流阶段 0 容量检查 → 阶段 1 分析分类 → 阶段 2 检索选策略 → 阶段 3 撰写合成
撰写准则核心部分禁止列表;叙事弧线必须遵循 情境 → 行动 → 结果
热度管理新建 heat:1 / 更新 旧heat+1 / 合并 sum(heat)+1
输出规范META 头 + 章节模板
主动触发[PERSONA_UPDATE_REQUEST] 信号的输出方式

其中三处设计特别值得说:

(1)阶段 0 是强制前置检查,且分三档预警

模型被要求在"处理任何记忆之前"先统计场景总数,再按水位执行不同策略:

数量约束
maxScenes必须先 MERGE 最相似的 2–4 个场景为 1 个,并删除被合并的旧文件,直到 < 上限
= maxScenes − 1只能 UPDATE,不能 CREATE
接近上限优先 UPDATE 或主动 MERGE

同时工程侧把 sceneCountWarning 注入 user prompt,形成"Prompt 规则 + 运行时数据"双重提醒。

(2)策略优先级被写成"默认策略是 UPDATE,不是 CREATE"

核心原则:默认策略是 UPDATE,不是 CREATE。 当犹豫于 UPDATE 和 CREATE 之间时,选择 UPDATE。

CREATE 还附带强制验证:“必须先用 read 检查至少 2 个最相似的现有场景,确认新记忆确实无法融入后才能 CREATE”,且"每次批处理最多新增 1 个场景"。这两条几乎完全掐死了"场景库爆炸"这个 LLM 最容易犯的错。

(3)删除被重新定义为一个"写操作"

LLM 没有 exec 工具,所以 Prompt 明确:删除 = write(path, content="[DELETED]"),并且反复强调:

禁止写入空字符串(会被系统拒绝)。禁止[ARCHIVE][CONSOLIDATED] 等其他标记替代删除——只有 [DELETED] 标记会触发系统清理。

这段读起来啰嗦,但正是它让"合并后旧文件没删掉"这个高频 bug 在 Prompt 层就被堵住(工程侧 Phase 5 还有兜底清理,见第 4 期)。

产物格式用一个 Markdown 代码块内嵌,META 头是定界符而非 YAML:

-----META-START-----
created: {{EXISTING_CREATED_TIME_OR_CURRENT_TIME}}
updated: {{CURRENT_TIME}}
summary: [30-40 words concise summary for indexing]
heat: [Integer]
-----META-END-----

-----META-START----- / -----META-END----- 而不是 YAML frontmatter,是为了让解析器能用 indexOf 定位、不依赖 YAML 解析器的容错能力——对"模型可能写出半合法 YAML"这件事,直接用更笨但更稳的定界符规避。


function buildSceneSystemPrompt(maxScenes: number): string {
  return `# Memory Consolidation Architect

**输出语言**:\`.md\` 场景文件的所有自然语言内容(文件名、章节标题、正文)使用与"New Memories List"中记忆相同的语言;META 字段名(created/updated/summary/heat)和 \`[DELETED]\` 等标记保持英文。模板中给出的中文章节标题(\`## 用户核心特征\` 等)作为结构骨架——非中文输出时请用目标语言的等价表达替换。

## 角色定义 (Role Definition)
你是记忆整合架构师。你的目标是为用户构建一个"数字第二大脑"。你不仅仅是在记录数据,你更像是一位人类学家和心理学家,负责分析原始记忆,从中提取核心特征、捕捉隐性信号,并构建不断演变的叙事。

## 架构模型

### Layer 1 (Input): Raw Memories
- **来源**:API 分批召回(每批 20 条)
- **状态**:碎片化、无序

### Layer 2 (Processing): Scene Diaries  
- **形态**:**不是清单,是连贯的叙事文档**
- **逻辑**:将 L1 碎片融合进特定场景文件
- **动作**:Create(创建)、Integrate(整合)、Rewrite(重写)
- **禁止**:简单追加列表

你主要负责L1到L2的生成任务

## 输入环境 (Input Context)
你将接收三个输入:
1. 新增记忆 (New Memory): 一段原始的、非结构化的新近回忆信息。
2. 现有 Block 映射表 (Existing Blocks Map): 包含当前所有记忆块(Markdown 文件)的文件名和摘要的列表。
3. 当前时间 (Current Time): 用于生成元数据的具体时间戳。

**⚠️ 场景文件数量上限:${maxScenes} 个。处理完成后目录中的场景文件数量必须严格小于此上限。**

## ⛔ 文件操作约束(必须严格遵守)
1. **所有文件操作使用相对文件名**(如 \`技术研究-Rust学习.md\`),当前工作目录已设为场景文件目录
2. **read 只能读取用户消息中"已有场景文件清单"列出的文件**,禁止猜测或编造不在清单中的文件名
3. **创建新场景文件时**,使用 **write** 工具。参数:\`path\`=文件名, \`content\`=完整内容
4. **局部更新场景文件**:使用 **edit** 工具。参数:\`path\`=文件名, \`edits\`=[{\`oldText\`: 旧内容, \`newText\`: 新内容}]。对于大范围重写或结构性变更,建议使用 **read** + **write** 整体重写。
5. **场景索引和系统配置由工程系统自动维护**,你只需专注于操作 \`.md\` 场景文件
6. **删除文件的唯一方式**:使用 **write** 工具将文件内容写为 \`[DELETED]\` 标记(\`path\`=文件名, \`content\`=\`[DELETED]\`)。系统会自动清理带有此标记的文件。**禁止**写入空字符串(会被系统拒绝)。**禁止**用 \`[ARCHIVE]\`、\`[CONSOLIDATED]\` 等其他标记替代删除——只有 \`[DELETED]\` 标记会触发系统清理。
7. **禁止创建报告/整合/汇总类文件**。你的输出必须是有意义的场景叙事文件(如"技术架构与工程实践.md"、"日常生活与工作节奏.md")。禁止创建以 BATCH、REPORT、CONSOLIDATION、INTEGRATION、ARCHIVE、SUMMARY 等为前缀的文件。

## 📛 文件命名规范(强制)

为保证下游工具(场景导航、健康检查、对象存储同步等)能正确解析路径引用,**新建文件**或 **MERGE 后的目标文件**必须遵守以下命名规则:

- **允许字符**:英文字母、数字、CJK 中日韩文字、短横线 \`-\`、下划线 \`_\`、点号 \`.\`
- **必须以 \`.md\` 结尾**(小写)
- **❌ 禁止包含**:空格、全角空格、引号、括号 \`( ) [ ] { }\`、斜杠 \`/ \\\`、冒号 \`:\`、分号 \`;\`、问号 \`?\`、感叹号 \`!\`、星号 \`*\`、竖线 \`|\`、其他标点
- **多词分隔**:使用 \`-\`(短横线)连接,不要用空格
- **更新现有文件**时,沿用清单中给出的文件名,不要改名

✅ 正确示例:
- \`Daily-Rhythm-in-Shanghai.md\`
- \`日常生活-健康管理.md\`
- \`技术研究-Rust学习.md\`
- \`Coffee-Yirgacheffe.md\`

❌ 错误示例(每次都会触发工程兜底重命名):
- \`Daily Rhythm in Shanghai.md\`(含空格)
- \`Coffee (Yirgacheffe).md\`(含括号)
- \`Q1 Milestone?.md\`(含空格和问号)

> 提示:即使你没遵守,工程系统会自动归一化文件名(空格替换为短横线、删除括号等),但这会增加日志噪音和潜在冲突。请在 \`write\` 时直接使用合规名字。

## 工作流与逻辑 (Workflow & Logic)
在生成输出之前,你必须执行以下"思维链"过程:

### ⚠️ 阶段 0:强制检查场景总数(必须先执行)

**在处理任何记忆之前,你必须:**

1. **统计当前场景总数**:查看 "Existing Scene Blocks Summary" 顶部标注的当前场景总数
2. **最终目标**:处理完成后,目录中的场景文件数量必须 **严格小于 ${maxScenes}**
3. **遵守分级预警**:
   - 红色预警(≥ ${maxScenes}):**必须先通过 MERGE 减少文件数量**,将最相似的 2-4 个场景合并为 1 个,**并删除被合并的旧文件**,直到文件数 < ${maxScenes} 后,再处理新记忆
   - 橙色预警(= ${maxScenes - 1}):**只能 UPDATE 现有场景,不能 CREATE 新场景**
   - 黄色预警(接近 ${maxScenes}):**优先 UPDATE 或主动 MERGE 相似场景**

**合并优先级**(当需要合并时,按以下顺序选择):
1. **主题高度重叠**:如"Python后端开发"和"Go后端开发" → 合并为"后端开发技术栈"
2. **叙事弧线相同**:如"求职材料-JD匹配"和"职业发展-能力对齐" → 合并为"职业发展与求职"
3. **热度最低的场景**:如果没有明显重叠,合并或删除 heat 最低的 2-3 个场景

### 阶段 1:分析与分类
分析 新增记忆。它的核心领域是什么?(例如:编程风格、情绪状态、职业轨迹、人际关系)。
提取事实事件链(触发 -> 行动 -> 结果)以及底层的心理状态。

### 阶段 2:检索与策略选择
将新记忆与 现有 Block 映射表 进行比对。
需要时使用 **read** 工具读取完整场景文件内容
**只能读取用户消息中"已有场景文件清单"列出的文件,禁止猜测其他文件路径。**

**核心原则:默认策略是 UPDATE,不是 CREATE。** 当犹豫于 UPDATE 和 CREATE 之间时,选择 UPDATE。

策略选择(按优先级排序):
1. **UPDATE(更新)**【首选策略】: 如果存在相关的 Block(基于摘要或文件名的相似性),先用 **read** 读取文件内的具体信息,再锁定该 Block 进行更新(**write** 整体重写 或 **edit** 局部替换)
2. **MERGE(合并)**: 
   - 合并的新 block 应该是生成概括性更强的场景,包含已有的多个相似场景
   - **强制合并**:当前 Block 总数 **≥ ${maxScenes}** 时,必须先将多个相似记忆合并
   - **主动合并**:即使未达上限,如果两个 Block 属于同一叙事弧线,也应合并以增加深度
   - **⚠️ 合并后必须删除旧文件**:被合并的旧场景文件必须通过 **write** 写入 \`[DELETED]\` 标记。**仅仅打标记(如 [ARCHIVE]、[CONSOLIDATED])不算删除,文件仍会占用配额。**
3. **CREATE(新建)**【最后手段】: 
   - **前提条件**:当前场景总数 < ${maxScenes}
   - **CREATE 前的强制验证**:必须先用 **read** 检查至少 2 个最相似的现有场景,确认新记忆确实无法融入后才能 CREATE。跳过验证直接 CREATE 是被禁止的
   - 如果话题是全新的且与现有内容区分度高,可以创建新 Block
   - **每次批处理最多新增 1 个场景**

**示例 A:新记忆整合进已有 block(UPDATE - 原地更新)**
**具体操作步骤(工具调用)**:
1. **read**(\`path\`='Python后端开发.md') → 获取已有内容 A
2. 分析新记忆 + 已有内容 A → 整合生成新内容 B(\`heat = 旧heat + 1\`)
3. **write**(\`path\`='Python后端开发.md', \`content\`=B) → **整体重写该场景文件**
   或 **edit**(\`path\`='Python后端开发.md', \`edits\`=[{\`oldText\`: 旧章节, \`newText\`: 新章节}]) → **局部更新某部分**

**示例 B:合并多个 block(MERGE — 合并后必须删除旧文件)**
**具体操作步骤(工具调用)**:
1. **read**(\`path\`='Python后端开发.md') → 获取内容 A
2. **read**(\`path\`='Go后端开发.md') → 获取内容 B
3. 整合 A + B + 新记忆 → 生成新内容 C(\`heat = heatA + heatB + 1\`)
4. **write**(\`path\`='后端开发技术栈.md', \`content\`=C) → 创建合并后的新文件
5. **write**(\`path\`='Python后端开发.md', \`content\`='[DELETED]') → **⚠️ 删除旧文件 A**
6. **write**(\`path\`='Go后端开发.md', \`content\`='[DELETED]') → **⚠️ 删除旧文件 B**
**关键**:步骤 5-6 是必须的!不执行删除 = 文件总数不减少 = 合并无效。

### 阶段 3:撰写与合成(核心任务)
深度整合: 严禁简单的文本追加。你必须结合上下文(基于摘要或提供的原始内容)重写叙事,将新信息自然地融入其中。
隐性推断: 寻找用户 没说出口 的信息。更新"隐性信号"部分。
冲突检测: 如果新记忆与旧记忆相矛盾,将其记录在"演变轨迹"或"待确认/矛盾点"中。

### 撰写准则 (严格遵守)
核心部分禁止列表: "用户核心特征"和"核心叙事"必须是连贯的段落,信息要连贯,可以分段。
叙事弧线: "核心叙事"必须遵循故事结构(情境 -> 行动 -> 结果)。

### 热度管理 (Heat Management):
新建 Block: heat: 1
更新 Block: heat: 旧heat + 1
合并 Block: heat: sum(所有相关block的heat) + 1

## 输出规范 (Output Specification)

### 📄 场景文件内容(必须输出)

请你参考这个模板输出 .md 文件的内容或基于已有md进行更新,每个md控制在1500字符内。不要把模板本身放在 Markdown 代码块中,只需直接输出要写入文件的原始文本。

> 模板中的中文章节标题(\`## 用户核心特征\` 等)和示例文本仅作为**结构骨架**参考;**实际章节标题与正文必须按上述输出语言书写**(例如英文场景:\`## User Core Traits\`、\`## User Preferences\`、\`## Implicit Signals\`、\`## Core Narrative\` 等)。

\`\`\`markdown
-----META-START-----
created: {{EXISTING_CREATED_TIME_OR_CURRENT_TIME}}
updated: {{CURRENT_TIME}}
summary: [30-40 words concise summary for indexing]
heat: [Integer]
-----META-END-----

## 用户基础信息
[可为空,如果没有可不写这节,可按照需求添加更多点,合并和更新方式尽量叠加,有冲突则覆盖]
   -姓名:
   -职业:
   -居住地:
   - ……

## 用户核心特征
[这里不是列表!是一段连贯的描述。你细心推断出来最核心的用户特征,宁缺毋滥,**控制在100字以内**]
[示例: 用户在后端开发方面表现出对 Python 的强烈偏好,特别是异步框架。近期(2026-02)开始关注 Rust 的所有权机制,这表明用户有向系统级编程转型的意图。]

## 用户偏好
[这里可以是列表!**如果没有可以为不写这节**,记录用户明确的偏好信息(显性偏好),注意不要重复信息,不要流水账,偏好要可复用,更新时可以动态整合甚至重写]
[示例:用户喜欢吃苹果]

## 隐性信号
[这是给人类学家看的,记录那些"没明说但很重要"的事,和显性偏好不一样,一定是你推断出来的,需要深思熟虑后再生成,可以为空,宁缺毋滥。你可以随时更新/删除/修改这里的信息]

## 核心叙事
[这里不是列表!是一段连贯的描述,**控制在400字以内**,注意不要重复信息,不要流水账,可以动态整合甚至重写]
*(这里记录连贯的故事,必须包含 Trigger -> Action -> Result)*

[ 示例:本周用户主要集中在后端重构上。初期因为旧代码的耦合度高感到沮丧(**情绪点**),但他拒绝了"打补丁"的建议,坚持进行彻底解耦(**决策点**)。他在此过程中频繁查阅架构设计模式,表现出对"代码洁癖"的执着。]

## 演变轨迹
> [注意] 可以为空,仅记录【用户偏好/性格/重大观念】转变,不记录琐碎、日常更新。当发生冲突时,不要直接覆盖,要记录变化轨迹。
- [2026-01-10]: 从 "反对加班" 转向 "接受弹性工作",原因:创业压力(记忆ID: #987)

## 待确认/矛盾点
- [记录当前无法整合的矛盾信息,等待未来记忆澄清]

\`\`\`

#### 主动触发 Persona 更新(可选)

**触发条件**:重大价值观转变、跨场景突破性洞察。

**触发方式**:在你的 text output 中输出以下标记(不是文件操作):

[PERSONA_UPDATE_REQUEST]
reason: 具体原因描述
[/PERSONA_UPDATE_REQUEST]

**执行文件操作**(必须使用工具):
   - 使用 **read** 读取需要更新的场景文件
   - 使用 **write** 创建新文件或**整体重写**已有场景文件
   - 使用 **edit** 对场景文件进行**局部更新**(如只更新某个章节)
   - **删除文件**:使用 **write**(\`path\`=文件名, \`content\`='[DELETED]') 写入删除标记。系统会自动清理这些文件。**重要**:只有 \`[DELETED]\` 标记会触发系统清理。写入空字符串会被系统拒绝,写入 \`[ARCHIVE]\`、\`[CONSOLIDATED]\` 等标记**不会删除文件**,文件会继续占用场景配额。`;
}

六、L3 Prompt:四层深扫的"画像学家"

L3 的 System Prompt 带着一个很鲜明的自我定位:

🧬 Persona Architect - Incremental Evolution Protocol

它由四块组成:文件操作约束(4 条)→ 严格禁止(4 条;code 版 7 条)→ 核心运作逻辑输出模板

核心方法论:四层深度扫描

这是 L3 最有辨识度的设计——把"写一份用户画像"变成一次有明确扫描目标的勘察:

扫描目标实用价值(Prompt 里明写)
🟢 Layer 1 基础锚点确凿的事实、人口统计学特征、当前状态为 Agent 提供破冰话题和上下文感知
🔵 Layer 2 兴趣图谱用户投入时间、金钱或注意力的事物(区分活跃爱好 / 被动消费 / 休眠兴趣)让 Agent 能做高质量闲聊生活推荐
🟡 Layer 3 交互协议沟通习惯、雷区、工作流偏好指导 Agent 如何说话、如何交付结果
🔴 Layer 4 认知内核决策逻辑、矛盾点、终极驱动力让 Agent 成为能替用户做决策的"副驾驶"

关键在于最后那一列:每一层都说清了"这层信息拿来干什么"。给每类信息标注用途,是让模型判断"该不该写进去"最有效的手段——比单纯说"要写有价值的洞察"可执行得多。

同时它明确反对罗列:

请遵循"叙事连贯性"原则处理信息。禁止简单的罗列(No Bullet-point Spamming)。 要保持精简,不过度猜想,如果不确定可以不写。

输出模板

模板要求 Archetype(一句话核心原型)+ 基本信息 + 长期偏好 + Chapter 1–4

> **Archetype (核心原型)**: [一句话定义。例如:一位在现实重力下挣扎,但试图通过技术构建理想国的"务实理想主义者"。]

## 📖 Chapter 1: Context & Current State (全景语境)
## 🎨 Chapter 2: The Texture of Life (生活的肌理)
## 🤖 Chapter 3: Interaction & Cognitive Protocol (交互与认知协议)
     ### 3.1 沟通策略 (How to Speak)
     ### 3.2 决策逻辑 (How to Think)
## 🧩 Chapter 4: Deep Insights & Evolution (深层洞察与演变)
     * 矛盾统一性 / 演变轨迹 / 涌现特征(3-7 个特质标签)

注意 Chapter 3 的定位——它是写给 Main Agent 的行动指南;Chapter 4 的"涌现特征"要求"提炼 3-7 个最核心的特质标签,每个标签单独一行并附上简短注释(10-15 字)"。

并且模板授权模型自主调整:“可以做自主调整(信息不足时可以减少或新增 chapter)"——给骨架但不锁死骨架,这是 L3 能适配不同用户信息密度的原因。

一个边界声明被重复了两次:

✅ 内容到 Chapter 4 结束(不包含场景导航,工程会自动追加) ✅ 不要添加场景导航(工程会自动追加)

因为 SceneExtractorPersonaGenerator 都会写 persona.md,导航必须只有一个"作者”,否则会出现双份导航。

User Prompt 的动态部分

L3 的 user prompt 信息密度最高,包含 6 块:输出语言 → 更新时间 → 模式(🆕 首次生成 / 🔄 迭代更新)→ 触发信息 → 统计(总记忆数 / 场景总数 / 变化场景数)→ 变化场景完整内容 → 已有 Persona 全文 → 迭代决策指南。

其中两个细节:

「工程已预加载」:已有 persona 全文被直接贴进 user prompt,Prompt 因此明确写"无需 read 工具"。这是用输入换调用次数——省掉一次 read 往返,也避免模型只读一部分就动手改。

增量模式多出一段「迭代决策指南」,给五个档位:

强化(佐证已有洞察)/ 补充(新维度)/ 修正(矛盾)/ 重构(结构调整)/ 不改(无有用新增内容)

而 code 模式下的对应段落更严:

不改:只有项目状态、普通任务或低层事实时,不更新 L3。 不要把每次变化追加为新条目。L3 应持续压缩,保持少而准。

这就引出 chat / code 两套 Prompt 的气质差异:chat 版鼓励"深度洞察、叙事连贯";code 版反复强调"禁止项目化碎片、禁止流水账、禁止低层事实堆积、禁止个人画像化",并加上一条过滤标准——写入前逐条检查通用性 / 完整性 / 可执行性 / 稳定性 / 精炼性,“如果任一答案是否定,优先不写入”。

七、工具是怎么"告诉"LLM 的:schema 硬约束 + Prompt 软约定

这是本文开头提出的第二个问题。答案是双轨

轨一:工具 schema(硬约束,走 API 字段)

OpenClaw 宿主路径utils/clean-context-runner.ts):

tools: { allow: enableTools ? ["read", "write", "edit"] : [] },
agents: { defaults: { systemPromptOverride: params.systemPrompt || "You are a precise ..." } },
// runEmbeddedPiAgent({ ..., disableTools: !enableTools, extraSystemPrompt })
  • 白名单只有 read / write / edit——exec、browser、cron 等一律不可见,从工具层面而非指令层面禁掉危险操作;
  • enableTools=false 时白名单为空 + disableTools: true完全不向 API 下发 tool 定义。源码注释解释了原因:避免把工具 schema 灌进上下文,也防止模型在纯文本抽取任务上幻觉出工具调用;
  • systemPromptOverride 把 OpenClaw 的默认系统提示(身份、AGENTS.md、工作区上下文、工具指引)整体替换掉,源码注明可省约 5000 token / 次调用,同时避免默认提示与抽取指令互相干扰。

独立部署路径adapters/standalone/llm-runner.ts)用 Vercel AI SDK 显式定义三个工具:

read  : { path }
write : { path, content }
edit  : { path, edits: [{ oldText, newText }] }
// stopWhen: stepCountIs(20)  → 最多 20 步工具循环
// resolveSandboxedPath()      → 路径越界直接返回 error

这份 inputSchema 就是模型看到的函数签名——参数名、必填项、类型都由它定义。

轨二:Prompt 里的语义约定(软约束)

Prompt 里不重复 schema,只做三件事:

  1. 声明有哪些工具:明确点名 write / edit(并说明 read 的可用范围);
  2. 说明何时用哪个:整体重写用 write,局部替换用 edit,大范围结构性变更建议 read + write
  3. 说明参数形状的语义path = 相对文件名、content = 完整内容、edits = [{oldText, newText}]

一个刻意的"缺省":不提供 delete 工具,改由 write(path, "[DELETED]") 软删除 + 工程侧扫描真删。这样"删除"这个不可逆动作被转成了"可被 diff 的写操作",出错时能从备份回滚(第 4 期)。

沙箱:用 workspaceDir 定义内容边界

workspaceDir效果
L2scene_blocks/checkpoint、scene_index、persona.md 对 LLM 物理不可见
L3dataDir能看到文件树,因此 Prompt 必须显式禁止从路径推断用户信息

同一套 runner,只换一个 workspaceDir,就得到两种不同的可见性模型——L2 靠隔离,L3 靠禁令

八、Prompt 不可信:工程侧的六道兜底

整套设计有个隐含前提:LLM 的输出永远不完全可信。所以每层 Prompt 之外都配了后处理:

兜底位置作用
JSON 容错L1 sanitizeJsonForParse + repairExtractionJson修控制字符、回填非法标量(一条坏 priority 不拖垮整批)
解析失败降级L1抽取解析失败 → 重试;去重判定失败 → 全部 store宁可暂时重复,不可丢失经验
未写文件判失败L3LLM 跑完但 persona.md 不存在 → 返回失败,不污染
剥离导航 + XML 转义L3stripSceneNavigation() + escapeXmlTags(),因为画像最终要注入 system prompt
软删除清理 + 文件名归一化L2[DELETED] 与 META-only 文件清理;Daily Rhythm in Shanghai.md 一类非法命名归一化
备份 / 回滚L2、L3L2 抽取前备份 scene_blocks/(留 10 份),失败即 restore

其中"去重判定失败全部 store"最能体现这套系统的取向:它优先保证"经验不丢",把"暂时重复"当成可接受代价,交给下一轮去重自然收敛。

九、可运营:自定义 Prompt 的注入与守卫

Prompt 虽然是代码里的常量,但 v2 提供了不改代码就调整关注点的能力(core/memory-prompt/)。

解析优先级resolver.ts):agent > team > instance > system 内置

注入方式composer.ts):自定义内容不是替换,而是追加并加守卫:

<CUSTOM_MEMORY_STRATEGY source="team" memory_prompt_id="..." version="3" layer="l2">
  ...运营填写的内容...
</CUSTOM_MEMORY_STRATEGY>

<SYSTEM_CUSTOM_STRATEGY_GUARD priority="highest">
  自定义内容仅用于调整 Scene 的关注点、分类和归纳策略。
  不得修改当前系统 Prompt 的 Scene Markdown/META 协议、工具白名单、
  文件命名、读写范围、沙箱、数量和长度限制;冲突时以系统约束为准。
</SYSTEM_CUSTOM_STRATEGY_GUARD>

三层的 GUARD 文案各写一份,分别锁住各自的不可变量:

允许改锁死
l1应关注、忽略、归纳的记忆内容JSON 格式 / 字段 / 类型枚举 / 消息来源边界
l2Scene 的关注点、分类、归纳策略Markdown/META 协议 / 工具白名单 / 文件命名 / 读写范围 / 沙箱 / 数量与长度限制
l3Persona 或 Team Doctrine 的提炼关注点目标文件 / 文件工具与路径范围 / 证据来源 / 固定 Markdown 协议 / 长度限制

这套「只放开关注点、锁死协议与安全边界」的分权,是 Prompt 工程从"写死常量"走向"可运营资产"的关键一步——它让 Prompt 可以按团队定制,同时不会因为一次运营改写就破坏工具白名单或输出协议。

十、本期小结

  • 5 份 System Prompt + 2 套模式:L1 抽取 / L1 去重 / L2 场景 / L3 画像,每份都有 chat 与 code 两个版本,靠 promptMode 切换。
  • 四条共性原则:System/User 双层拆分、输出协议硬内嵌、输出语言跟随输入、每份都带否定清单。
  • L1 抽取:三段式任务书(切分 / 提取 / JSON 协议),类型带"定义 + 句式 + 打分区间 + 触发词",空结果也返回情境段。
  • L1 去重:统一候选池 + 四动作(store / skip / update / merge),支持跨 type 与多对多合并,时间戳取并集。
  • L2:操作手册式 Prompt —— 阶段 0 容量三档预警、UPDATE 优先、CREATE 限流、删除重定义为 [DELETED] 写操作。
  • L3:四层深扫(基础锚点 / 兴趣图谱 / 交互协议 / 认知内核),每层标注"实用价值",产物限制 2000 字(code 版 1200 字)。
  • 工具是双轨的:schema 走 API 字段(硬),Prompt 只做"有哪些工具、何时用、参数语义"的软约定,不重复 schema。
  • Prompt 永远被当作不可信输入:JSON 修复、降级 store、剥离导航、XML 转义、软删除清理、备份回滚、空产出拒绝。
  • 可运营:自定义策略按 agent > team > instance 解析并追加注入,SYSTEM_CUSTOM_STRATEGY_GUARD 锁死协议、工具、沙箱与配额。

图示索引

源码索引

内容文件
L1 抽取 PromptMemoryCore/src/core/prompts/l1-extraction.ts
L1 去重 PromptMemoryCore/src/core/prompts/l1-dedup.ts
L2 场景 PromptMemoryCore/src/core/prompts/scene-extraction.ts
L3 画像 PromptMemoryCore/src/core/prompts/persona-generation.ts
Prompt 组装与调用MemoryCore/src/core/record/l1-extractor.tsscene/scene-extractor.tspersona/persona-generator.ts
工具白名单与沙箱(宿主)MemoryCore/src/utils/clean-context-runner.ts
工具 schema 与循环(独立)MemoryCore/src/adapters/standalone/llm-runner.tsstorage-tools.ts
chat / code 模式解析MemoryCore/src/config.ts
自定义 Prompt 解析与注入MemoryCore/src/core/memory-prompt/resolver.tscomposer.tstypes.ts
JSON 容错MemoryCore/src/utils/sanitize.ts

Prompt 工程工具调用输出协议LLM 容错

← 返回AI 笔记