19 KiB
算法对齐检查
对照
apps/memos-local-plugin/docs/Reflect2Skill_算法设计核心.md逐节检查memos-local-plugin的后端 + 前端实现,列出:
- ✅ 已实现:代码 + UI 都跟得上
- ⚠️ 部分实现 / 有偏差:核心在,但闭环没合上、字段没显示、或细节走样
- ❌ 缺失:算法文档要求但代码里没有
每一项都给出对应文件路径行号,便于后续按 ROI 修复。 配套阅读:
GRANULARITY-AND-MEMORY-LAYERS.md(术语 + 粒度 + 经验/环境认知/技能层级关系)。
§0 交互驱动的自我进化框架
§0.1 步级决策过程 — 步级记忆 (L1 grounded trace)
- ✅ 捕获
(s_t, a_t, o_t, ρ_t)—core/capture/capture.ts+step-extractor.ts+reflection-extractor.ts - ✅ 写
traces表(storage/migrations/001-initial.sql) - ✅ 一个工具调用 = 一个 step;最终 assistant 回复 = 一个 step(
step-extractor.tssub-step 拆分) - ✅ 同一用户消息引出的所有 sub-step 共享
turn_id,前端按(episodeId, turnId)聚合(migration 013 +MemoriesView.buildGroups) - ⚠️ 反思合成:
capture/reflection-synth.ts默认开启,但 alpha 评分还依赖 reflection — LLM 缺席时会退化成 α=0,导致 V 全为 0。建议:alpha-scorer.tsheuristic fallback 至少给 α=0.3 最低值
§0.2 任务级反馈 — 会话/回合关系分类
- ✅
q_{k+1}vsq_k的 revision / follow_up / new_task 判定 —core/session/relation-classifier.ts - ✅ revision → reopen episode;follow_up → 同 session 新 episode;new_task → 新 session —
pipeline/orchestrator.ts::onTurnStart - ❌ revision 触发已 finalize traces 重新 backprop — algorithm §0.2 / §2.4.3 要求"修正型反馈回溯修正
q_k轮所有 traces 的V",目前 reopen 事件不会触发新一轮 reward 计算。缺口:reopen 时应派发reward.rescheduled事件让 reward subscriber 用新的R_human覆盖旧 V
§0.5 在线进化更新规则
- ✅ 每条新记忆写入触发增量经验关联:
memory/l2/subscriber.ts监听reward.updated→runL2 - ✅ 多经验抽象环境认知:
memory/l3/subscriber.ts同时订阅l2.policy.induced和l2.policy.updated(status=active 时) - ✅ 检索按 priority 降权:
retrieval/recency.ts::applyRecencyDecay - ❌ 手动改 trace.value —
updateTrace只能改summary / userText / agentText / tags,前端无"标为正/负样本"按钮 - ⚠️ 修订入口分散:经验有
setPolicyStatus+upsert、环境认知有adjustConfidence、技能有archive/rebuild,但没有原子的 "加一条 boundary / 改 verification" 操作
§0.6 反馈效用量化
- ✅
reward/human-scorer.ts调 LLM 按 rubric 打分,落到R_{\text{human}} \in [-1, 1] - ✅ 三 axes(goalAchievement / processQuality / userSatisfaction)实现都在
- ✅
reward/backprop.ts反思加权回溯:V_t = α_t · R + (1-α_t) · γ · V_{t+1} - ✅ 反思权重 α_t —
core/capture/alpha-scorer.ts(LLM 评分 + heuristic 兜底) - ⚠️ Analytics 页面没把 R_human 三 axes 拆开显示
§0.6 Policy Gain
- ✅
G(f^{(2)}) = \bar{V}_{\text{with}} - \tilde{V}_{\text{without}}—core/memory/l2/gain.ts::computeGain,with-set 用 softmax 加权(V7 §0.6 eq. 3),without-set 用 Bayesian shrinkage 把经验均值往中性V_0=0.5收缩(pseudocountN_0=5) - ✅ shrinkage 的工程动机:原始 V7 公式假设训练样本里有显式失败对照,但真实交互中绝大多数 episode 都成功,导致
\bar{V}_{\text{without}} \approx \bar{V}_{\text{with}}、G \to 0,所有 policy 永远卡在 candidate;中性先验保证有用 policy 在\bar{V}_{\text{with}} \approx 0.7–0.85时拿到G \approx 0.05–0.20正分;样本量足够后先验自然淡化 - ✅ 首次 induction gain 修复(详见末尾"已修复"小节)
- ✅ L2/L3/Skill 默认阈值同步下调以匹配新的 gain 分布:
l2.minTraceValue0.05→0.01、l3.minPolicies3→2、l3.minPolicyGain0.1→0.02、skill.minGain0.1→0.02、skill.candidateTrials5→3 - ✅ L3 cluster 二段式 admission(
core/memory/l3/cluster.ts):原 V7 §2.4.1 严格 cosine 形成 cluster 在 LLM 归纳出主题分散的 policy 时几乎无法成型;改成"严格优先 → 否则用整个 domain-key bucket 做 loose cluster",并把cohesion ∈ [0,1]透传给abstract.ts,对 loose cluster 的 confidence 做 0.6× ~ 1.0× 渐变降权。PolicyCluster新增cohesion+admission: "strict"|"loose"两个字段
§1 三层认知(小步 / 经验 / 环境认知)形式化对照
| 算法层 | 形式定义 | 实现行 / 文件 | 评价 |
|---|---|---|---|
| 小步 (L1) | f^{(1)} = (s, a, o, \rho, r) |
core/types.ts::TraceRow |
✅ 字段齐全 |
| 经验 (L2) | f^{(2)} = (\phi, \pi, \kappa, \Omega, \{f^{(1)}\}) |
core/types.ts::PolicyRow |
⚠️ trigger/procedure/verification/boundary 都有;{f^(1)} 只到 episode 粒度(sourceEpisodeIds),没有直接的 trace ids |
| 环境认知 (L3) | f^{(3)} = (\mathcal{E}, \mathcal{I}, \mathcal{C}, \{f^{(2)}\}) |
core/types.ts::WorldModelRow + WorldModelStructure |
✅ 完整 — environment / inference / constraints 三段式齐全;每条 entry 还有 evidenceIds(含 policy + trace ids) |
§2 技能结晶与持续进化
§2.1 Skill 字段完整性
算法文档要求的字段(Skill: YAML 示例)vs 当前 SkillRow + procedureJson(SkillProcedure):
| 算法要求字段 | 对应实现 | 状态 |
|---|---|---|
id |
SkillRow.id |
✅ |
trigger |
procedureJson.steps 第一步 + policy.trigger(renderInvocationGuide 拼接) |
⚠️ 没单独字段,藏在 markdown 里 |
procedure |
procedureJson.steps[] |
✅ 结构化 |
verification |
— | ❌ Skill 没有独立 verification 字段(policy 有,但没传到 skill) |
scope |
procedureJson.preconditions |
⚠️ 部分 — preconditions 算前置条件,但没 applies_to / not_applies_to / boundary 三段式 |
evidence_anchors (L1 trace 级) |
SkillRow.evidenceAnchors (migration 014) + SkillDTO.evidenceAnchors |
✅ 已实现 — packager.buildSkillRow 持久化 gatherEvidence 返回的 trace ids(cap 10, best-first),SkillsView drawer 渲染点击跳 MemoriesView |
domain_model (L3) |
sourceWorldModelIds |
⚠️ 只存 id 数组,crystallize 时不把 world model 内容传给 LLM 作 domain prior |
decision_guidance.anti_pattern |
procedureJson.decisionGuidance.antiPattern |
✅ 已实现 — SKILL_CRYSTALLIZE_PROMPT v2 读 policy.boundary @repair 块 + counter examples,packager.buildProcedure 用 draft 数据填充(不再硬编码空) |
decision_guidance.preference |
procedureJson.decisionGuidance.preference |
✅ 同上 |
reliability.support_count |
support |
✅ |
reliability.success_rate |
— | ⚠️ 只有 trialsAttempted / trialsPassed,可推导但没暴露 |
reliability.beta_posterior |
— | ❌ |
结论:算法 §2.1 要求的核心字段已经基本就位 — evidence_anchors、decision_guidance 都从"硬编码空"修复为真正端到端落地(生成→持久化→DTO→前端显示)。剩余缺口收窄为:domain_model 内容传给 crystallize(只传了 id)、独立 verification 列、reliability.success_rate / beta_posterior 暴露。
§2.4.1 五层加工流水线
| # | 算法步骤 | 实现 | 状态 |
|---|---|---|---|
| 1 | Trace extraction | core/capture/* |
✅ |
| 2 | Value backfill | core/reward/*(任务级 R_human + 反思加权回溯) |
✅ |
| 3 | 跨任务经验关联 / 诱导 | core/memory/l2/*(subscriber + induce + candidate_pool) |
✅ |
| 4 | Episode stitching(辅助) | core/retrieval/tier2-trace.ts::rollupEpisodes + core/session/episode-manager.ts |
✅ 旧 alignment 写错为 ⚠️,实际实现完整 |
| 5 | Model abstraction + value-guided 降权 | core/memory/l3/* + retrieval/recency.ts |
✅ |
§2.4.3 反向修订
- ⚠️ "改写小步 + 回溯价值更新":
updateTrace只改文本,没有重新 backprop 入口 - ⚠️ "修订经验":
setPolicyStatus改 status、upsert改 body,但没"加 boundary / 改 verification" 的原子方法 - ⚠️ "修订环境认知":
adjustConfidence调 confidence,但不会用新反馈重跑l3.abstractionprompt - ⚠️ "修订技能":
retireSkill✅ +skill/skill.tsrebuild 分支 ✅,但没显式的 "Repair / Shrink" 路径
§2.4.5 V 的五个用途
| # | 用途 | 实现 | 状态 |
|---|---|---|---|
| ① | 检索降权 | retrieval/recency.ts::applyRecencyDecay |
✅ |
| ② | 检索排序 | retrieval/tier2-trace.ts 按 V+priority 排序 |
✅ |
| ③ | 策略归纳加权 | memory/l2/similarity.ts::valueWeightedMean(softmax(V/τ)) |
✅ |
| ④ | Skill 可靠性 (η) | skill/packager.ts::deriveInitialEta 从 policy.gain + support 推导 |
⚠️ η 在 packager 里基于 policy.gain 推导;trial 期更新 η 走 lifecycle.ts::updateEta。没暴露 success_rate / beta |
| ⑤ | 决策指引生成 | core/feedback/* |
⚠️ 生成有,闭环没合上(详见 §2.4.6) |
§2.4.6 Decision Repair —— 算法核心闭环之一
| 环节 | 实现 | 状态 |
|---|---|---|
| Burst 触发(同工具连续失败 ≥ threshold) | core/feedback/feedback.ts |
✅ |
| 用户文本触发("不对 / 应该 X / 不要 Y") | core/feedback/classifier.ts |
✅ |
| 算法要求的"同 context 下 V 分布对比 > δ"触发 | — | ❌ 没实现这条触发路径 |
| LLM 合成 anti_pattern + preference | core/feedback/synthesize.ts + core/llm/prompts/decision-repair.ts |
✅ |
| Heuristic fallback(最高/最低 V 取 reflection) | synthesize.ts::templateDraft |
✅ |
写入 decision_repairs 表 |
core/storage/repos/decision_repairs.ts |
✅ |
| 挂到 PolicyRow | feedback.ts::attachRepairToPolicies 把 {preference, antiPattern} 塞进 policy.boundary 的 @repair {…} JSON 块 |
⚠️ 工作但是 hack — 应该是独立列 |
| PolicyDTO 透出 preference / antiPattern | memory-core.ts::parsePolicyGuidanceBlock |
✅ |
| PoliciesView 显示 | viewer/src/views/PoliciesView.tsx:243-256, 497-518 |
✅ |
| 写入 SkillRow.procedureJson.decisionGuidance | core/skill/packager.ts::buildProcedure |
✅ 已实现 — 用 draft.decisionGuidance 替换原硬编码空 |
| Skill crystallize prompt 输入 policy 的 @repair | core/llm/prompts/skill-crystallize.ts v2 + crystallize.ts::packPrompt::parseRepairBlock |
✅ 已实现 — 把 @repair {…} 提到 repair_hints,加上 counter_examples 一起喂给 LLM |
| SkillsView 显示 decisionGuidance | viewer/src/views/SkillsView.tsx::SkillDrawer |
✅ 已实现 — drawer 新增 "Decision guidance (prefer / avoid)" 段,列出双数组 |
| retrieval/injector 把 decision_guidance 注入到 agent prompt | core/retrieval/decision-guidance.ts::collectDecisionGuidance + injector.ts::renderDecisionGuidance |
✅ 已实现 — retrieval 拉 active policies, 按 sourceEpisodeIds 与召回的 trace 关联, 解析 @repair 块, 在注入包尾部渲染 "## Decision guidance" 段 |
→ 决策修复链路完整闭环:repair 的 preference / anti-pattern 三处都消费了:① 写入到 Skill 的 decisionGuidance 字段并随技能注入;② retrieval 时按 episode-policy 关联从 active policies 临时召回,独立成段注入到 agent prompt;③ PoliciesView + SkillsView 都展示。Agent 现在真正能感知到"吃一堑长一智"的教训。
§2.5 技能结晶五步
| Step | 算法描述 | 实现 | 状态 |
|---|---|---|---|
| 0 | 收集 Support Set(正例 / 反例 / 边界样本) | core/skill/evidence.ts::gatherEvidence 取高 V trace 作正例 |
⚠️ 只取正例,没区分反例(V<0)和边界样本(V≈0)。算法要求三类 |
| 0 | L3 作为 domain prior | — | ❌ crystallize 不接收 sourceWorldModelIds 对应的 worldModel 内容 |
| 1 | LLM 诱导草案 | core/skill/crystallize.ts + SKILL_CRYSTALLIZE_PROMPT |
✅ |
| 2 | 结构化解析 → ϕ/π/κ/η 四元组 | crystallize.ts::normaliseDraft + packager.ts::buildSkillRow |
⚠️ ϕ trigger 没单独字段;κ verification 完全缺失 |
| 3 | 双重检验 — 一致性检验 | core/skill/verifier.ts(coverage + resonance) |
✅ |
| 3 | 双重检验 — 执行增益检验(小规模实际部署) | — | ❌ 算法要求"在后续任务中实际部署 + 对比",目前只有被动 trialsAttempted/passed 计数 |
| 4 | Active vs Probationary | core/skill/lifecycle.ts |
✅ |
| 5 | 结晶后持续修订(强化 / Repair / Shrink / Retire / Rebuild) | lifecycle.ts 有 strengthen / archive;skill.ts 有 rebuild 分支 |
⚠️ 缺显式 Repair / Shrink |
§2.6 三层检索
- ✅ Tier 1(技能):
retrieval/tier1-skill.ts,按 η 阈值过滤 - ✅ Tier 2a(单步记忆):
retrieval/tier2-trace.ts,三路并行(structural / semantic / pattern / FTS),RRF 融合 - ✅ Tier 2b(子任务序列回放):
retrieval/tier2-trace.ts::rollupEpisodes(按 episode_id 拼接) - ✅ Tier 3(环境认知):
retrieval/tier3-world.ts,按 confidence + 领域标签 - ✅ MMR:
retrieval/mmr.ts - ✅ RRF:
retrieval/rrf.ts - ❌ decision_guidance 注入(§2.4.6 闭环断点)— 在 Tier 2 片段后追加
<anti_pattern>/<preference>段未实现
§3 evidence 全栈追踪能力对照
这是用户专门问到的"evidence 是否实现 + 用在哪",单独成节。
| 起点 | 能否找到底层 trace 证据? | 通过什么字段 / API |
|---|---|---|
| 一条 L2 经验 | ⚠️ 只能找到 episode(不到 trace) | PolicyRow.sourceEpisodeIds → 然后枚举每个 episode 的 traces。candidate_pool 在诱导阶段持有过具体 trace ids 但消费完就清 |
| 一条 L3 环境认知 | ✅ 每条 entry 直接挂 trace + policy ids | WorldModelStructureEntry.evidenceIds |
| 一条技能 | ✅ 直接挂 trace ids(migration 014) | SkillRow.evidenceAnchors: TraceId[] — packager 落 gatherEvidence 输出,cap 10,best-first |
前端显示:
- L2 PoliciesView:✅ 显示来源 episode 列表
- L3 WorldModelsView:✅ 显示来源 policy 列表 + ✅ entry 级 evidenceIds chips(含 trace + policy 跳转)
- Skill SkillsView:✅ 显示 source policy + source world model 跳转 chip + ✅ trace 级 evidence anchors 跳 MemoriesView
LLM prompt 是否要求输出 evidence?
- L2 induction prompt(v2):✅ 输出
support_trace_ids(但没存到 PolicyRow,仍是一处小缺口) - L3 abstraction prompt(v2):✅ 输出
evidenceIdsper entry,已存进WorldModelStructure+ UI 渲染 - Skill crystallize prompt(v2):✅ 不要求 LLM 输出(packager 直接持久化
gatherEvidence的输出)+ ✅ 持久化到SkillRow.evidenceAnchors
已修复(本仓库历史)
- L2 首次 induction gain 为负 → 永久 candidate(
core/memory/l2/l2.ts) - L3 对
policy.updated → active不敏感(core/memory/l3/subscriber.ts) - 任务轮次阈值缺失 → 单条消息也会被记成 completed(
core/reward/reward.ts+minExchangesForCompletion/minContentCharsForCompletion) - 多工具调用之间的"思考"丢失(
adapters/openclaw/bridge.tsthinkingBefore + 持久化进tool_calls_json) - 状态术语统一(
probationary/retired→candidate/archived,policies / skills / world_model 统一) — 已固化进001-initial.sql - trace.turn_id 列(前端"一轮一卡"聚合用,算法层不可见) — 已固化进
001-initial.sql - prompt v2 — L2 / L3 加 boundary 守卫,明确禁止"经验越界写成环境事实 / 环境认知越界写成动作建议"
- Decision guidance 全链路闭环(V7 §2.4.6):
- retrieval/injector 新增 "Decision guidance" 段(
core/retrieval/decision-guidance.ts+injector.ts::renderDecisionGuidance) - Skill crystallize prompt v2 读 policy
@repair+ counter examples - packager 真正落地
procedureJson.decisionGuidance(不再硬编码空) - SkillsView drawer 加 "Decision guidance" 段
- retrieval/injector 新增 "Decision guidance" 段(
- Skill
evidence_anchors持久化 + 显示(V7 §2.1):skills.evidence_anchors_json列(已固化进001-initial.sql)- SkillRow + SkillDTO 加
evidenceAnchors: TraceId[] - SkillsView drawer 渲染 trace chip 跳 MemoriesView
- L3 entry 级 evidenceIds 前端渲染(V7 §1.1):
- WorldModelDTO 暴露
structure: { environment, inference, constraints }三段式 - WorldModelsView drawer 加
<StructureSection>每个 entry 显示 evidence chips(trace 跳 MemoriesView,policy 跳 PoliciesView)
建议后续迭代(按 ROI 排序)
高 ROI(剩余闭环缺口)
-
revision 反馈触发 trace 回溯重算 — 算法 §0.2 / §2.4.3 核心
- relation classifier 识别 revision 后,派发
reward.rescheduled事件 - reward subscriber 重新打分,覆盖旧 V
- 影响:用户说"不对"时,整轮 trace 的 V 真的会被新的 R_human 覆盖(目前 reopen 后还是旧 V)
- relation classifier 识别 revision 后,派发
-
Skill crystallize 接收 L3 world model 作 domain prior — 算法 §2.5 step 1
packPrompt加载policy.sourceEpisodeIds → world_models referencing those policies的 body 段- prompt schema 加
domain_model: string输出字段 packager持久化到procedureJson.domainModel
-
L2 induction 持久化
support_trace_ids— 当前已经 LLM 输出但没存- PolicyRow 加
evidenceAnchors: TraceId[]列(新建002-policy-evidence-anchors.sql) induce.ts把 LLM 返回的support_trace_ids落进字段- PoliciesView drawer 加 trace 跳转 chip
- PolicyRow 加
中 ROI
-
Trace 手动改 value("标为正/负样本") — 算法 §2.4.5
- 后端:
updateTrace接受valuepatch(带 audit 字段) - 前端:MemoriesView 抽屉加按钮
- 后端:
-
Decision repair "同 context 下 V 分布对比" 触发路径 — §2.4.6 三种触发的最后一种
- 现在只有 burst + 用户文本两种触发;算法还要求"V 分布差 > δ"自动触发
- 实现:
feedback/subscriber.ts监听reward.updated,对刚收尾的 episode 跑一次 V 分布对比
低 ROI
- α_t heuristic 兜底:LLM 缺席时 α 至少 0.3
- Analytics 页面 R_human 三 axes 拆开显示
- Skill verification / scope 字段单独拆出(不再藏在 invocationGuide markdown 里)
- Skill 主动小规模 A/B 试用(把 trial 从被动累计改成主动派发)
- Skill
reliability.beta_posterior / success_rate暴露到 DTO + UI