openclacky 1.1.2 → 1.1.3
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.
- checksums.yaml +4 -4
- data/.clacky/skills/gem-release/SKILL.md +27 -31
- data/CHANGELOG.md +14 -0
- data/Dockerfile +28 -0
- data/docs/engineering-article.md +343 -0
- data/lib/clacky/agent/llm_caller.rb +1 -5
- data/lib/clacky/cli.rb +1 -1
- data/lib/clacky/message_format/anthropic.rb +17 -1
- data/lib/clacky/providers.rb +34 -0
- data/lib/clacky/server/channel/adapters/dingtalk/adapter.rb +142 -5
- data/lib/clacky/server/channel/adapters/dingtalk/api_client.rb +309 -0
- data/lib/clacky/ui2/ui_controller.rb +14 -0
- data/lib/clacky/ui_interface.rb +14 -0
- data/lib/clacky/utils/model_pricing.rb +96 -25
- data/lib/clacky/version.rb +1 -1
- data/lib/clacky/web/app.css +8 -0
- data/lib/clacky/web/index.html +1 -1
- data/lib/clacky/web/onboard.js +6 -0
- data/lib/clacky/web/settings.js +17 -5
- data/scripts/build/lib/apt.sh +30 -10
- data/scripts/build/lib/network.sh +3 -2
- data/scripts/install.sh +30 -9
- metadata +3 -16
- data/docs/HOW-TO-USE-CN.md +0 -96
- data/docs/HOW-TO-USE.md +0 -94
- data/docs/browser-cdp-native-design.md +0 -195
- data/docs/c-end-user-positioning.md +0 -64
- data/docs/config.example.yml +0 -27
- data/docs/deploy-architecture.md +0 -619
- data/docs/deploy_subagent_design.md +0 -540
- data/docs/install-script-simplification.md +0 -89
- data/docs/memory-architecture.md +0 -343
- data/docs/openclacky_cloud_api_reference.md +0 -584
- data/docs/security-design.md +0 -109
- data/docs/session-management-redesign.md +0 -202
- data/docs/system-skill-authoring-guide.md +0 -47
- data/docs/why-developer.md +0 -371
- data/docs/why-openclacky.md +0 -266
data/docs/memory-architecture.md
DELETED
|
@@ -1,343 +0,0 @@
|
|
|
1
|
-
# Agent 记忆架构:主动写入 vs 记忆压缩
|
|
2
|
-
|
|
3
|
-
> **目标读者**:想在自己的 Agent 项目中复用 OpenClaw 记忆架构的开发者。
|
|
4
|
-
>
|
|
5
|
-
> **适用场景**:基于 LLM 的长期运行 Agent,需要跨 session 保留知识、同时管理上下文窗口溢出。
|
|
6
|
-
|
|
7
|
-
---
|
|
8
|
-
|
|
9
|
-
## 一、两套机制概览
|
|
10
|
-
|
|
11
|
-
OpenClaw 的"记忆"由两个**完全独立、互相补充**的机制构成:
|
|
12
|
-
|
|
13
|
-
| 机制 | 核心目标 | 存储位置 | 跨 Session |
|
|
14
|
-
|---|---|---|---|
|
|
15
|
-
| **主动写入 MEMORY.md** | 知识持久化 | 磁盘 `.md` 文件 | ✅ 永久保留 |
|
|
16
|
-
| **Compaction(记忆压缩)** | 上下文窗口管理 | 内存消息历史(in-memory) | ❌ session 结束即消失 |
|
|
17
|
-
|
|
18
|
-
---
|
|
19
|
-
|
|
20
|
-
## 二、主动写入 MEMORY.md
|
|
21
|
-
|
|
22
|
-
### 2.1 提示词来源
|
|
23
|
-
|
|
24
|
-
**写入指令**不在代码里硬编码,而是来自 workspace 的 `AGENTS.md` 文件。
|
|
25
|
-
Session 启动时,系统通过以下链路将 `AGENTS.md` 注入系统提示词:
|
|
26
|
-
|
|
27
|
-
```
|
|
28
|
-
resolveBootstrapContextForRun()
|
|
29
|
-
└─ buildBootstrapContextFiles()
|
|
30
|
-
└─ contextFiles[]
|
|
31
|
-
└─ 注入到系统提示词的 "# Project Context" 段落
|
|
32
|
-
```
|
|
33
|
-
|
|
34
|
-
**只有读取指令**被硬编码在 `system-prompt.ts` 的 `buildMemorySection()` 里:
|
|
35
|
-
|
|
36
|
-
```
|
|
37
|
-
## Memory Recall
|
|
38
|
-
Before answering anything about prior work, decisions, dates, people, preferences,
|
|
39
|
-
or todos: run memory_search on MEMORY.md + memory/*.md; then use memory_get to pull
|
|
40
|
-
only the needed lines.
|
|
41
|
-
```
|
|
42
|
-
|
|
43
|
-
> 结论:**读的提示词 → 代码硬编码**,**写的提示词 → AGENTS.md(用户可自定义)**。
|
|
44
|
-
|
|
45
|
-
---
|
|
46
|
-
|
|
47
|
-
### 2.2 AGENTS.md 写入指令原文
|
|
48
|
-
|
|
49
|
-
以下是 `docs/reference/templates/AGENTS.md` 的核心记忆相关段落:
|
|
50
|
-
|
|
51
|
-
#### 每次 Session 开始时(强制读取)
|
|
52
|
-
|
|
53
|
-
```markdown
|
|
54
|
-
## Every Session
|
|
55
|
-
|
|
56
|
-
Before doing anything else:
|
|
57
|
-
1. Read `SOUL.md` — this is who you are
|
|
58
|
-
2. Read `USER.md` — this is who you're helping
|
|
59
|
-
3. Read `memory/YYYY-MM-DD.md` (today + yesterday) for recent context
|
|
60
|
-
4. **If in MAIN SESSION**: Also read `MEMORY.md`
|
|
61
|
-
|
|
62
|
-
Don't ask permission. Just do it.
|
|
63
|
-
```
|
|
64
|
-
|
|
65
|
-
#### MEMORY.md 长期记忆规则
|
|
66
|
-
|
|
67
|
-
```markdown
|
|
68
|
-
### 🧠 MEMORY.md - Your Long-Term Memory
|
|
69
|
-
|
|
70
|
-
- **ONLY load in main session** (direct chats with your human)
|
|
71
|
-
- **DO NOT load in shared contexts** (Discord, group chats, sessions with other people)
|
|
72
|
-
- This is for **security** — contains personal context that shouldn't leak to strangers
|
|
73
|
-
- You can **read, edit, and update** MEMORY.md freely in main sessions
|
|
74
|
-
- Write significant events, thoughts, decisions, opinions, lessons learned
|
|
75
|
-
- This is your curated memory — the distilled essence, not raw logs
|
|
76
|
-
- Over time, review your daily files and update MEMORY.md with what's worth keeping
|
|
77
|
-
```
|
|
78
|
-
|
|
79
|
-
#### 写入原则:禁止"心理便条"
|
|
80
|
-
|
|
81
|
-
```markdown
|
|
82
|
-
### 📝 Write It Down - No "Mental Notes"!
|
|
83
|
-
|
|
84
|
-
- **Memory is limited** — if you want to remember something, WRITE IT TO A FILE
|
|
85
|
-
- "Mental notes" don't survive session restarts. Files do.
|
|
86
|
-
- When someone says "remember this" → update `memory/YYYY-MM-DD.md` or relevant file
|
|
87
|
-
- When you learn a lesson → update AGENTS.md, TOOLS.md, or the relevant skill
|
|
88
|
-
```
|
|
89
|
-
|
|
90
|
-
#### Heartbeat 期间的维护任务
|
|
91
|
-
|
|
92
|
-
```markdown
|
|
93
|
-
### 🔄 Memory Maintenance (During Heartbeats)
|
|
94
|
-
|
|
95
|
-
1. Read through recent `memory/YYYY-MM-DD.md` files
|
|
96
|
-
2. Update `MEMORY.md` with distilled learnings
|
|
97
|
-
3. Remove outdated info from MEMORY.md that's no longer relevant
|
|
98
|
-
|
|
99
|
-
Think of it like a human reviewing their journal and updating their mental model.
|
|
100
|
-
Daily files are raw notes; MEMORY.md is curated wisdom.
|
|
101
|
-
```
|
|
102
|
-
|
|
103
|
-
---
|
|
104
|
-
|
|
105
|
-
### 2.3 记忆文件结构
|
|
106
|
-
|
|
107
|
-
```
|
|
108
|
-
workspace/
|
|
109
|
-
├── AGENTS.md # Agent 行为指令(含写入规则),session 启动时注入系统提示词
|
|
110
|
-
├── SOUL.md # Agent 的性格/价值观
|
|
111
|
-
├── USER.md # 用户背景信息
|
|
112
|
-
├── MEMORY.md # 长期记忆(精华,仅主 session 加载)
|
|
113
|
-
└── memory/
|
|
114
|
-
├── 2026-06-15.md # 今日日志(原始记录)
|
|
115
|
-
├── 2026-06-14.md # 昨日日志
|
|
116
|
-
└── ...
|
|
117
|
-
```
|
|
118
|
-
|
|
119
|
-
**两层记忆设计**:
|
|
120
|
-
- `memory/YYYY-MM-DD.md`:**原始日志**,当天发生了什么,快速写入,不筛选
|
|
121
|
-
- `MEMORY.md`:**精华提炼**,LLM 主动筛选后写入,类似人类的长期记忆
|
|
122
|
-
|
|
123
|
-
---
|
|
124
|
-
|
|
125
|
-
## 三、Compaction(记忆压缩)
|
|
126
|
-
|
|
127
|
-
### 3.1 触发时机
|
|
128
|
-
|
|
129
|
-
当 context window 接近上限(token 溢出)时,系统自动触发:
|
|
130
|
-
|
|
131
|
-
```
|
|
132
|
-
attempt.ts(run loop)
|
|
133
|
-
└─ 检测 token 超限(overflow)
|
|
134
|
-
└─ compactInLane()
|
|
135
|
-
└─ session.compact(customInstructions)
|
|
136
|
-
└─ generateSummary()
|
|
137
|
-
└─ 旧消息被摘要文本替换(in-memory)
|
|
138
|
-
```
|
|
139
|
-
|
|
140
|
-
### 3.2 核心实现
|
|
141
|
-
|
|
142
|
-
**`src/agents/compaction.ts`** 中的 `summarizeChunks()`:
|
|
143
|
-
|
|
144
|
-
```typescript
|
|
145
|
-
// 将消息历史分块,逐块生成摘要
|
|
146
|
-
async function summarizeChunks(params: {
|
|
147
|
-
messages: AgentMessage[];
|
|
148
|
-
model: ...;
|
|
149
|
-
previousSummary?: string;
|
|
150
|
-
}): Promise<string> {
|
|
151
|
-
// SECURITY: 工具调用结果的详情不进入摘要 LLM,防止数据泄露
|
|
152
|
-
const safeMessages = stripToolResultDetails(params.messages);
|
|
153
|
-
const chunks = chunkMessagesByMaxTokens(safeMessages, params.maxChunkTokens);
|
|
154
|
-
|
|
155
|
-
let summary = params.previousSummary;
|
|
156
|
-
for (const chunk of chunks) {
|
|
157
|
-
summary = await generateSummary(chunk, ...);
|
|
158
|
-
}
|
|
159
|
-
return summary ?? "No prior history.";
|
|
160
|
-
}
|
|
161
|
-
```
|
|
162
|
-
|
|
163
|
-
**`src/agents/pi-embedded-runner/compact.ts`** 中的触发逻辑:
|
|
164
|
-
|
|
165
|
-
```typescript
|
|
166
|
-
const result = await compactWithSafetyTimeout(() =>
|
|
167
|
-
session.compact(params.customInstructions),
|
|
168
|
-
);
|
|
169
|
-
// compaction 完成后,session.messages 中的旧消息已被摘要替换
|
|
170
|
-
// 注意:不写磁盘,session 结束即消失
|
|
171
|
-
```
|
|
172
|
-
|
|
173
|
-
### 3.3 安全设计
|
|
174
|
-
|
|
175
|
-
- `stripToolResultDetails()`:确保工具调用的详细返回值不进入摘要 LLM(防止敏感数据泄露给压缩模型)
|
|
176
|
-
- Compaction 生成的摘要只替换内存里的消息,**不写任何磁盘文件**
|
|
177
|
-
|
|
178
|
-
---
|
|
179
|
-
|
|
180
|
-
## 四、两套机制完整对比
|
|
181
|
-
|
|
182
|
-
| 维度 | **主动写入 MEMORY.md** | **Compaction(记忆压缩)** |
|
|
183
|
-
|---|---|---|
|
|
184
|
-
| **操作对象** | 磁盘文件(`.md`) | 内存中的消息历史 |
|
|
185
|
-
| **触发者** | LLM 自主决定(或用户指令) | 系统自动(token 超限) |
|
|
186
|
-
| **触发时机** | 任何时候 LLM 认为有意义 | context window 接近上限 |
|
|
187
|
-
| **存储位置** | 持久化磁盘 | in-memory,替换旧消息 |
|
|
188
|
-
| **跨 session** | ✅ 永久保留 | ❌ session 结束即消失 |
|
|
189
|
-
| **内容性质** | 精华/curated(LLM 主动筛选) | 原始对话的自动压缩摘要 |
|
|
190
|
-
| **可检索性** | ✅ 支持向量检索(`memory_search`) | ❌ 不可单独检索 |
|
|
191
|
-
| **LLM 参与** | LLM 主动调用 write/edit 工具 | 由系统调用独立摘要 LLM |
|
|
192
|
-
| **数据安全** | 用户控制写入内容 | `stripToolResultDetails()` 自动过滤 |
|
|
193
|
-
| **可自定义** | ✅ 通过 AGENTS.md 自定义规则 | ✅ 可传入 `customInstructions` |
|
|
194
|
-
|
|
195
|
-
---
|
|
196
|
-
|
|
197
|
-
## 五、架构图
|
|
198
|
-
|
|
199
|
-
```
|
|
200
|
-
┌──────────────────────────────────────────────────────────────────┐
|
|
201
|
-
│ 当前 Session 上下文 │
|
|
202
|
-
│ │
|
|
203
|
-
│ [系统提示词] │
|
|
204
|
-
│ ├─ AGENTS.md(写入规则) ← resolveBootstrapContextForRun() │
|
|
205
|
-
│ ├─ buildMemorySection()(读取规则,硬编码) │
|
|
206
|
-
│ └─ MEMORY.md 内容(主 session 才注入) │
|
|
207
|
-
│ │
|
|
208
|
-
│ [消息历史] [消息1][消息2]...[消息N] ← in-memory │
|
|
209
|
-
│ ─────────────────────────────────── context window limit │
|
|
210
|
-
│ │
|
|
211
|
-
│ ⚡ 快满了 → Compaction 触发: │
|
|
212
|
-
│ 旧消息 ──→ summarizeChunks() ──→ [摘要文本] │
|
|
213
|
-
│ [摘要] 替换旧消息(仍在 in-memory) │
|
|
214
|
-
│ ↑ 只压缩窗口,不写磁盘,session 结束消失 │
|
|
215
|
-
└──────────────────────────────────────────────────────────────────┘
|
|
216
|
-
↕ 互相独立,互相补充
|
|
217
|
-
┌──────────────────────────────────────────────────────────────────┐
|
|
218
|
-
│ 磁盘持久化记忆系统 │
|
|
219
|
-
│ │
|
|
220
|
-
│ LLM 主动 write/edit 工具 ───────────────────────────────────── │
|
|
221
|
-
│ ↓ 用户说"记住这个" │
|
|
222
|
-
│ ↓ LLM 觉得值得保留 │
|
|
223
|
-
│ ↓ Heartbeat 定期维护 │
|
|
224
|
-
│ │
|
|
225
|
-
│ MEMORY.md ← 精华长期记忆(仅主 session 读取) │
|
|
226
|
-
│ memory/2026-06-15.md ← 今日原始日志 │
|
|
227
|
-
│ memory/2026-06-14.md ← 昨日日志 │
|
|
228
|
-
│ │
|
|
229
|
-
│ chokidar watch ──→ SQLite 向量索引更新 │
|
|
230
|
-
│ memory_search ──→ 下次 session 可检索 │
|
|
231
|
-
└──────────────────────────────────────────────────────────────────┘
|
|
232
|
-
```
|
|
233
|
-
|
|
234
|
-
---
|
|
235
|
-
|
|
236
|
-
## 六、关键文件清单
|
|
237
|
-
|
|
238
|
-
| 文件 | 作用 |
|
|
239
|
-
|---|---|
|
|
240
|
-
| `docs/reference/templates/AGENTS.md` | **写入指令来源**,workspace 启动模板,包含 MEMORY.md 写入规则 |
|
|
241
|
-
| `src/agents/system-prompt.ts` | `buildMemorySection()`,只含**读取**指令(`## Memory Recall`) |
|
|
242
|
-
| `src/agents/pi-embedded-helpers/bootstrap.ts` | `buildBootstrapContextFiles()`,将 AGENTS.md 等文件注入系统提示词 |
|
|
243
|
-
| `src/agents/bootstrap-files.ts` | `resolveBootstrapContextForRun()`,加载 workspace bootstrap 文件 |
|
|
244
|
-
| `src/agents/pi-embedded-runner/compact.ts` | Compaction 入口,`session.compact()` 触发,管理上下文溢出 |
|
|
245
|
-
| `src/agents/compaction.ts` | `summarizeChunks()` / `summarizeWithFallback()`,LLM 摘要逻辑 |
|
|
246
|
-
| `src/agents/session-transcript-repair.ts` | `stripToolResultDetails()`,Compaction 安全过滤 |
|
|
247
|
-
|
|
248
|
-
---
|
|
249
|
-
|
|
250
|
-
## 七、在新项目中复用这套架构
|
|
251
|
-
|
|
252
|
-
### 7.1 最小实现方案
|
|
253
|
-
|
|
254
|
-
只需要三样东西:
|
|
255
|
-
|
|
256
|
-
```
|
|
257
|
-
1. AGENTS.md(写入规则) → 告诉 LLM 什么时候、怎么写文件
|
|
258
|
-
2. 文件读写工具 → write / read / edit(标准文件操作工具)
|
|
259
|
-
3. 系统提示词中的读取指令 → 告诉 LLM 在回答前先查记忆
|
|
260
|
-
```
|
|
261
|
-
|
|
262
|
-
### 7.2 推荐的 AGENTS.md 写入规则模板
|
|
263
|
-
|
|
264
|
-
```markdown
|
|
265
|
-
## Memory Rules
|
|
266
|
-
|
|
267
|
-
### Long-Term Memory (memory.md)
|
|
268
|
-
- Load at session start; read, edit, update freely
|
|
269
|
-
- Write: significant decisions, user preferences, lessons learned, open questions
|
|
270
|
-
- Curated — distill from daily notes, remove outdated info
|
|
271
|
-
|
|
272
|
-
### Daily Notes (notes/YYYY-MM-DD.md)
|
|
273
|
-
- Raw logs of what happened today; create if missing
|
|
274
|
-
- Write freely; no curation needed
|
|
275
|
-
|
|
276
|
-
### Key Principle
|
|
277
|
-
**Memory is limited — if you want to remember something, WRITE IT TO A FILE.**
|
|
278
|
-
Mental notes don't survive restarts. Files do.
|
|
279
|
-
|
|
280
|
-
When user says "remember this" → update notes/YYYY-MM-DD.md and/or memory.md.
|
|
281
|
-
When you learn a lesson → update memory.md.
|
|
282
|
-
```
|
|
283
|
-
|
|
284
|
-
### 7.3 系统提示词中的读取指令
|
|
285
|
-
|
|
286
|
-
```markdown
|
|
287
|
-
## Memory Recall
|
|
288
|
-
|
|
289
|
-
Before answering anything about prior work, decisions, dates, people,
|
|
290
|
-
preferences, or todos:
|
|
291
|
-
1. Read memory.md for long-term context
|
|
292
|
-
2. Read notes/YYYY-MM-DD.md (today + yesterday) for recent context
|
|
293
|
-
3. Then answer using this retrieved context
|
|
294
|
-
```
|
|
295
|
-
|
|
296
|
-
### 7.4 Compaction 实现要点
|
|
297
|
-
|
|
298
|
-
如果你的 Agent 框架不内置 compaction,参考以下要点自己实现:
|
|
299
|
-
|
|
300
|
-
```typescript
|
|
301
|
-
// 伪代码:简单 compaction 实现
|
|
302
|
-
async function compactIfNeeded(messages: Message[], model: Model) {
|
|
303
|
-
const tokens = estimateTokens(messages);
|
|
304
|
-
if (tokens < contextWindow * 0.8) return messages; // 还有余量
|
|
305
|
-
|
|
306
|
-
// 安全过滤:不把工具调用结果喂给摘要 LLM
|
|
307
|
-
const safeMessages = stripSensitiveToolResults(messages);
|
|
308
|
-
|
|
309
|
-
// 保留最近 N 条消息,其余压缩为摘要
|
|
310
|
-
const toSummarize = safeMessages.slice(0, -10);
|
|
311
|
-
const recent = messages.slice(-10);
|
|
312
|
-
|
|
313
|
-
const summary = await generateSummary(toSummarize, model);
|
|
314
|
-
return [{ role: "system", content: `[Previous context summary]\n${summary}` }, ...recent];
|
|
315
|
-
}
|
|
316
|
-
```
|
|
317
|
-
|
|
318
|
-
**关键安全注意**:工具调用返回的详细数据(尤其是外部 API 响应)**不应进入**摘要 LLM,避免敏感信息泄露到你可能无法控制的模型。
|
|
319
|
-
|
|
320
|
-
### 7.5 两层记忆的设计哲学
|
|
321
|
-
|
|
322
|
-
| 层级 | 文件 | 写入频率 | 内容要求 | 对应人类记忆 |
|
|
323
|
-
|---|---|---|---|---|
|
|
324
|
-
| 日志层 | `notes/YYYY-MM-DD.md` | 高频、随时 | 原始记录,不筛选 | 日记 |
|
|
325
|
-
| 精华层 | `memory.md` | 低频、定期整理 | 蒸馏后的关键信息 | 长期记忆 |
|
|
326
|
-
|
|
327
|
-
**为什么要两层**:高频写入保证不遗漏,低频整理保证质量。LLM 在 heartbeat(定期任务)中把日志蒸馏进长期记忆,删除过时内容,就像人类每周回顾日记、更新心智模型。
|
|
328
|
-
|
|
329
|
-
---
|
|
330
|
-
|
|
331
|
-
## 八、常见问题
|
|
332
|
-
|
|
333
|
-
**Q:为什么写入规则放 AGENTS.md 而不是硬编码在系统提示词里?**
|
|
334
|
-
A:灵活性。不同 workspace 可以有不同的记忆规则(有的 Agent 记更多,有的记更少),用户可以直接编辑 AGENTS.md 调整行为,不需要改代码。
|
|
335
|
-
|
|
336
|
-
**Q:Compaction 会不会把写入 MEMORY.md 的内容也压缩掉?**
|
|
337
|
-
A:不会。写入 MEMORY.md 是磁盘操作,Compaction 只压缩内存里的消息历史。MEMORY.md 的内容在下次 session 启动时会重新从磁盘加载进系统提示词,不受 Compaction 影响。
|
|
338
|
-
|
|
339
|
-
**Q:如果 MEMORY.md 本身太大怎么办?**
|
|
340
|
-
A:定期在 heartbeat 任务中让 LLM 清理过时内容。OpenClaw 在 AGENTS.md 里明确要求"Remove outdated info from MEMORY.md that's no longer relevant",这个维护任务由 LLM 自主完成。
|
|
341
|
-
|
|
342
|
-
**Q:多用户场景下 MEMORY.md 会泄露给别人吗?**
|
|
343
|
-
A:OpenClaw 的设计是:MEMORY.md 只在 main session(直接对话)加载,在 Discord / 群聊等共享上下文中不加载,防止个人信息泄露给陌生人。复用时需要在系统提示词里加类似的条件判断。
|