@memtensor/memos-local-openclaw-plugin 1.0.9 → 1.0.10
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/README.md +4 -0
- package/index.ts +21 -4
- package/package.json +1 -1
- package/src/skill/generator.ts +18 -4
- package/src/viewer/html.ts +1 -0
- package/src/skill/ALGORITHMS.md +0 -141
- package/src/skill/CHANGELOG-DESIGN.md +0 -24
- package/src/skill/DESIGN.md +0 -72
- package/src/skill/experience-extractor.ts +0 -191
- package/src/skill/feedback-signals.ts +0 -181
- package/src/viewer/html-v2.ts +0 -1631
package/README.md
CHANGED
|
@@ -148,6 +148,10 @@ Add the plugin config to `~/.openclaw/openclaw.json`:
|
|
|
148
148
|
"entries": {
|
|
149
149
|
"memos-local-openclaw-plugin": {
|
|
150
150
|
"enabled": true,
|
|
151
|
+
"hooks": {
|
|
152
|
+
// Required on OpenClaw 2026.4.24+ for automatic memory_add via agent_end
|
|
153
|
+
"allowConversationAccess": true
|
|
154
|
+
},
|
|
151
155
|
"config": {
|
|
152
156
|
"embedding": {
|
|
153
157
|
"provider": "openai_compatible",
|
package/index.ts
CHANGED
|
@@ -2386,10 +2386,27 @@ Groups: ${groupNames.length > 0 ? groupNames.join(", ") : "(none)"}`,
|
|
|
2386
2386
|
|
|
2387
2387
|
function isGatewayStartCommand(): boolean {
|
|
2388
2388
|
const args = process.argv.map(a => String(a || "").toLowerCase());
|
|
2389
|
-
|
|
2390
|
-
|
|
2391
|
-
|
|
2392
|
-
|
|
2389
|
+
|
|
2390
|
+
// 1. Match dev environment scripts (pnpm dev / start / watch usually call these mjs files)
|
|
2391
|
+
if (args.some(a => a.includes("run-node.mjs") || a.includes("watch-node.mjs"))) {
|
|
2392
|
+
return true;
|
|
2393
|
+
}
|
|
2394
|
+
|
|
2395
|
+
// 2. Match formal CLI commands (gateway or daemon)
|
|
2396
|
+
const targetIdx = Math.max(args.lastIndexOf("gateway"), args.lastIndexOf("daemon"));
|
|
2397
|
+
if (targetIdx !== -1) {
|
|
2398
|
+
const next = args[targetIdx + 1];
|
|
2399
|
+
|
|
2400
|
+
// Match:
|
|
2401
|
+
// - No subcommand, only gateway config (e.g. openclaw gateway)
|
|
2402
|
+
// - Followed by parameter (e.g. openclaw gateway --port 8080)
|
|
2403
|
+
// - Specific start action (start, restart, run, install)
|
|
2404
|
+
if (!next || next.startsWith("-") || ["start", "restart", "run", "install"].includes(next)) {
|
|
2405
|
+
return true;
|
|
2406
|
+
}
|
|
2407
|
+
}
|
|
2408
|
+
|
|
2409
|
+
return false;
|
|
2393
2410
|
}
|
|
2394
2411
|
|
|
2395
2412
|
const startServiceCore = async (isHostStart = false) => {
|
package/package.json
CHANGED
package/src/skill/generator.ts
CHANGED
|
@@ -24,17 +24,24 @@ This Skill is special: it comes from real execution experience — every step wa
|
|
|
24
24
|
|
|
25
25
|
## Core principles (follow strictly but do NOT include these in output)
|
|
26
26
|
|
|
27
|
+
### Assume the agent is smart
|
|
28
|
+
The agent consuming this skill is an LLM — it already knows how to use git, write code, read docs, and run commands.
|
|
29
|
+
- Only write what the agent CANNOT derive from the codebase or general knowledge: project-specific gotchas, non-obvious config, verified flags/versions, and real pitfalls encountered.
|
|
30
|
+
- Do NOT explain basic concepts, standard tool usage, or things any competent developer already knows.
|
|
31
|
+
|
|
27
32
|
### Progressive disclosure
|
|
28
33
|
- The frontmatter description (~100 words) is ALWAYS in the agent's context — it must be self-sufficient for deciding whether to use this skill.
|
|
29
34
|
- The SKILL.md body loads when triggered — keep it under 400 lines, focused, no fluff.
|
|
30
35
|
- If the task involved large configs/scripts, mention them but DON'T inline everything — just reference that scripts/ or references/ may contain them.
|
|
36
|
+
- If the skill covers multiple variants (e.g. different OS targets, different cloud providers), put variant-specific details in references/ files. The main SKILL.md should cover the common workflow only.
|
|
31
37
|
|
|
32
38
|
### Description as trigger mechanism
|
|
33
|
-
The description field
|
|
39
|
+
The description field is the SOLE input the agent uses to decide whether to activate this skill. If the description doesn't match, the body is never read — so put maximum effort here.
|
|
34
40
|
- Don't just say what it does — list the situations, keywords, and phrasings that should trigger it.
|
|
35
41
|
- Claude/agents tend to under-trigger skills. Counter this by being explicit about when to use it.
|
|
42
|
+
- Include indirect phrasings: users often describe the goal ("make it run anywhere") rather than the tool ("Docker").
|
|
36
43
|
- Bad: "How to deploy Node.js to Docker"
|
|
37
|
-
- Good: "How to containerize and deploy a Node.js application using Docker.
|
|
44
|
+
- Good: "How to containerize and deploy a Node.js application using Docker. TRIGGER when: user mentions Docker deployment, Dockerfile writing, container builds, multi-stage builds, port mapping, .dockerignore, image optimization, CI/CD container pipelines, or any task involving packaging a Node/JS backend into a container — even if they don't say 'Docker' explicitly but describe wanting to 'package the app for production' or 'run it anywhere'."
|
|
38
45
|
|
|
39
46
|
### Writing style
|
|
40
47
|
- Use imperative form
|
|
@@ -43,6 +50,9 @@ The description field decides whether the agent activates this skill. Write it "
|
|
|
43
50
|
- Generalize from the specific task so the skill works for similar future scenarios, don't over-fit to this exact project
|
|
44
51
|
- Keep real commands/code/config from the task record — these are verified to work
|
|
45
52
|
|
|
53
|
+
### No extra files
|
|
54
|
+
- Output ONLY the SKILL.md content. Do NOT create or reference README.md, CHANGELOG.md, INSTALLATION_GUIDE.md, CONTRIBUTING.md, or similar boilerplate files. All information belongs in SKILL.md, scripts/, or references/.
|
|
55
|
+
|
|
46
56
|
### Language matching (CRITICAL)
|
|
47
57
|
You MUST write the ENTIRE skill in the SAME language as the user's messages in the task record.
|
|
48
58
|
- If the user wrote in Chinese → the skill title, description, all prose sections MUST be in Chinese
|
|
@@ -57,12 +67,16 @@ DO NOT default to English. Look at the task record below and match its language.
|
|
|
57
67
|
|
|
58
68
|
Output ONLY the complete SKILL.md content. No extra text before or after.
|
|
59
69
|
|
|
70
|
+
The frontmatter contains ONLY name and description. Nothing else.
|
|
71
|
+
(OpenClaw metadata such as emoji is optional — if desired, add it as a \`<!-- metadata: {{"openclaw": {{"emoji": "..."}}}}} -->\` HTML comment at the very end of the file body, NOT in the frontmatter.)
|
|
72
|
+
|
|
60
73
|
---
|
|
61
74
|
name: "{NAME}"
|
|
62
|
-
description: "{A natural, proactive description. 60-120 words. Cover what it does + multiple phrasings/scenarios that should trigger it.
|
|
63
|
-
metadata: {{ "openclaw": {{ "emoji": "{emoji}" }} }}
|
|
75
|
+
description: "{A natural, proactive description. 60-120 words. Cover what it does + multiple phrasings/scenarios that should trigger it. Include a TRIGGER line listing keywords and indirect phrasings. Be pushy about triggering.}"
|
|
64
76
|
---
|
|
65
77
|
|
|
78
|
+
Name rules: 2-4 English words, kebab-case, lowercase (e.g. "docker-node-deploy", "aws-s3-backup"). This is a machine identifier.
|
|
79
|
+
|
|
66
80
|
# {Title — clear, action-oriented}
|
|
67
81
|
|
|
68
82
|
{One sentence: what this skill helps you do and why it's valuable}
|
package/src/viewer/html.ts
CHANGED
|
@@ -7205,6 +7205,7 @@ function onProviderChange(){}
|
|
|
7205
7205
|
async function loadConfig(){
|
|
7206
7206
|
try{
|
|
7207
7207
|
const r=await fetch('/api/config');
|
|
7208
|
+
if(r.status===401){toast(t('settings.session.expired'),'error');return;}
|
|
7208
7209
|
if(!r.ok) return;
|
|
7209
7210
|
const cfg=await r.json();
|
|
7210
7211
|
const emb=cfg.embedding||{};
|
package/src/skill/ALGORITHMS.md
DELETED
|
@@ -1,141 +0,0 @@
|
|
|
1
|
-
# Skill 反馈驱动重构 — 算法原理
|
|
2
|
-
|
|
3
|
-
## 1. 纯语义反馈识别
|
|
4
|
-
|
|
5
|
-
### 模块
|
|
6
|
-
`feedback-signals.ts` → `FeedbackSignalExtractor`
|
|
7
|
-
|
|
8
|
-
### 输入
|
|
9
|
-
多轮对话 chunks(user + assistant + tool),含 turnId
|
|
10
|
-
|
|
11
|
-
### 输出
|
|
12
|
-
`FeedbackSignal[]`,每条包含 type、source、evidence、turnId、confidence
|
|
13
|
-
|
|
14
|
-
### 信号类型
|
|
15
|
-
| Type | 含义 | 示例 |
|
|
16
|
-
|------|------|------|
|
|
17
|
-
| reject | 用户否定当前方案 | "这不是我要的效果" |
|
|
18
|
-
| correction | 用户修正 agent 输出 | "不是这个文件,应该是 config.yaml" |
|
|
19
|
-
| constraint | 用户追加约束 | "而且不能用 root 用户" |
|
|
20
|
-
| success | 用户确认成功 | "可以了,这次对了" |
|
|
21
|
-
| confusion | 用户表达困惑 | "为什么会这样?" |
|
|
22
|
-
| preference | 用户表达偏好 | "我更喜欢用 pnpm" |
|
|
23
|
-
|
|
24
|
-
### 隐式反馈识别
|
|
25
|
-
不依赖关键词匹配。通过 LLM 理解上下文语义:
|
|
26
|
-
- 用户重述目标 → 可能是隐式 reject
|
|
27
|
-
- 用户频繁追问细节 → 可能是 confusion
|
|
28
|
-
- 用户切换话题但不明确否定 → 可能是 preference
|
|
29
|
-
|
|
30
|
-
## 2. TaskExperience 提取
|
|
31
|
-
|
|
32
|
-
### 模块
|
|
33
|
-
`experience-extractor.ts` → `ExperienceExtractor`
|
|
34
|
-
|
|
35
|
-
### 三级策略
|
|
36
|
-
| Mode | 调用次数 | 输出 |
|
|
37
|
-
|------|----------|------|
|
|
38
|
-
| minimal | 0 次额外 LLM | 不生成经验 |
|
|
39
|
-
| balanced | 1 次 LLM | TaskExperience(含内联 feedbackSignals) |
|
|
40
|
-
| full | 2 次 LLM | 独立 feedbackSignals + TaskExperience |
|
|
41
|
-
|
|
42
|
-
### 输出 schema
|
|
43
|
-
```json
|
|
44
|
-
{
|
|
45
|
-
"goal": "用户要做什么",
|
|
46
|
-
"successCriteria": ["标准1", "标准2"],
|
|
47
|
-
"failedAttempts": [{
|
|
48
|
-
"attemptLabel": "短标签",
|
|
49
|
-
"approachSummary": "尝试了什么",
|
|
50
|
-
"failureReason": "为什么失败",
|
|
51
|
-
"replacedBy": "被什么替代"
|
|
52
|
-
}],
|
|
53
|
-
"workingApproach": "最终成功的方案",
|
|
54
|
-
"transferableHeuristics": [{
|
|
55
|
-
"kind": "avoid|prefer|verify|clarify",
|
|
56
|
-
"trigger": "触发场景",
|
|
57
|
-
"rule": "应该/不应该做什么",
|
|
58
|
-
"why": "原因",
|
|
59
|
-
"evidence": "证据"
|
|
60
|
-
}]
|
|
61
|
-
}
|
|
62
|
-
```
|
|
63
|
-
|
|
64
|
-
### 缓存策略
|
|
65
|
-
- 结果缓存在 `task_experience_cache` 表
|
|
66
|
-
- 同一 task 不重复提取
|
|
67
|
-
- worker 重试安全
|
|
68
|
-
|
|
69
|
-
## 3. Heuristic 生命周期
|
|
70
|
-
|
|
71
|
-
### 状态机
|
|
72
|
-
```
|
|
73
|
-
[新提取] → candidate → active → refined → stale → [归档]
|
|
74
|
-
```
|
|
75
|
-
|
|
76
|
-
### 晋升条件 (candidate → active)
|
|
77
|
-
- `supportTaskCount >= 2`
|
|
78
|
-
- `evidenceCount >= 3`
|
|
79
|
-
- 被 skill 升级流程采纳
|
|
80
|
-
|
|
81
|
-
### 降级条件 (active → stale)
|
|
82
|
-
- 连续多次召回后被判定为 misleading
|
|
83
|
-
- 被更新版 heuristic 替代
|
|
84
|
-
- 依赖的工具/环境过时
|
|
85
|
-
|
|
86
|
-
### 去重/合并算法
|
|
87
|
-
对每条新 candidate:
|
|
88
|
-
1. 计算 embedding
|
|
89
|
-
2. 在已有 heuristic 库中做余弦相似度搜索
|
|
90
|
-
3. 相似度 >= 0.85 → 更新已有 heuristic(增加 evidence 和 support count)
|
|
91
|
-
4. 0.60 <= 相似度 < 0.85 → 追加证据到已有 heuristic
|
|
92
|
-
5. 相似度 < 0.60 → 新建 candidate
|
|
93
|
-
|
|
94
|
-
## 4. Heuristic 召回打分
|
|
95
|
-
|
|
96
|
-
### 三因子公式
|
|
97
|
-
```
|
|
98
|
-
score = 0.45 × topic_similarity + 0.35 × trigger_signal_similarity + 0.20 × quality_weight
|
|
99
|
-
```
|
|
100
|
-
|
|
101
|
-
### topic_similarity
|
|
102
|
-
query embedding 与 heuristic embedding 的余弦相似度
|
|
103
|
-
|
|
104
|
-
### trigger_signal_similarity
|
|
105
|
-
当前对话的 LiveInteractionSignals 与 heuristic trigger 的匹配度。
|
|
106
|
-
LiveInteractionSignals 包括:
|
|
107
|
-
- userDissatisfaction
|
|
108
|
-
- constraintRestatement
|
|
109
|
-
- repeatedFailure
|
|
110
|
-
- acceptanceUnclear
|
|
111
|
-
- sameErrorRepeated
|
|
112
|
-
|
|
113
|
-
### quality_weight 归一化
|
|
114
|
-
```
|
|
115
|
-
base = 0.5
|
|
116
|
-
+ 0.2 if status in (active, refined)
|
|
117
|
-
+ 0.15 if supportTaskCount >= 3
|
|
118
|
-
+ 0.10 if supportTaskCount >= 5
|
|
119
|
-
+ 0.05 if lastValidatedAt within 7 days
|
|
120
|
-
= min(1.0, total)
|
|
121
|
-
```
|
|
122
|
-
|
|
123
|
-
## 5. 统一 LLM 过滤
|
|
124
|
-
|
|
125
|
-
memory 和 heuristic 候选合并为统一候选池:
|
|
126
|
-
- 每条带 `candidateType: "memory" | "heuristic"`
|
|
127
|
-
- 告诉 LLM 两类职责不同:memory = 事实证据,heuristic = 行为约束
|
|
128
|
-
- 限制总输出:最多 3 memory + 2 heuristic,或总量 5
|
|
129
|
-
|
|
130
|
-
### Prompt 注入顺序
|
|
131
|
-
1. Heuristic(行为约束 / 避坑提醒)
|
|
132
|
-
2. Skill summary(方法入口)
|
|
133
|
-
3. Memory evidence(事实证据)
|
|
134
|
-
|
|
135
|
-
## 6. Context Budget
|
|
136
|
-
|
|
137
|
-
| 类型 | 上限 | 格式 |
|
|
138
|
-
|------|------|------|
|
|
139
|
-
| heuristic | 2-3 条 | trigger + rule + why(1-2 句) |
|
|
140
|
-
| skill | 1-2 条 | 名称 + 适用场景 + 调用提示 |
|
|
141
|
-
| memory | 2-3 条 | 相关 evidence span |
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
# Skill 反馈驱动重构 — 设计变更记录
|
|
2
|
-
|
|
3
|
-
## 2026-04-13: 初始实现
|
|
4
|
-
|
|
5
|
-
### 变更内容
|
|
6
|
-
- 新增 types: FeedbackSignal, FailedAttempt, TaskExperience, SkillHeuristic, HeuristicRecallEvent
|
|
7
|
-
- 新增 SQLite 表: task_feedback_signals, task_failed_attempts, task_experience_cache, skill_heuristics, skill_heuristic_embeddings, heuristic_recall_events
|
|
8
|
-
- 新增模块: feedback-signals.ts, experience-extractor.ts
|
|
9
|
-
- 修改 evaluator.ts: 支持 TaskExperience 输入
|
|
10
|
-
- 修改 generator.ts: 双产物输出 (SKILL.md + heuristics)
|
|
11
|
-
- 修改 upgrader.ts: 携带完整原始证据升级
|
|
12
|
-
- 修改 evolver.ts: 整合 experience → eval → generate/upgrade → heuristic 管线
|
|
13
|
-
- 修改 recall/engine.ts: 双通道召回 (memory + heuristic) + 三因子打分
|
|
14
|
-
- 修改 index.ts: heuristic 召回 + prompt 注入
|
|
15
|
-
- 新增 viewer/html-v2.ts: 新版 viewer 前端
|
|
16
|
-
- 修改 viewer/server.ts: heuristic API 路由 + viewer v2 切换
|
|
17
|
-
- 修改 sharing/types.ts: SkillBundle 增加 heuristics 字段
|
|
18
|
-
|
|
19
|
-
### 设计决策
|
|
20
|
-
1. **balanced 模式为默认**: 一次 LLM 调用同时产出 feedbackSignals + failedAttempts + heuristics,平衡成本和质量
|
|
21
|
-
2. **heuristic 独立存储**: 不混入 chunks 表,有独立 embedding 和生命周期
|
|
22
|
-
3. **统一 LLM 过滤**: memory 和 heuristic 合并过滤,避免两次 LLM 调用
|
|
23
|
-
4. **注入顺序 heuristic-first**: 让 agent 先看到避坑规则,再看方法和证据
|
|
24
|
-
5. **向后兼容**: 所有新功能默认启用但可关闭,老数据不强制回填
|
package/src/skill/DESIGN.md
DELETED
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
# Skill 反馈驱动重构 — 设计说明
|
|
2
|
-
|
|
3
|
-
## 方案背景
|
|
4
|
-
|
|
5
|
-
将 skill 系统从"只总结成功步骤"升级为一套同时沉淀三类资产的系统:
|
|
6
|
-
- **Verified Skill**: 最终验证可复用的操作路径 (SKILL.md)
|
|
7
|
-
- **Failure Heuristics**: 从失败和用户纠偏中提炼出的避坑规则 (SkillHeuristic)
|
|
8
|
-
- **Feedback Signals**: 用户否定、修正、确认成功等高价值监督信号 (FeedbackSignal)
|
|
9
|
-
|
|
10
|
-
## 三层资产模型
|
|
11
|
-
|
|
12
|
-
### Skill (技能)
|
|
13
|
-
- 存储:`skills` 表 + 文件系统 (`skills-store/<name>/SKILL.md`)
|
|
14
|
-
- 职责:记录"怎么做" — 可复用的操作步骤、配置、命令
|
|
15
|
-
- 触发:通过 description embedding 在 recall 时被检索
|
|
16
|
-
- 生命周期:`draft → active → archived`
|
|
17
|
-
|
|
18
|
-
### SkillHeuristic (经验规则)
|
|
19
|
-
- 存储:`skill_heuristics` 表 + `skill_heuristic_embeddings`
|
|
20
|
-
- 职责:记录"别怎么做/什么时候要警惕" — 避坑、验证、偏好规则
|
|
21
|
-
- 触发:通过 embedding 在 recall 时被主动注入 prompt
|
|
22
|
-
- 生命周期:`candidate → active → refined → stale`
|
|
23
|
-
- 与 Skill 关系:多对多(通过 `relatedSkillIds`),可跨 skill 或无关联
|
|
24
|
-
|
|
25
|
-
### FeedbackSignal (反馈信号)
|
|
26
|
-
- 存储:`task_feedback_signals` 表
|
|
27
|
-
- 职责:原始监督信号,是 heuristic 的证据来源
|
|
28
|
-
- 来源:用户否定、执行错误、助手自检
|
|
29
|
-
- 生命周期:与任务绑定,不独立演化
|
|
30
|
-
|
|
31
|
-
## 数据流
|
|
32
|
-
|
|
33
|
-
```
|
|
34
|
-
CaptureMessages → Chunks & Tasks
|
|
35
|
-
↓
|
|
36
|
-
FeedbackSignalExtractor (feedback-signals.ts)
|
|
37
|
-
↓
|
|
38
|
-
ExperienceExtractor (experience-extractor.ts)
|
|
39
|
-
↓
|
|
40
|
-
TaskExperience
|
|
41
|
-
↙ ↘
|
|
42
|
-
SkillEvaluator HeuristicWriter (evolver.ts)
|
|
43
|
-
↓ ↓
|
|
44
|
-
SkillGenerator/ skill_heuristics 表
|
|
45
|
-
SkillUpgrader ↓
|
|
46
|
-
↓ RecallEngine.searchHeuristics()
|
|
47
|
-
SKILL.md ↓
|
|
48
|
-
Prompt 注入 (heuristic → skill → memory)
|
|
49
|
-
```
|
|
50
|
-
|
|
51
|
-
## 与现有系统兼容
|
|
52
|
-
|
|
53
|
-
- 所有新功能通过 `SkillEvolutionConfig` 的新字段控制开关
|
|
54
|
-
- 老数据不回填:`read-time compatible, write-time upgraded`
|
|
55
|
-
- 新表缺失时不影响旧系统启动
|
|
56
|
-
- heuristic 不混入 chunks 表,独立存储和检索
|
|
57
|
-
|
|
58
|
-
## 配置
|
|
59
|
-
|
|
60
|
-
新增配置项(均挂在 `skillEvolution` 下):
|
|
61
|
-
- `experienceExtractionEnabled`: 是否启用经验提炼 (默认 true)
|
|
62
|
-
- `heuristicEnabled`: 是否启用 heuristic 生成 (默认 true)
|
|
63
|
-
- `heuristicRecallEnabled`: 是否启用 heuristic 召回 (默认 true)
|
|
64
|
-
- `experienceMode`: `"minimal" | "balanced" | "full"` (默认 balanced)
|
|
65
|
-
- `heuristicRecallTopK`: 最多召回几条 heuristic (默认 3)
|
|
66
|
-
- `heuristicScoringWeights`: 三因子打分权重
|
|
67
|
-
|
|
68
|
-
## 关键参考
|
|
69
|
-
|
|
70
|
-
- Reflexion (Shinn et al., 2023): LLM 自我反思和经验回放
|
|
71
|
-
- Voyager (Wang et al., 2023): 技能库自动扩展
|
|
72
|
-
- 本项目文档: `skill-feedback-redesign.md`
|
|
@@ -1,191 +0,0 @@
|
|
|
1
|
-
import { v4 as uuid } from "uuid";
|
|
2
|
-
import type { SqliteStore } from "../storage/sqlite";
|
|
3
|
-
import type { Task, Chunk, TaskExperience, FeedbackSignal, FailedAttempt, TransferableHeuristic, ExperienceMode, PluginContext } from "../types";
|
|
4
|
-
import { DEFAULTS } from "../types";
|
|
5
|
-
import { FeedbackSignalExtractor } from "./feedback-signals";
|
|
6
|
-
import { buildSkillConfigChain, callLLMWithFallback } from "../shared/llm-call";
|
|
7
|
-
|
|
8
|
-
const EXPERIENCE_PROMPT = `You are a task experience extraction expert. Analyze the completed task record and extract structured experience data.
|
|
9
|
-
|
|
10
|
-
Focus on:
|
|
11
|
-
1. What was the goal and how was success defined?
|
|
12
|
-
2. What approaches were tried and failed? WHY did they fail?
|
|
13
|
-
3. What was the final working approach?
|
|
14
|
-
4. What user feedback drove direction changes?
|
|
15
|
-
5. What transferable lessons/heuristics can be extracted for future similar tasks?
|
|
16
|
-
|
|
17
|
-
For transferable heuristics, each should be:
|
|
18
|
-
- "avoid": Something to NOT do (backed by real failure evidence)
|
|
19
|
-
- "prefer": A better approach discovered through trial-and-error
|
|
20
|
-
- "verify": A check that should be performed to prevent known failure modes
|
|
21
|
-
- "clarify": A question that should be asked early to avoid going down wrong paths
|
|
22
|
-
|
|
23
|
-
Task title: {TITLE}
|
|
24
|
-
Task summary:
|
|
25
|
-
{SUMMARY}
|
|
26
|
-
|
|
27
|
-
Conversation highlights:
|
|
28
|
-
{CONVERSATION}
|
|
29
|
-
|
|
30
|
-
{FEEDBACK_SECTION}
|
|
31
|
-
|
|
32
|
-
Reply with JSON only, strictly following this schema:
|
|
33
|
-
{
|
|
34
|
-
"goal": "What the user wanted to achieve",
|
|
35
|
-
"successCriteria": ["criterion1", "criterion2"],
|
|
36
|
-
"failedAttempts": [
|
|
37
|
-
{
|
|
38
|
-
"attemptLabel": "short label",
|
|
39
|
-
"approachSummary": "what was tried",
|
|
40
|
-
"failureReason": "why it failed",
|
|
41
|
-
"replacedBy": "what replaced it or null"
|
|
42
|
-
}
|
|
43
|
-
],
|
|
44
|
-
"workingApproach": "The final approach that worked",
|
|
45
|
-
"transferableHeuristics": [
|
|
46
|
-
{
|
|
47
|
-
"kind": "avoid|prefer|verify|clarify",
|
|
48
|
-
"trigger": "when/what situation triggers this heuristic",
|
|
49
|
-
"rule": "what to do or not do",
|
|
50
|
-
"why": "reason based on real evidence",
|
|
51
|
-
"evidence": "brief quote from conversation"
|
|
52
|
-
}
|
|
53
|
-
]
|
|
54
|
-
}`;
|
|
55
|
-
|
|
56
|
-
export class ExperienceExtractor {
|
|
57
|
-
private feedbackExtractor: FeedbackSignalExtractor;
|
|
58
|
-
|
|
59
|
-
constructor(
|
|
60
|
-
private store: SqliteStore,
|
|
61
|
-
private ctx: PluginContext,
|
|
62
|
-
) {
|
|
63
|
-
this.feedbackExtractor = new FeedbackSignalExtractor(ctx);
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
async extract(task: Task, chunks: Chunk[]): Promise<TaskExperience | null> {
|
|
67
|
-
const mode = this.ctx.config.skillEvolution?.experienceMode ?? DEFAULTS.experienceMode;
|
|
68
|
-
if (mode === "minimal") return null;
|
|
69
|
-
|
|
70
|
-
const cached = this.store.getCachedTaskExperience(task.id);
|
|
71
|
-
if (cached) {
|
|
72
|
-
this.ctx.log.debug(`ExperienceExtractor: using cached experience for task ${task.id}`);
|
|
73
|
-
return cached;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
const chain = buildSkillConfigChain(this.ctx);
|
|
77
|
-
if (chain.length === 0) {
|
|
78
|
-
this.ctx.log.warn("ExperienceExtractor: no LLM config, skipping");
|
|
79
|
-
return null;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
let feedbackSignals: FeedbackSignal[] = [];
|
|
83
|
-
if (mode === "full") {
|
|
84
|
-
feedbackSignals = await this.feedbackExtractor.extract(task.id, chunks);
|
|
85
|
-
for (const signal of feedbackSignals) {
|
|
86
|
-
this.store.insertFeedbackSignal(signal);
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
const conversationText = this.buildConversation(chunks);
|
|
91
|
-
const feedbackSection = feedbackSignals.length > 0
|
|
92
|
-
? `\nPre-extracted feedback signals:\n${JSON.stringify(feedbackSignals.map(s => ({ type: s.type, source: s.source, evidence: s.evidence, confidence: s.confidence })), null, 2)}`
|
|
93
|
-
: "";
|
|
94
|
-
|
|
95
|
-
const prompt = EXPERIENCE_PROMPT
|
|
96
|
-
.replace("{TITLE}", task.title)
|
|
97
|
-
.replace("{SUMMARY}", task.summary.slice(0, 4000))
|
|
98
|
-
.replace("{CONVERSATION}", conversationText.slice(0, 12000))
|
|
99
|
-
.replace("{FEEDBACK_SECTION}", feedbackSection);
|
|
100
|
-
|
|
101
|
-
try {
|
|
102
|
-
const raw = await callLLMWithFallback(chain, prompt, this.ctx.log, "ExperienceExtractor", {
|
|
103
|
-
maxTokens: 3000, temperature: 0.1, timeoutMs: 90_000, openclawAPI: this.ctx.openclawAPI,
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
const experience = this.parseExperience(raw, task.id, feedbackSignals);
|
|
107
|
-
if (!experience) return null;
|
|
108
|
-
|
|
109
|
-
if (mode === "balanced" && experience.feedbackSignals.length > 0) {
|
|
110
|
-
for (const signal of experience.feedbackSignals) {
|
|
111
|
-
this.store.insertFeedbackSignal(signal);
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
for (const attempt of experience.failedAttempts) {
|
|
116
|
-
this.store.insertFailedAttempt(attempt);
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
this.store.cacheTaskExperience(task.id, experience);
|
|
120
|
-
this.ctx.log.info(`ExperienceExtractor: extracted experience for task "${task.title}" — ${experience.failedAttempts.length} failed attempts, ${experience.transferableHeuristics.length} heuristics`);
|
|
121
|
-
return experience;
|
|
122
|
-
} catch (err) {
|
|
123
|
-
this.ctx.log.warn(`ExperienceExtractor failed: ${err}`);
|
|
124
|
-
return null;
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
private buildConversation(chunks: Chunk[]): string {
|
|
129
|
-
return chunks
|
|
130
|
-
.filter(c => c.role !== "system")
|
|
131
|
-
.map(c => {
|
|
132
|
-
const role = c.role === "user" ? "User" : c.role === "assistant" ? "Assistant" : c.role === "tool" ? "Tool" : c.role;
|
|
133
|
-
const content = c.role === "tool" ? c.content.slice(0, 800) : c.content;
|
|
134
|
-
return `[${role}]: ${content}`;
|
|
135
|
-
})
|
|
136
|
-
.join("\n\n");
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
private parseExperience(raw: string, taskId: string, preSignals: FeedbackSignal[]): TaskExperience | null {
|
|
140
|
-
const match = raw.match(/\{[\s\S]*\}/);
|
|
141
|
-
if (!match) return null;
|
|
142
|
-
try {
|
|
143
|
-
const obj = JSON.parse(match[0]);
|
|
144
|
-
const now = Date.now();
|
|
145
|
-
|
|
146
|
-
const failedAttempts: FailedAttempt[] = (obj.failedAttempts || []).map((a: any) => ({
|
|
147
|
-
id: uuid(),
|
|
148
|
-
taskId,
|
|
149
|
-
attemptLabel: a.attemptLabel || "unknown",
|
|
150
|
-
approachSummary: a.approachSummary || "",
|
|
151
|
-
failureReason: a.failureReason || "",
|
|
152
|
-
failureSignalIds: [],
|
|
153
|
-
replacedBy: a.replacedBy || null,
|
|
154
|
-
createdAt: now,
|
|
155
|
-
}));
|
|
156
|
-
|
|
157
|
-
const inlineSignals: FeedbackSignal[] = (obj.feedbackSignals || []).map((s: any) => ({
|
|
158
|
-
id: uuid(),
|
|
159
|
-
taskId,
|
|
160
|
-
type: s.type || "reject",
|
|
161
|
-
source: s.source || "user",
|
|
162
|
-
evidence: s.evidence || "",
|
|
163
|
-
turnId: s.turnId || "",
|
|
164
|
-
confidence: s.confidence || 0.5,
|
|
165
|
-
createdAt: now,
|
|
166
|
-
}));
|
|
167
|
-
|
|
168
|
-
const heuristics: TransferableHeuristic[] = (obj.transferableHeuristics || []).map((h: any) => ({
|
|
169
|
-
kind: h.kind || "avoid",
|
|
170
|
-
trigger: h.trigger || "",
|
|
171
|
-
rule: h.rule || "",
|
|
172
|
-
why: h.why || "",
|
|
173
|
-
evidence: h.evidence || "",
|
|
174
|
-
}));
|
|
175
|
-
|
|
176
|
-
return {
|
|
177
|
-
id: uuid(),
|
|
178
|
-
taskId,
|
|
179
|
-
goal: obj.goal || "",
|
|
180
|
-
successCriteria: Array.isArray(obj.successCriteria) ? obj.successCriteria : [],
|
|
181
|
-
failedAttempts,
|
|
182
|
-
workingApproach: obj.workingApproach || "",
|
|
183
|
-
feedbackSignals: preSignals.length > 0 ? preSignals : inlineSignals,
|
|
184
|
-
transferableHeuristics: heuristics,
|
|
185
|
-
createdAt: now,
|
|
186
|
-
};
|
|
187
|
-
} catch {
|
|
188
|
-
return null;
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
}
|