数据截至 (上游 commit 38277815ed44)
自进化引擎:触发器作业与 FIX / DERIVED / CAPTURED
30 秒导读: 这是 OpenSpace 的灵魂章。别的 agent 框架把技能(SKILL.md)当成人写死的静态文件; OpenSpace 让技能自己进化。新版把"任务后进化"做成了作业流水线:任务收尾先把证据落进 EvidenceStore,
TriggerEngine按检查点(如task_session_persisted)创建持久化的 TriggerJob;EvolutionEngine.process_job把每个作业沿 建包 → 决策 → 准入 → 改稿 → 验证 → 行为评估 → 提交 分段处理;真正改文件的内核(SkillEvolver)依旧是"定向 diff、落盘验证、 失败把磁盘真实内 容喂回重试",并按 FIX/DERIVED/CAPTURED 三种语义把新版本连同"从谁演化来" 的血缘写进 SQLite 版本 DAG。
上游重构提示: 旧版"三触发器"(
process_analysis/process_tool_degradation/process_metric_check,入口在旧tool_layer.py)与确认门(_llm_confirm_evolution)、 定期体检(_diagnose_skill_health)、<EVOLUTION_COMPLETE>/<EVOLUTION_FAILED>令牌 已被移除/替换:触发统一改为 TriggerJob(ANALYSIS / QUALITY_SIGNAL / MANUAL),进化循环改用 结构化 finalization JSON 收敛(evolver.py:1252docstring)。分析器(analyzer.py)、改稿内核 (evolver.py的三模式与 apply-retry)、patch 模块、版本 DAG 内核保留。本章按新版源码重写。
本章属于 OpenSpace 系列。上游是主循环(任务怎么执行、finalize 怎么落证据) 和技能协议(技能怎么被披露/加载);下游边界是 工具接地层(工具质量信号如何变成 QUALITY_SIGNAL 触发)。全景见 index。
1. 这是什么(零基础也能懂)
一句话定义
自进化引擎 = 技能的"复盘 + 改稿"系统。 一次任务跑完,它像一个复盘教练:回看整场录像, 判断"这次用的攻略行不行",然后要么改攻略、要么写本进阶攻略、要么把刚摸索出的新打法记成一份新攻略。
它解决什么问题
人写的 SKILL.md 会过时、会不够用、会漏掉刚被 agent 现场摸索出来的好套路。手工维护跟不上。 自进化引擎让这三件事自动发生:
| 问题 | 现象 | 对应进化 |
|---|---|---|
| 技能坏了 / 过时 | 攻略里的 curl 参数格式变了、工具退化了 | FIX(原地修,同名新版本) |
| 技能不够强 | 能用但常常做不完,或两个技能该合成一个 | DERIVED(衍生新版,新目录) |
| 冒出新套路 | 这次任务没靠现成技能,却摸索出一条可复用的路子 | CAPTURED(捕获成全新技能) |
一句话直觉
把它想成 git for skills,而且提交者是 AI:每次进化都创建一个新的技能"版本节点",记录
它的父节点、改了什么(content_diff)、当时的完整快照(content_snapshot)。所有节点连成一张
版本 DAG(有向无环图),你随时能回溯一个技能"从哪进化来、改过几手"。
用起来什么样
这套系统对用户是透明的——它挂在任务收尾的 post-execution 阶段(runtime/app.py:1966
run_post_execution_tasks),任务一结束自动跑。日志里会看到技能被列出/发现/进化的痕迹,
进化出的新技能出现在返回值的 evolved_skills 里。一次任务,一批证据,零到多个 TriggerJob,
每个作业落成零到多个新的技能版本。
本节不碰代码细节;下面开始由浅入深往里钻。
2. 顶层全景(这条闭环怎么转)
怎么读这张图
从上到下是时间顺序:任务收尾落证据 → 触发器建作业 → 进化流水线逐段处理 → 版本入库。 左列是"触发与编排",右列是"流水线各段",改稿内核在底部被各段复用。
任务收尾 ExecutionFinalizer.finalize [execution_finalizer.py:23]
│ 发证据(task_finished_pre_persist / task_session_persisted)
│ 扫 session/skill/tool-quality/quality-signal 四类 checkpoint
▼
① 触发阶段 TriggerEngine [triggers/engine.py:25]
· evaluate_checkpoint("task_session_persisted") → ANALYSIS 作业
(AnalysisTriggerPolicy [triggers/policies.py:44])
· QUALITY_SIGNAL 作业(工具失败/语义问题 [policies.py:192-200])
· MANUAL 作业(手动请求 [policies.py:116])
│ 作业持久化在 TriggerStore(可认领/可恢复)
▼
② 编排阶段 runtime 侧认领并执行
run_post_execution_tasks → drain_evolution_jobs [runtime/app.py:1966/:2068]
· claim_jobs / claim_next(多 worker 安全) [triggers/store.py:372/:294]
▼
③ 流水线 EvolutionEngine.process_job [evolution/engine.py:103]
packet(证据打包) ─▶ decision(分析+决策) ─▶ admission(准入)
─▶ authoring(改稿) ─▶ validate ─▶ behavior eval ─▶ commit(入库)
│ │改稿内核:SkillEvolver [evolver.py:364]
│ │ _evolve_fix / _evolve_derived / _evolve_captured
│ │ _run_evolution_loop + _apply_with_retry
▼ ▼
patch.py 落盘 store.py:evolve_skill(1476)
fix/derive/create 新版本入库 + 旧版失活 + lineage 血缘
部件一句话职责
| 部件 | 干什么 | 在哪个文件 |
|---|---|---|
EvidenceStore | 证据落盘与检索,触发器的输入源 | openspace/skill_engine/evidence/store.py:312 |
TriggerEngine / TriggerStore | 按策略建 TriggerJob、认领、完成 | openspace/skill_engine/triggers/engine.py:25、triggers/store.py:65 |
EvolutionEngine | 作业流水线(mode 分级:autonomous/fix_only/audit_only) | openspace/skill_engine/evolution/engine.py:75 |
DecisionEngine | 在证据包上跑分析器,产出可审计决策 | openspace/skill_engine/decision/engine.py:34 |
EvolutionAdmission | 准入门:该决定要不要真的动技能 | openspace/skill_engine/evolution/admission.py:146 |
SkillEvolver(authoring 后端) | 改稿内核:三种模式的定向改写 | openspace/skill_engine/evolver.py:364 |
EvolutionValidator / SkillBehaviorEvaluator | 结构校验 / 行为评估(带修订回路) | evolution/validator.py:129、evolution/behavior_eval.py:232 |
EvolutionCommitter | 提交:写库、备份、失活旧版 | openspace/skill_engine/evolution/engine.py:718 |
ExecutionAnalyzer | 复盘一次执行,产出 ExecutionAnalysis | openspace/skill_engine/analyzer.py:307 |
patch 模块 | 把改动文本(FULL/DIFF/PATCH)真正应用到技能目录 | openspace/skill_engine/patch.py |
SkillStore | SQLite 持久化 + 版本 DAG(父子血缘、质量计数) | openspace/skill_engine/store.py:435 |
types | 全套数据模型 | openspace/skill_engine/types.py |
主线走一遍(高层)
任务收尾时 ExecutionFinalizer 先把证据落盘并扫描各 checkpoint;run_post_execution_tasks
(runtime/app.py:1966)让 TriggerEngine.evaluate_checkpoint 建 ANALYSIS 作业,随即
drain_evolution_jobs(runtime/app.py:2068)认领并逐个交给 EvolutionEngine.process_job;
质量侧的 maybe_evolve_quality(runtime/app.py:2319)按全局执行计数补发 QUALITY_SIGNAL 作业。
作业处理产物 evolved_skill_records 汇进 final_result["evolved_skills"] 返回。
3. 触发链第一段:把一次执行复盘成结构化结论
这一节讲分析阶段:录制轨迹进,
ExecutionAnalysis出。它现在被包进DecisionEngine作为流水线的 decision 段,但分析器本体基本未变。
3.1 它要解决的小问题
主循环只留下一堆录制文件(录像),不 是结论。要进化,先得有人回看录像、下判断:
任务完成了吗?哪个技能被真正用上了?哪个工具出了问题?这份判断就是
ExecutionAnalysis(types.py:334)。
3.2 入口与去重
ExecutionAnalyzer.analyze_execution(analyzer.py:356)是入口。防御逻辑保留:
录制目录不存在就跳过(analyzer.py:381-383);一个任务只分析一次——
load_analyses_for_task 查到已有分析直接返回(analyzer.py:388-392),避免重复烧钱;
另有低信号跳过(_should_skip_low_signal_analysis,analyzer.py:399)。
3.3 读录制轨迹:把录像整理成上下文
_load_recording_context(analyzer.py:675)把录制目录里的文件拼成 context dict
(metadata.json / conversations.jsonl / 工具调用记录)。分析不只看 agent 说了什么,
还看它实际做了什么——两路证据交叉,判断更准。
3.4 构造分析 prompt:只喂相关信息
_build_analysis_prompt(analyzer.py:963)把 context 压成 prompt,做预算控制:
对话日志走优先级截断(conversation_formatter.format_conversations,用户指令与最后一轮
回复永不截断)、技能正文超长截断并提示用 read_file 看全文。
3.5 分析循环与解析
_run_analysis_loop(analyzer.py:1260)是个小型 agent 循环:多数一轮出 JSON,必要时可调工具
查证;_parse_analysis(analyzer.py:1528)把 JSON 转成强类型 ExecutionAnalysis。
_correct_skill_ids(analyzer.py:102)按"同名前缀 + 编辑距离 ≤ 阈值"纠正 LLM 复述错的
hex 后缀。分析入库走 store.record_analysis(store.py:1441,调用点 analyzer.py:422),
tool_issues 会汇入证据层(QUALITY_SIGNAL 的燃料,见 §4.2)。
4. 触发引擎:从证据到 TriggerJob
旧版三个散装入口被统一的"证据 → 策略 → 作业"模型取代。触发器回答"什么时候、为什么改"。
4.1 证据先行
一切触发都以 EvidenceStore(evidence/store.py:312)里的证据为基础:任务收尾发的
task_finished 事件、会话持久化 checkpoint 扫描、技能 listed/invoked 事件(第2章)、
工具执行结果(evidence/tool_adapter.py)都是证据。TriggerEngine 用
manifest watermark(单调递增水位)保证"每条证据最多触发一次"。
4.2 三类触发
default_policies(triggers/policies.py:216)装三类策略:
| 触发类型 | 何时产生 | 策略 |
|---|---|---|
| ANALYSIS | task_session_persisted checkpoint(任务会话已持久化) | AnalysisTriggerPolicy(policies.py:44),幂等键 analysis:task_finished:<session>:<task>(policies.py:84-88) |
| QUALITY_SIGNAL | 工具失败/LLM 语义问题累积成信号 | 映射表 policies.py:192-200(tool_failure_affects_skill、tool_semantic_issue) |
| MANUAL | 用户/宿主手动请求进化 | ManualTriggerPolicy(policies.py:116) |
ANALYSIS 作业还会带任务树:_source_task_ids_for_task_tree 把子任务(多 agent)的
执行一并圈进分析范围(policies.py:62-70)。