dsh-tdai-memory 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +56 -0
- package/THIRD-PARTY-NOTICES.md +20 -0
- package/index.js +328 -0
- package/package.json +53 -0
- package/recall-inject.js +80 -0
- package/vendor/tdai/adapters/index.js +15 -0
- package/vendor/tdai/adapters/openclaw/host-adapter.js +83 -0
- package/vendor/tdai/adapters/openclaw/index.js +5 -0
- package/vendor/tdai/adapters/openclaw/llm-runner.js +69 -0
- package/vendor/tdai/adapters/standalone/host-adapter.js +58 -0
- package/vendor/tdai/adapters/standalone/index.js +5 -0
- package/vendor/tdai/adapters/standalone/llm-runner.js +246 -0
- package/vendor/tdai/config.js +363 -0
- package/vendor/tdai/core/conversation/l0-recorder.js +420 -0
- package/vendor/tdai/core/hooks/auto-capture.js +236 -0
- package/vendor/tdai/core/hooks/auto-recall.js +653 -0
- package/vendor/tdai/core/index.js +8 -0
- package/vendor/tdai/core/persona/persona-generator.js +184 -0
- package/vendor/tdai/core/persona/persona-trigger.js +94 -0
- package/vendor/tdai/core/profile/profile-sync.js +200 -0
- package/vendor/tdai/core/prompts/l1-dedup.js +136 -0
- package/vendor/tdai/core/prompts/l1-extraction.js +132 -0
- package/vendor/tdai/core/prompts/persona-generation.js +160 -0
- package/vendor/tdai/core/prompts/scene-extraction.js +269 -0
- package/vendor/tdai/core/record/l1-dedup.js +318 -0
- package/vendor/tdai/core/record/l1-extractor.js +379 -0
- package/vendor/tdai/core/record/l1-reader.js +178 -0
- package/vendor/tdai/core/record/l1-writer.js +141 -0
- package/vendor/tdai/core/report/reporter.js +86 -0
- package/vendor/tdai/core/scene/filename-normalizer.js +161 -0
- package/vendor/tdai/core/scene/scene-extractor.js +410 -0
- package/vendor/tdai/core/scene/scene-format.js +53 -0
- package/vendor/tdai/core/scene/scene-index.js +82 -0
- package/vendor/tdai/core/scene/scene-navigation.js +60 -0
- package/vendor/tdai/core/seed/input.js +404 -0
- package/vendor/tdai/core/seed/seed-runtime.js +314 -0
- package/vendor/tdai/core/seed/types.js +9 -0
- package/vendor/tdai/core/store/bm25-client.js +117 -0
- package/vendor/tdai/core/store/bm25-local.js +68 -0
- package/vendor/tdai/core/store/embedding.js +632 -0
- package/vendor/tdai/core/store/factory.js +95 -0
- package/vendor/tdai/core/store/search-utils.js +54 -0
- package/vendor/tdai/core/store/sqlite.js +1822 -0
- package/vendor/tdai/core/store/tcvdb-client.js +221 -0
- package/vendor/tdai/core/store/tcvdb.js +1047 -0
- package/vendor/tdai/core/store/types.js +17 -0
- package/vendor/tdai/core/tdai-core.js +446 -0
- package/vendor/tdai/core/tools/conversation-search.js +199 -0
- package/vendor/tdai/core/tools/memory-search.js +207 -0
- package/vendor/tdai/core/types.js +12 -0
- package/vendor/tdai/utils/backup.js +189 -0
- package/vendor/tdai/utils/checkpoint.js +356 -0
- package/vendor/tdai/utils/clean-context-runner.js +472 -0
- package/vendor/tdai/utils/ensure-hook-policy.js +213 -0
- package/vendor/tdai/utils/env.js +13 -0
- package/vendor/tdai/utils/managed-timer.js +127 -0
- package/vendor/tdai/utils/manifest.js +91 -0
- package/vendor/tdai/utils/memory-cleaner.js +300 -0
- package/vendor/tdai/utils/no-think-fetch.js +109 -0
- package/vendor/tdai/utils/openclaw-state-dir.js +26 -0
- package/vendor/tdai/utils/pipeline-factory.js +517 -0
- package/vendor/tdai/utils/pipeline-manager.js +892 -0
- package/vendor/tdai/utils/sanitize.js +360 -0
- package/vendor/tdai/utils/serial-queue.js +103 -0
- package/vendor/tdai/utils/session-filter.js +88 -0
- package/vendor/tdai/utils/text-utils.js +29 -0
- package/vendor/tdai/utils/time.js +252 -0
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* L1 Extraction Prompt: 情境切分 + 记忆提取
|
|
3
|
+
*
|
|
4
|
+
* Based on Kenty's validated prototype prompt (l1_memory_extraction_prompt.md).
|
|
5
|
+
* System prompt handles scene segmentation + memory extraction in a single LLM call.
|
|
6
|
+
* User prompt template fills in previous_scene_name, background_messages, new_messages.
|
|
7
|
+
*/
|
|
8
|
+
import { formatForLLM, describeTimeZoneForPrompt } from "../../utils/time.js";
|
|
9
|
+
// ============================
|
|
10
|
+
// System Prompt
|
|
11
|
+
// ============================
|
|
12
|
+
export const EXTRACT_MEMORIES_SYSTEM_PROMPT = `你是专业的"情境切分与记忆提取专家"。
|
|
13
|
+
你的任务是分析用户的对话,判断情境切换,并从中提取结构化的核心记忆(仅限 persona, episodic, instruction 三类)。
|
|
14
|
+
|
|
15
|
+
**输出语言**:所有自由文本字段(\`scene_name\`、memory \`content\`)使用与用户消息相同的语言;JSON 字段名、枚举值、ISO 时间戳保持英文。
|
|
16
|
+
|
|
17
|
+
### 任务一:情境切分(Scene Segmentation)
|
|
18
|
+
分析【待提取的新消息】,结合【上一个情境】,判断并输出当前对话的情境。
|
|
19
|
+
- 继承:无明显切换,沿用上一个情境。
|
|
20
|
+
- 切换条件:用户发出明确指令(如"换话题")、意图转变、或提出独立新目标。
|
|
21
|
+
- 一段对话可能只有一个情境,也可能有多个情境(话题多次切换时)。
|
|
22
|
+
- 命名规则:"我(AI)在和xxx(用户身份)做xxx(目标活动)"(**使用上述输出语言**,约 30-50 个字符或等价长度,单句,全局唯一)。
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
### 任务二:核心记忆提取(Memory Extraction)
|
|
27
|
+
结合背景和当前情境,仅从【待提取的新消息】中提取核心信息。
|
|
28
|
+
|
|
29
|
+
【通用提取原则】
|
|
30
|
+
1. 宁缺毋滥:过滤琐碎闲聊、临时性指令和一次性操作(如"这次、本单");剔除不可靠的边缘信息。
|
|
31
|
+
2. 独立完整:记忆必须"跳出当前对话依然成立",无上下文也能看懂。提取主体必须以"用户(姓名)"或"AI"为核心。
|
|
32
|
+
3. 归纳合并:强关联或因果关系的多条消息,必须合并为一条完整记忆,不可碎片化。
|
|
33
|
+
|
|
34
|
+
【支持提取的三大类型】(必须严格遵守类型规则)
|
|
35
|
+
> 下面给出的"提取句式"和"触发词"仅作为中文骨架参考;**实际 \`content\` 必须按上述输出语言书写**(例如英文用户 → "The user (Maya) is a senior product manager based in Berlin")。
|
|
36
|
+
|
|
37
|
+
1. 个性化记忆 (type: "persona")
|
|
38
|
+
- 定义:用户的稳定属性、偏好、技能、价值观、习惯(如住所、职业、饮食禁忌)。
|
|
39
|
+
- 提取句式:"用户([姓名])喜欢/是/擅长..."
|
|
40
|
+
- 打分 (priority):80-100(健康/禁忌/核心特质);50-70(一般喜好/技能);<50(模糊次要,可丢弃)。
|
|
41
|
+
- 触发词:喜欢、习惯、经常、我这个人...
|
|
42
|
+
|
|
43
|
+
2. 客观事件记忆 (type: "episodic")
|
|
44
|
+
- 定义:客观发生的动作、决定、计划或达成结果。绝不包含纯主观感受。
|
|
45
|
+
- 提取句式:"用户([姓名])在 [最好是精确绝对时间] 于 [地点] [做了某事(可以包含起因、经过、结果)]"。
|
|
46
|
+
- 时间约束:尽量基于消息的 timestamp 推算绝对时间,如能确定则在 metadata 中输出 activity_start_time 和 activity_end_time(ISO 8601格式)。无法确定时可省略。
|
|
47
|
+
- 打分 (priority):80-100(重要事件/计划);60-70(一般完整活动);<60(琐碎事项,直接丢弃)。
|
|
48
|
+
|
|
49
|
+
3. 全局指令记忆 (type: "instruction")
|
|
50
|
+
- 定义:用户对 AI 提出的长期行为规则、格式偏好、语气控制。
|
|
51
|
+
- 提取句式:"用户要求/希望 AI 以后回答时..."
|
|
52
|
+
- 触发词:以后都、从现在开始、记住、必须。
|
|
53
|
+
- 打分 (priority):-1(极其严格的全局死命令);90-100(核心行为规则);70-80(重要要求);<70(临时要求,直接丢弃)。
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
### 不应该提取的内容
|
|
58
|
+
- 琐碎闲聊、问候;临时性的纯工具性请求(如"这次帮我翻译一下")
|
|
59
|
+
- 一次性操作指令(如"这次、本单"相关)
|
|
60
|
+
- 重复的内容;AI助手自身的行为或输出
|
|
61
|
+
- 不属于以上3类的信息
|
|
62
|
+
- 纯主观感受(不带客观事件的情绪表达)
|
|
63
|
+
|
|
64
|
+
---
|
|
65
|
+
|
|
66
|
+
### 任务三:输出格式规范(JSON)
|
|
67
|
+
返回且仅返回一个合法的 JSON 数组。数组的每一项是一个情境,包含该情境的消息范围和抽取到的记忆:
|
|
68
|
+
|
|
69
|
+
[
|
|
70
|
+
{
|
|
71
|
+
"scene_name": "当前生成或继承的情境名称",
|
|
72
|
+
"message_ids": ["属于该情境的消息ID列表"],
|
|
73
|
+
"memories": [
|
|
74
|
+
{
|
|
75
|
+
"content": "完整、独立的记忆陈述(按对应类型的句式要求)",
|
|
76
|
+
"type": "persona|episodic|instruction",
|
|
77
|
+
"priority": 80,
|
|
78
|
+
"source_message_ids": ["消息ID_1", "消息ID_2"],
|
|
79
|
+
"metadata": {}
|
|
80
|
+
}
|
|
81
|
+
]
|
|
82
|
+
}
|
|
83
|
+
]
|
|
84
|
+
|
|
85
|
+
metadata 字段说明:
|
|
86
|
+
- episodic 类型:如能确定活动时间,填入 {"activity_start_time": "ISO8601", "activity_end_time": "ISO8601"}
|
|
87
|
+
- 其他类型或无法确定时间:输出空对象 {}
|
|
88
|
+
|
|
89
|
+
如果整段对话无有意义的记忆,也要输出情境分割结果,memories 为空数组:
|
|
90
|
+
[
|
|
91
|
+
{
|
|
92
|
+
"scene_name": "情境名称",
|
|
93
|
+
"message_ids": ["id1", "id2"],
|
|
94
|
+
"memories": []
|
|
95
|
+
}
|
|
96
|
+
]
|
|
97
|
+
|
|
98
|
+
请严格按上述 JSON 数组格式输出,不要输出任何额外的 Markdown 代码块修饰符(如 \`\`\`json)或解释文本。`;
|
|
99
|
+
// ============================
|
|
100
|
+
// Prompt Builder
|
|
101
|
+
// ============================
|
|
102
|
+
/**
|
|
103
|
+
* Format the user prompt for L1 extraction.
|
|
104
|
+
*
|
|
105
|
+
* @param newMessages - Messages to extract memories from (with ids and timestamps)
|
|
106
|
+
* @param backgroundMessages - Previous messages for context only (not for extraction)
|
|
107
|
+
* @param previousSceneName - The last known scene name (for continuity)
|
|
108
|
+
*/
|
|
109
|
+
export function formatExtractionPrompt(params) {
|
|
110
|
+
const { newMessages, backgroundMessages = [], previousSceneName = "无" } = params;
|
|
111
|
+
const bgText = backgroundMessages.length > 0
|
|
112
|
+
? backgroundMessages
|
|
113
|
+
.map((m) => `[${m.id}] [${m.role}] [${formatForLLM(m.timestamp)}]: ${m.content}`)
|
|
114
|
+
.join("\n\n")
|
|
115
|
+
: "无";
|
|
116
|
+
const newText = newMessages
|
|
117
|
+
.map((m) => `[${m.id}] [${m.role}] [${formatForLLM(m.timestamp)}]: ${m.content}`)
|
|
118
|
+
.join("\n\n");
|
|
119
|
+
return `**${describeTimeZoneForPrompt()}**
|
|
120
|
+
|
|
121
|
+
**输出语言**:根据下方"待提取的新消息"中 user 发言的主导语言书写 \`scene_name\` 和 memory \`content\`。
|
|
122
|
+
|
|
123
|
+
【上一个情境】:${previousSceneName}
|
|
124
|
+
|
|
125
|
+
【背景对话】(仅供理解上下文推断关系/时间,严禁从中提取记忆):
|
|
126
|
+
${bgText}
|
|
127
|
+
|
|
128
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
129
|
+
|
|
130
|
+
【待提取的新消息】(务必结合 timestamp 推算时间,只从这里提取记忆!):
|
|
131
|
+
${newText}`;
|
|
132
|
+
}
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Persona Generation Prompt — instructs LLM to generate/update user persona
|
|
3
|
+
* using the four-layer deep scan model.
|
|
4
|
+
*
|
|
5
|
+
* v3: Split into systemPrompt (role + constraints + logic + template) and
|
|
6
|
+
* userPrompt (data). Tool names aligned to OpenClaw actual API (write/edit).
|
|
7
|
+
*/
|
|
8
|
+
// ============================
|
|
9
|
+
// System Prompt (stable: role + constraints + logic + template)
|
|
10
|
+
// ============================
|
|
11
|
+
const PERSONA_SYSTEM_PROMPT = `# 🧬 Persona Architect - Incremental Evolution Protocol
|
|
12
|
+
|
|
13
|
+
**Output language contract**:
|
|
14
|
+
- Detect the dominant language from the changed scene content.
|
|
15
|
+
- \`persona.md\` natural-language content, profile headings, and narrative sections must use that language.
|
|
16
|
+
- For English scene content, output English persona headings and English body text.
|
|
17
|
+
- For non-Chinese scene content, do not emit Chinese persona headings.
|
|
18
|
+
- If the language is ambiguous, default to English.
|
|
19
|
+
- Keep Markdown syntax, file name \`persona.md\`, tool names, and structural markers in English.
|
|
20
|
+
|
|
21
|
+
请你结合已有的 persona.md 和新增/变化的 block 信息深度分析,然后使用文件工具将结果写入 \`persona.md\` 文件。
|
|
22
|
+
|
|
23
|
+
## ⛔ 文件操作约束(必须严格遵守)
|
|
24
|
+
|
|
25
|
+
1. **必须使用文件工具将最终 persona 内容写入 \`persona.md\`**。当前工作目录已设为数据目录,直接使用文件名 \`persona.md\`。
|
|
26
|
+
- **首次生成 / 大幅重写**:使用 **write** 工具整体写入。参数:\`path\`=\`persona.md\`, \`content\`=完整内容
|
|
27
|
+
- **增量更新(局部修改)**:使用 **edit** 工具精确替换。参数:\`path\`=\`persona.md\`, \`edits\`=[{\`oldText\`: 旧内容片段, \`newText\`: 新内容片段}]
|
|
28
|
+
2. **只能操作 \`persona.md\` 这一个文件**,禁止读取或写入任何其他文件(包括 scene_blocks/、.metadata/ 等)。
|
|
29
|
+
3. **写入的内容必须只包含最终的 persona 文档**,不要包含你的思考过程、分析步骤或任何非 persona 内容。
|
|
30
|
+
4. **无需 read 工具**:当前 persona.md 的完整内容已在用户消息中提供,直接基于它进行更新即可。
|
|
31
|
+
|
|
32
|
+
### 🚫 严格禁止
|
|
33
|
+
- **禁止过长**:persona.md 内容总长度不要超过 2000 字符,及时做总结和删除不重要的信息。
|
|
34
|
+
- **禁止过度推测**:没提到的信息不要过度臆想导致产生幻觉,特别是在冷启动阶段,要保持克制,如果没有相关信息完全可以不填!
|
|
35
|
+
- **禁止使用非场景来源的信息**:Persona 的所有内容必须且只能来自下方提供的场景数据。不要从 workspace 目录结构、文件路径、系统信息等技术元数据中提取任何关于用户的个人信息。
|
|
36
|
+
- **禁止操作 persona.md 以外的任何文件**。
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
## ⚙️ 核心运作逻辑 (The Core Logic)
|
|
41
|
+
|
|
42
|
+
🧠 核心思维引擎:连接与综合 (Connect & Synthesize)
|
|
43
|
+
请遵循 "叙事连贯性" 原则处理信息。禁止简单的罗列(No Bullet-point Spamming)。
|
|
44
|
+
|
|
45
|
+
1. 寻找"贯穿线" (The Connecting Thread)
|
|
46
|
+
不要孤立地看信息。要寻找不同领域行为背后的共同逻辑。
|
|
47
|
+
** 要保持精简,不过度猜想,如果不确定可以不写 **
|
|
48
|
+
|
|
49
|
+
执行以下**四层深度扫描**:
|
|
50
|
+
|
|
51
|
+
### 🟢 Layer 1: 基础锚点 (The Base & Facts) -> 【建立连接】
|
|
52
|
+
* **扫描目标**: 确凿的事实、人口统计学特征、当前状态。
|
|
53
|
+
* **实用价值**: 为 Agent 提供**破冰话题**和**上下文感知**。
|
|
54
|
+
|
|
55
|
+
### 🔵 Layer 2: 兴趣图谱 (The Interest Graph) -> 【提供谈资】
|
|
56
|
+
* **扫描目标**: 用户投入时间、金钱或注意力的事物。
|
|
57
|
+
* **提取原则**: **区分活跃度**(活跃爱好 / 被动消费 / 休眠兴趣)。
|
|
58
|
+
* **实用价值**: 让 Agent 能够进行**高质量的闲聊 (Chit-chat)** 和 **生活推荐**。
|
|
59
|
+
|
|
60
|
+
### 🟡 Layer 3: 交互协议 (The Interface) -> 【消除摩擦】
|
|
61
|
+
* **扫描目标**: 用户的沟通习惯、雷区、工作流偏好。
|
|
62
|
+
* **实用价值**: 指导 Agent **如何说话、如何交付结果**,避免踩雷。
|
|
63
|
+
|
|
64
|
+
### 🔴 Layer 4: 认知内核 (The Core) -> 【深度共鸣】
|
|
65
|
+
* **扫描目标**: 决策逻辑、矛盾点、终极驱动力。
|
|
66
|
+
* **实用价值**: 让 Agent 成为**能够替用户做决策**的"副驾驶"。
|
|
67
|
+
|
|
68
|
+
---
|
|
69
|
+
|
|
70
|
+
## 📝 输出模板 (The Persona Template)
|
|
71
|
+
|
|
72
|
+
请参考以下格式,使用 **write** 工具写入最终内容。可以做自主调整(信息不足时可以减少或新增 chapter)(**必须保持 Markdown 格式**):
|
|
73
|
+
|
|
74
|
+
\`\`\`\`markdown
|
|
75
|
+
# User Narrative Profile
|
|
76
|
+
|
|
77
|
+
> **Archetype**: [Define the user's core narrative archetype in one sentence.]
|
|
78
|
+
|
|
79
|
+
> **Basic Information**
|
|
80
|
+
(Basic user facts such as age, gender, occupation, or location. Overwrite only when a conflict is resolved; otherwise merge compatible facts.)
|
|
81
|
+
-
|
|
82
|
+
-
|
|
83
|
+
|
|
84
|
+
> **Long-term Preferences**
|
|
85
|
+
(The user's most stable and reusable preferences observed from scene evidence.)
|
|
86
|
+
-
|
|
87
|
+
-
|
|
88
|
+
|
|
89
|
+
## 📖 Chapter 1: Context & Current State
|
|
90
|
+
*(Merge basic facts and current state into a coherent background.)*
|
|
91
|
+
|
|
92
|
+
**[Write a coherent description. Use short bullets only when the facts are clearly distinct.]**
|
|
93
|
+
|
|
94
|
+
## 🎨 Chapter 2: The Texture of Life
|
|
95
|
+
*(Connect interests, consumption patterns, and daily habits to show the user's lived texture.)*
|
|
96
|
+
|
|
97
|
+
**[Write a coherent description, focusing on the unity of interests, preferences, and taste. Use short bullets only when needed.]**
|
|
98
|
+
|
|
99
|
+
## 🤖 Chapter 3: Interaction & Cognitive Protocol
|
|
100
|
+
*(This is the Main Agent's action guide. Keep it semi-structured for utility, but explain why each guidance point matters.)*
|
|
101
|
+
|
|
102
|
+
### 3.1 How to Speak
|
|
103
|
+
### 3.2 How to Think
|
|
104
|
+
|
|
105
|
+
## 🧩 Chapter 4: Deep Insights & Evolution
|
|
106
|
+
*(Anthropological observation notes.)*
|
|
107
|
+
|
|
108
|
+
* **Productive Contradictions**: [Describe traits that seem conflicting but are coherent in context.]
|
|
109
|
+
* **Evolution Trajectory**: [Optionally include dated points describing recent meaningful changes.]
|
|
110
|
+
* **Emergent Traits**: Extract 3-7 core trait tags, one per line, each with a short note.
|
|
111
|
+
- \`TagName\` - Short note
|
|
112
|
+
\`\`\`\`
|
|
113
|
+
|
|
114
|
+
---
|
|
115
|
+
|
|
116
|
+
### ⚠️ 成功标准
|
|
117
|
+
- ✅ **必须使用 write 或 edit 工具写入最终结果到 \`persona.md\`**
|
|
118
|
+
- ✅ 基于场景证据生成深度洞察
|
|
119
|
+
- ✅ 内容到 Chapter 4 结束(不包含场景导航,工程会自动追加)
|
|
120
|
+
- ✅ 必须严格按照上面的模板格式
|
|
121
|
+
- ✅ 不要添加场景导航(工程会自动追加)
|
|
122
|
+
- ✅ 只操作 persona.md,不要操作其他文件`;
|
|
123
|
+
// ============================
|
|
124
|
+
// User Prompt builder (dynamic data)
|
|
125
|
+
// ============================
|
|
126
|
+
export function buildPersonaPrompt(params) {
|
|
127
|
+
const { mode, currentTime, totalProcessed, sceneCount, changedSceneCount, changedScenesContent, existingPersona, triggerInfo, } = params;
|
|
128
|
+
const modeLabel = mode === "first" ? "🆕 首次生成" : "🔄 迭代更新";
|
|
129
|
+
const triggerSection = triggerInfo
|
|
130
|
+
? `\n### 触发信息\n${triggerInfo}\n`
|
|
131
|
+
: "";
|
|
132
|
+
const existingPersonaSection = existingPersona
|
|
133
|
+
? `\n## 📄 当前 Persona(工程已预加载)\n\n` +
|
|
134
|
+
`*以下是现有 persona.md 的完整内容(${existingPersona.length} 字符),基于此更新后请控制在2000字内:*\n\n` +
|
|
135
|
+
`\`\`\`markdown\n${existingPersona}\n\`\`\`\n\n---\n`
|
|
136
|
+
: "";
|
|
137
|
+
const iterationGuide = mode === "incremental"
|
|
138
|
+
? `\n## 🔄 迭代决策指南\n\n` +
|
|
139
|
+
`面对变化场景,自主判断处理方式:强化(佐证已有洞察)/ 补充(新维度)/ 修正(矛盾)/ 重构(结构调整)/ 不改(无有用新增内容)。\n`
|
|
140
|
+
: "";
|
|
141
|
+
const userPrompt = `**Output language**: \`persona.md\` headings and body text must use the dominant language of the changed scene content below. For English scene content, use English persona headings.
|
|
142
|
+
|
|
143
|
+
**⏰ 更新时间**: ${currentTime}
|
|
144
|
+
**模式**: ${modeLabel}
|
|
145
|
+
${triggerSection}
|
|
146
|
+
## 📊 统计
|
|
147
|
+
- **总记忆数**: ${totalProcessed} 条
|
|
148
|
+
- **场景总数**: ${sceneCount} 个
|
|
149
|
+
- **变化场景**: ${changedSceneCount} 个(自上次更新后)
|
|
150
|
+
|
|
151
|
+
---
|
|
152
|
+
${changedScenesContent}
|
|
153
|
+
|
|
154
|
+
${existingPersonaSection}
|
|
155
|
+
${iterationGuide}`;
|
|
156
|
+
return {
|
|
157
|
+
systemPrompt: PERSONA_SYSTEM_PROMPT,
|
|
158
|
+
userPrompt,
|
|
159
|
+
};
|
|
160
|
+
}
|
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Scene Extraction Prompt — instructs LLM to consolidate memories into scene blocks
|
|
3
|
+
* using file tools (read, write, edit).
|
|
4
|
+
*
|
|
5
|
+
* v2: Split into systemPrompt (role + constraints + workflow + output spec) and
|
|
6
|
+
* userPrompt (dynamic data). Tool names aligned to OpenClaw actual API.
|
|
7
|
+
*
|
|
8
|
+
* Scene files can be updated via:
|
|
9
|
+
* - read + write (full rewrite) for large structural changes
|
|
10
|
+
* - edit (targeted partial updates, e.g. updating a single section)
|
|
11
|
+
*
|
|
12
|
+
* Security: The LLM is sandboxed to scene_blocks/ only (workspaceDir = scene_blocks/).
|
|
13
|
+
* It has NO visibility into checkpoint, scene_index, persona.md, or any other system file.
|
|
14
|
+
* File deletion is achieved via "soft-delete" — writing the marker `[DELETED]` to the file
|
|
15
|
+
* — and the SceneExtractor subsequently removes soft-deleted files with fs.unlink.
|
|
16
|
+
* Note: writing an empty/whitespace-only string is rejected by the core write tool's
|
|
17
|
+
* parameter validation, so we use a non-empty marker instead.
|
|
18
|
+
*
|
|
19
|
+
* Persona update requests are communicated via text output signals (out-of-band),
|
|
20
|
+
* parsed by the engineering side after LLM execution completes.
|
|
21
|
+
*/
|
|
22
|
+
// ============================
|
|
23
|
+
// System Prompt builder (role + constraints + workflow + output spec)
|
|
24
|
+
// Contains maxScenes as a constraint parameter.
|
|
25
|
+
// ============================
|
|
26
|
+
function buildSceneSystemPrompt(maxScenes) {
|
|
27
|
+
return `# Memory Consolidation Architect
|
|
28
|
+
|
|
29
|
+
**Output language contract**:
|
|
30
|
+
- Detect the dominant language from "New Memories List".
|
|
31
|
+
- Scene file names, Markdown section headings, and natural-language body text must use that language.
|
|
32
|
+
- For English memories, output English file names and English section headings.
|
|
33
|
+
- For non-Chinese memories, do not emit Chinese file names or Chinese section headings.
|
|
34
|
+
- If the language is ambiguous, default to English.
|
|
35
|
+
- Keep META field names (\`created\`, \`updated\`, \`summary\`, \`heat\`) and system markers such as \`[DELETED]\` in English.
|
|
36
|
+
|
|
37
|
+
## 角色定义 (Role Definition)
|
|
38
|
+
你是记忆整合架构师。你的目标是为用户构建一个"数字第二大脑"。你不仅仅是在记录数据,你更像是一位人类学家和心理学家,负责分析原始记忆,从中提取核心特征、捕捉隐性信号,并构建不断演变的叙事。
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
## 架构模型
|
|
42
|
+
|
|
43
|
+
### Layer 1 (Input): Raw Memories
|
|
44
|
+
- **来源**:API 分批召回(每批 20 条)
|
|
45
|
+
- **状态**:碎片化、无序
|
|
46
|
+
|
|
47
|
+
### Layer 2 (Processing): Scene Diaries
|
|
48
|
+
- **形态**:**不是清单,是连贯的叙事文档**
|
|
49
|
+
- **逻辑**:将 L1 碎片融合进特定场景文件
|
|
50
|
+
- **动作**:Create(创建)、Integrate(整合)、Rewrite(重写)
|
|
51
|
+
- **禁止**:简单追加列表
|
|
52
|
+
|
|
53
|
+
你主要负责L1到L2的生成任务
|
|
54
|
+
|
|
55
|
+
## 输入环境 (Input Context)
|
|
56
|
+
你将接收三个输入:
|
|
57
|
+
1. 新增记忆 (New Memory): 一段原始的、非结构化的新近回忆信息。
|
|
58
|
+
2. 现有 Block 映射表 (Existing Blocks Map): 包含当前所有记忆块(Markdown 文件)的文件名和摘要的列表。
|
|
59
|
+
3. 当前时间 (Current Time): 用于生成元数据的具体时间戳。
|
|
60
|
+
|
|
61
|
+
**⚠️ 场景文件数量上限:${maxScenes} 个。处理完成后目录中的场景文件数量必须严格小于此上限。**
|
|
62
|
+
|
|
63
|
+
## ⛔ 文件操作约束(必须严格遵守)
|
|
64
|
+
1. **所有文件操作使用相对文件名**(如 \`Engineering-Practice.md\`),当前工作目录已设为场景文件目录
|
|
65
|
+
2. **read 只能读取用户消息中"已有场景文件清单"列出的文件**,禁止猜测或编造不在清单中的文件名
|
|
66
|
+
3. **创建新场景文件时**,使用 **write** 工具。参数:\`path\`=文件名, \`content\`=完整内容
|
|
67
|
+
4. **局部更新场景文件**:使用 **edit** 工具。参数:\`path\`=文件名, \`edits\`=[{\`oldText\`: 旧内容, \`newText\`: 新内容}]。对于大范围重写或结构性变更,建议使用 **read** + **write** 整体重写。
|
|
68
|
+
5. **场景索引和系统配置由工程系统自动维护**,你只需专注于操作 \`.md\` 场景文件
|
|
69
|
+
6. **删除文件的唯一方式**:使用 **write** 工具将文件内容写为 \`[DELETED]\` 标记(\`path\`=文件名, \`content\`=\`[DELETED]\`)。系统会自动清理带有此标记的文件。**禁止**写入空字符串(会被系统拒绝)。**禁止**用 \`[ARCHIVE]\`、\`[CONSOLIDATED]\` 等其他标记替代删除——只有 \`[DELETED]\` 标记会触发系统清理。
|
|
70
|
+
7. **禁止创建报告/整合/汇总类文件**。你的输出必须是有意义的场景叙事文件(如"技术架构与工程实践.md"、"日常生活与工作节奏.md")。禁止创建以 BATCH、REPORT、CONSOLIDATION、INTEGRATION、ARCHIVE、SUMMARY 等为前缀的文件。
|
|
71
|
+
|
|
72
|
+
## 📛 文件命名规范(强制)
|
|
73
|
+
|
|
74
|
+
为保证下游工具(场景导航、健康检查、对象存储同步等)能正确解析路径引用,**新建文件**或 **MERGE 后的目标文件**必须遵守以下命名规则:
|
|
75
|
+
|
|
76
|
+
- **允许字符**:Unicode letters(包括 Latin/CJK/Cyrillic 等)、数字、短横线 \`-\`、下划线 \`_\`、点号 \`.\`
|
|
77
|
+
- **必须以 \`.md\` 结尾**(小写)
|
|
78
|
+
- **❌ 禁止包含**:空格、全角空格、引号、括号 \`( ) [ ] { }\`、斜杠 \`/ \\\`、冒号 \`:\`、分号 \`;\`、问号 \`?\`、感叹号 \`!\`、星号 \`*\`、竖线 \`|\`、其他标点
|
|
79
|
+
- **多词分隔**:使用 \`-\`(短横线)连接,不要用空格
|
|
80
|
+
- **更新现有文件**时,沿用清单中给出的文件名,不要改名
|
|
81
|
+
- **英文记忆的新建文件名**必须使用英文标题,并用短横线连接单词
|
|
82
|
+
|
|
83
|
+
✅ 正确示例:
|
|
84
|
+
- \`Daily-Rhythm-in-Shanghai.md\`
|
|
85
|
+
- \`日常生活-健康管理.md\`
|
|
86
|
+
- \`技术研究-Rust学习.md\`
|
|
87
|
+
- \`Coffee-Yirgacheffe.md\`
|
|
88
|
+
- \`Work-and-Engineering-Practice.md\`
|
|
89
|
+
|
|
90
|
+
❌ 错误示例(每次都会触发工程兜底重命名):
|
|
91
|
+
- \`Daily Rhythm in Shanghai.md\`(含空格)
|
|
92
|
+
- \`Coffee (Yirgacheffe).md\`(含括号)
|
|
93
|
+
- \`Q1 Milestone?.md\`(含空格和问号)
|
|
94
|
+
|
|
95
|
+
> 提示:即使你没遵守,工程系统会自动归一化文件名(空格替换为短横线、删除括号等),但这会增加日志噪音和潜在冲突。请在 \`write\` 时直接使用合规名字。
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
## 工作流与逻辑 (Workflow & Logic)
|
|
99
|
+
在生成输出之前,你必须执行以下"思维链"过程:
|
|
100
|
+
|
|
101
|
+
### ⚠️ 阶段 0:强制检查场景总数(必须先执行)
|
|
102
|
+
|
|
103
|
+
**在处理任何记忆之前,你必须:**
|
|
104
|
+
|
|
105
|
+
1. **统计当前场景总数**:查看 "Existing Scene Blocks Summary" 顶部标注的当前场景总数
|
|
106
|
+
2. **最终目标**:处理完成后,目录中的场景文件数量必须 **严格小于 ${maxScenes}**
|
|
107
|
+
3. **遵守分级预警**:
|
|
108
|
+
- 红色预警(≥ ${maxScenes}):**必须先通过 MERGE 减少文件数量**,将最相似的 2-4 个场景合并为 1 个,**并删除被合并的旧文件**,直到文件数 < ${maxScenes} 后,再处理新记忆
|
|
109
|
+
- 橙色预警(= ${maxScenes - 1}):**只能 UPDATE 现有场景,不能 CREATE 新场景**
|
|
110
|
+
- 黄色预警(接近 ${maxScenes}):**优先 UPDATE 或主动 MERGE 相似场景**
|
|
111
|
+
|
|
112
|
+
**合并优先级**(当需要合并时,按以下顺序选择):
|
|
113
|
+
1. **主题高度重叠**:如"Python后端开发"和"Go后端开发" → 合并为"后端开发技术栈"
|
|
114
|
+
2. **叙事弧线相同**:如"求职材料-JD匹配"和"职业发展-能力对齐" → 合并为"职业发展与求职"
|
|
115
|
+
3. **热度最低的场景**:如果没有明显重叠,合并或删除 heat 最低的 2-3 个场景
|
|
116
|
+
|
|
117
|
+
### 阶段 1:分析与分类
|
|
118
|
+
分析 新增记忆。它的核心领域是什么?(例如:编程风格、情绪状态、职业轨迹、人际关系)。
|
|
119
|
+
提取事实事件链(触发 -> 行动 -> 结果)以及底层的心理状态。
|
|
120
|
+
|
|
121
|
+
### 阶段 2:检索与策略选择
|
|
122
|
+
将新记忆与 现有 Block 映射表 进行比对。
|
|
123
|
+
需要时使用 **read** 工具读取完整场景文件内容
|
|
124
|
+
**只能读取用户消息中"已有场景文件清单"列出的文件,禁止猜测其他文件路径。**
|
|
125
|
+
|
|
126
|
+
**核心原则:默认策略是 UPDATE,不是 CREATE。** 当犹豫于 UPDATE 和 CREATE 之间时,选择 UPDATE。
|
|
127
|
+
|
|
128
|
+
策略选择(按优先级排序):
|
|
129
|
+
1. **UPDATE(更新)**【首选策略】: 如果存在相关的 Block(基于摘要或文件名的相似性),先用 **read** 读取文件内的具体信息,再锁定该 Block 进行更新(**write** 整体重写 或 **edit** 局部替换)
|
|
130
|
+
2. **MERGE(合并)**:
|
|
131
|
+
- 合并的新 block 应该是生成概括性更强的场景,包含已有的多个相似场景
|
|
132
|
+
- **强制合并**:当前 Block 总数 **≥ ${maxScenes}** 时,必须先将多个相似记忆合并
|
|
133
|
+
- **主动合并**:即使未达上限,如果两个 Block 属于同一叙事弧线,也应合并以增加深度
|
|
134
|
+
- **⚠️ 合并后必须删除旧文件**:被合并的旧场景文件必须通过 **write** 写入 \`[DELETED]\` 标记。**仅仅打标记(如 [ARCHIVE]、[CONSOLIDATED])不算删除,文件仍会占用配额。**
|
|
135
|
+
3. **CREATE(新建)**【最后手段】:
|
|
136
|
+
- **前提条件**:当前场景总数 < ${maxScenes}
|
|
137
|
+
- **CREATE 前的强制验证**:必须先用 **read** 检查至少 2 个最相似的现有场景,确认新记忆确实无法融入后才能 CREATE。跳过验证直接 CREATE 是被禁止的
|
|
138
|
+
- 如果话题是全新的且与现有内容区分度高,可以创建新 Block
|
|
139
|
+
- **每次批处理最多新增 1 个场景**
|
|
140
|
+
|
|
141
|
+
**示例 A:新记忆整合进已有 block(UPDATE - 原地更新)**
|
|
142
|
+
**具体操作步骤(工具调用)**:
|
|
143
|
+
1. **read**(\`path\`='Python后端开发.md') → 获取已有内容 A
|
|
144
|
+
2. 分析新记忆 + 已有内容 A → 整合生成新内容 B(\`heat = 旧heat + 1\`)
|
|
145
|
+
3. **write**(\`path\`='Python后端开发.md', \`content\`=B) → **整体重写该场景文件**
|
|
146
|
+
或 **edit**(\`path\`='Python后端开发.md', \`edits\`=[{\`oldText\`: 旧章节, \`newText\`: 新章节}]) → **局部更新某部分**
|
|
147
|
+
|
|
148
|
+
**示例 B:合并多个 block(MERGE — 合并后必须删除旧文件)**
|
|
149
|
+
**具体操作步骤(工具调用)**:
|
|
150
|
+
1. **read**(\`path\`='Python后端开发.md') → 获取内容 A
|
|
151
|
+
2. **read**(\`path\`='Go后端开发.md') → 获取内容 B
|
|
152
|
+
3. 整合 A + B + 新记忆 → 生成新内容 C(\`heat = heatA + heatB + 1\`)
|
|
153
|
+
4. **write**(\`path\`='后端开发技术栈.md', \`content\`=C) → 创建合并后的新文件
|
|
154
|
+
5. **write**(\`path\`='Python后端开发.md', \`content\`='[DELETED]') → **⚠️ 删除旧文件 A**
|
|
155
|
+
6. **write**(\`path\`='Go后端开发.md', \`content\`='[DELETED]') → **⚠️ 删除旧文件 B**
|
|
156
|
+
**关键**:步骤 5-6 是必须的!不执行删除 = 文件总数不减少 = 合并无效。
|
|
157
|
+
|
|
158
|
+
### 阶段 3:撰写与合成(核心任务)
|
|
159
|
+
深度整合: 严禁简单的文本追加。你必须结合上下文(基于摘要或提供的原始内容)重写叙事,将新信息自然地融入其中。
|
|
160
|
+
隐性推断: 寻找用户 没说出口 的信息。更新 "Implicit Signals" section, or its equivalent in the dialogue language.
|
|
161
|
+
冲突检测: 如果新记忆与旧记忆相矛盾,将其记录在 "Evolution Trajectory" 或 "Pending Confirmation / Contradictions" section, or their equivalents in the dialogue language.
|
|
162
|
+
|
|
163
|
+
### 撰写准则 (严格遵守)
|
|
164
|
+
核心部分禁止列表: "User Core Traits" and "Core Narrative" sections, or their equivalents in the dialogue language, must be coherent paragraphs. 信息要连贯,可以分段。
|
|
165
|
+
叙事弧线: "Core Narrative" section, or its equivalent in the dialogue language, must follow a story structure(Trigger -> Action -> Result)。
|
|
166
|
+
|
|
167
|
+
### 热度管理 (Heat Management):
|
|
168
|
+
新建 Block: heat: 1
|
|
169
|
+
更新 Block: heat: 旧heat + 1
|
|
170
|
+
合并 Block: heat: sum(所有相关block的heat) + 1
|
|
171
|
+
|
|
172
|
+
## 输出规范 (Output Specification)
|
|
173
|
+
|
|
174
|
+
### 📄 场景文件内容(必须输出)
|
|
175
|
+
|
|
176
|
+
请你参考这个模板输出 .md 文件的内容或基于已有md进行更新,每个md控制在1500字符内。不要把模板本身放在 Markdown 代码块中,只需直接输出要写入文件的原始文本。
|
|
177
|
+
|
|
178
|
+
> The section headings below are English fallback headings. Actual section headings and body text must follow the output language contract above. For English memories, keep English headings such as \`## User Core Traits\`, \`## User Preferences\`, \`## Implicit Signals\`, and \`## Core Narrative\`.
|
|
179
|
+
|
|
180
|
+
\`\`\`markdown
|
|
181
|
+
-----META-START-----
|
|
182
|
+
created: {{EXISTING_CREATED_TIME_OR_CURRENT_TIME}}
|
|
183
|
+
updated: {{CURRENT_TIME}}
|
|
184
|
+
summary: [30-40 words concise summary for indexing]
|
|
185
|
+
heat: [Integer]
|
|
186
|
+
-----META-END-----
|
|
187
|
+
|
|
188
|
+
## User Basic Information
|
|
189
|
+
[Optional. Omit this section if there is no reliable basic information. Merge compatible facts and overwrite only when a conflict is resolved.]
|
|
190
|
+
- Name:
|
|
191
|
+
- Occupation:
|
|
192
|
+
- Location:
|
|
193
|
+
- ...
|
|
194
|
+
|
|
195
|
+
## User Core Traits
|
|
196
|
+
[Not a list. Write one coherent paragraph about the most important inferred user traits. Be selective and keep it concise, within 100 words.]
|
|
197
|
+
[Example: The user shows a strong preference for Python backend development, especially async frameworks. Recently (2026-02), they started focusing on Rust ownership, suggesting an interest in systems-level programming.]
|
|
198
|
+
|
|
199
|
+
## User Preferences
|
|
200
|
+
[A list is allowed. Omit this section if there is no reliable preference. Record explicit, reusable preferences without duplication or daily logs. Dynamically merge or rewrite when updating.]
|
|
201
|
+
[Example: The user likes apples.]
|
|
202
|
+
|
|
203
|
+
## Implicit Signals
|
|
204
|
+
[Anthropologist notes: record important signals that were not stated directly. These must be thoughtful inferences, not explicit preferences. This section can be empty; prefer omission over weak speculation. Update, delete, or rewrite as evidence changes.]
|
|
205
|
+
|
|
206
|
+
## Core Narrative
|
|
207
|
+
[Not a list. Write one coherent narrative within 400 words. Avoid duplication and daily logs. Dynamically merge or rewrite when updating.]
|
|
208
|
+
*(Record a coherent story that must include Trigger -> Action -> Result.)*
|
|
209
|
+
|
|
210
|
+
[Example: This week the user focused on backend refactoring. They initially felt frustrated by tight coupling in legacy code, but rejected quick patches and insisted on deeper decoupling. During the process, they repeatedly consulted architecture patterns, showing a strong preference for clean code.]
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
## Evolution Trajectory
|
|
214
|
+
> [Note] This can be empty. Only record changes in preferences, personality, or major beliefs. Do not record trivial daily updates. When conflicts occur, preserve the change trajectory instead of overwriting directly.
|
|
215
|
+
- [2026-01-10]: Shifted from "opposes overtime" to "accepts flexible work" due to startup pressure (memory ID: #987)
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
## Pending Confirmation / Contradictions
|
|
219
|
+
- [Record contradictions that cannot yet be integrated and should wait for future memories to clarify.]
|
|
220
|
+
|
|
221
|
+
\`\`\`
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
#### 主动触发 Persona 更新(可选)
|
|
226
|
+
|
|
227
|
+
**触发条件**:重大价值观转变、跨场景突破性洞察。
|
|
228
|
+
|
|
229
|
+
**触发方式**:在你的 text output 中输出以下标记(不是文件操作):
|
|
230
|
+
|
|
231
|
+
[PERSONA_UPDATE_REQUEST]
|
|
232
|
+
reason: 具体原因描述
|
|
233
|
+
[/PERSONA_UPDATE_REQUEST]
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
**执行文件操作**(必须使用工具):
|
|
237
|
+
- 使用 **read** 读取需要更新的场景文件
|
|
238
|
+
- 使用 **write** 创建新文件或**整体重写**已有场景文件
|
|
239
|
+
- 使用 **edit** 对场景文件进行**局部更新**(如只更新某个章节)
|
|
240
|
+
- **删除文件**:使用 **write**(\`path\`=文件名, \`content\`='[DELETED]') 写入删除标记。系统会自动清理这些文件。**重要**:只有 \`[DELETED]\` 标记会触发系统清理。写入空字符串会被系统拒绝,写入 \`[ARCHIVE]\`、\`[CONSOLIDATED]\` 等标记**不会删除文件**,文件会继续占用场景配额。`;
|
|
241
|
+
}
|
|
242
|
+
// ============================
|
|
243
|
+
// User Prompt builder (dynamic data)
|
|
244
|
+
// ============================
|
|
245
|
+
export function buildSceneExtractionPrompt(params) {
|
|
246
|
+
const { memoriesJson, sceneSummaries, currentTimestamp, sceneCountWarning, existingSceneFiles, maxScenes, } = params;
|
|
247
|
+
const warningSection = sceneCountWarning
|
|
248
|
+
? `\n⚠️ **场景数量警告**: ${sceneCountWarning}\n`
|
|
249
|
+
: "";
|
|
250
|
+
const fileListSection = existingSceneFiles && existingSceneFiles.length > 0
|
|
251
|
+
? `### 📁 已有场景文件清单(仅以下文件可 read)\n${existingSceneFiles.map((f) => `- \`${f}\``).join("\n")}\n`
|
|
252
|
+
: `### 📁 已有场景文件清单\n(当前无已有场景文件)\n`;
|
|
253
|
+
const userPrompt = `**Output language**: Scene file names, section headings, and body text must use the dominant language in the New Memories List below. For English memories, use English memory titles and English headings.
|
|
254
|
+
${warningSection}
|
|
255
|
+
### 1️⃣ New Memories List
|
|
256
|
+
${memoriesJson}
|
|
257
|
+
|
|
258
|
+
### 2️⃣ Existing Scene Blocks Summary
|
|
259
|
+
${sceneSummaries}
|
|
260
|
+
|
|
261
|
+
### 3️⃣ Current Timestamp
|
|
262
|
+
${currentTimestamp}
|
|
263
|
+
|
|
264
|
+
${fileListSection}`;
|
|
265
|
+
return {
|
|
266
|
+
systemPrompt: buildSceneSystemPrompt(maxScenes),
|
|
267
|
+
userPrompt,
|
|
268
|
+
};
|
|
269
|
+
}
|