
Byted Ark Evolve
- 2 installs
- 408 repo stars
- Updated August 3, 2026
- volcengine/agentkit-samples
Agent self-evolution system that collects feedback signals, stores execution trajectories, proposes workspace-file mutations, and generates HTML reports.
About
Runs an agent self-evolution loop that gathers signals and golden/correction trajectories, analyzes patterns, and proposes user-confirmed mutations to workspace files. A developer uses it via /evolve to improve agent behavior and persist good patterns across sessions.
- Pareto constraint blocks any mutation that regresses quality/reliability/efficiency
- Local-only storage, no network calls, git-committed audit trail per apply
Byted Ark Evolve by the numbers
- 2 all-time installs (skills.sh)
- Ranked #13,957 of 16,556 AI & Agent Building skills by installs in the Skillselion catalog
- Data as of Aug 4, 2026 (Skillselion catalog sync)
npx skills add https://github.com/volcengine/agentkit-samples --skill byted-ark-evolveAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 2 |
|---|---|
| repo stars | ★ 408 |
| Last updated | August 3, 2026 |
| Repository | volcengine/agentkit-samples ↗ |
What it does
Agent self-evolution system that collects feedback signals, stores execution trajectories, proposes workspace-file mutations, and generates HTML reports.
Files
Evolution Skill
Agent 自进化系统。进化单位是整个 Agent(Identity + Context + Protocol + Capability + Runtime)。
核心原则
1. Quality > Reliability > Efficiency > Cost — 不允许牺牲质量换效率 2. 帕累托约束 — 任何 mutation 如果导致某个维度退化,BLOCK 并请用户决策 3. 写入即生效 — 用户确认变更后写入 workspace 文件,下次 session 即可加载新配置 4. 先对齐再执行 — 进化方案必须先呈现给用户确认
数据安全与权限边界
- 本地存储:所有运行时数据(信号、轨迹、变异、报告)写入用户本地
~/.{*claw*}/workspace/evolution-data/,不上传任何外部服务。 - 无外网调用:脚本不发起 HTTP/socket 请求(可代码层验证:无
requests/urllib/http.client/socket等导入用于网络通信)。 - 变更前置确认:mutation 写入 workspace 文件前,必须由用户在对话中显式接受提案;用户未接受前不会修改任何文件。
- 作用范围:写入仅限
~/.{*claw*}/workspace/目录及其子目录;不访问/etc/、~/.ssh/、~/.aws/等敏感路径。 - 可审计:每次 apply 通过 git commit 留痕,用户可随时
git log查看完整变更历史。 - 可追溯:每条 mutation 在 SQLite DB 中保留 source(evolution / user-direct / snapshot-diff)、决策状态、时间戳。
- 可禁用:删除
evolution-data/目录或卸载 skill 即完全停止;删除~/.{*claw*}/workspace/不会影响其他 skill。
数据目录
Runtime 适配:workspace 根路径自动探测~/.{*claw*}/workspace/(优先.arkclaw→.openclaw→ 其他含claw的目录),也可用CLAW_WORKSPACE环境变量显式指定。新装默认创建在~/.arkclaw/workspace/。
evolution-data/
├── file-registry.json ← 初始化扫描结果
├── evolution.db ← SQLite(信号、变异、轨迹)
├── snapshot.json ← Workspace 文件 hash 快照(兜底变更检测)
├── trajectories/
│ ├── golden/ ← 正确执行轨迹
│ └── corrections/ ← 错误→修正对
├── dashboard.html ← Dashboard 页面(dashboard-render.py 自动生成)
└── reports/
├── evolution-*.json ← 进化报告数据(JSON)
└── evolution-*.html ← 进化报告页面(渲染后)References(按需读取,不随 session 加载)
| 文件 | 内容 | 何时读取 |
|---|---|---|
references/layer-model.md | 5 层模型 + 归因规则 | 进化分析时 |
references/pareto-rules.md | 帕累托约束 + 验证标准 | 进化分析时 |
references/file-semantic-map.md | 已知文件→层/语义/风险映射 | 初始化 + 归因时 |
references/init-rules.md | 状态判定规则 + 占位符列表 | 初始化时 |
references/signal-types.md | 信号识别规则 + 示例 | 信号收集时 |
references/trajectory-templates.md | 轨迹存储格式 + 示例 | 轨迹写入时 |
references/evolution-steps.md | 进化分析详细流程(Step 0-7) | /evolve 执行时 |
references/onboarding.md | 新手引导内容 | 首次安装时 |
references/report-schema-example.json | 报告 JSON 数据格式示例 | 生成报告时 |
references/dashboard-schema-example.json | Dashboard JSON 数据格式示例 | 生成 Dashboard 时 |
---
零、初始化(/evolve init)
首次安装或 Agent 版本变化时执行。
触发条件:
evolution-data/不存在file-registry.json中agent_version与当前不一致
首次安装流程(建议连贯执行)
Step A — 新手引导(纯展示,无需用户确认)
检测到首次安装时,建议直接展示以下引导内容(无需额外询问"是否要看引导"):
欢迎使用进化系统
>
这个系统让我能从你的反馈中学习。简单来说:
- 你日常的纠正、建议会被自动记录
- 攒够一定数量后,我会分析这些反馈并提出改进方案
- 所有改动必须经你确认才会执行
>
可用命令
>
| 命令 | 说明 |
|------|------|
| /evolve | 手动触发一次进化分析 || /evolve init | 重新初始化 || /evolve scan | 扫描历史对话提取反馈 || /evolve dashboard | 生成进化 Dashboard || /evolve help | 查看完整引导 |>
现在开始初始化扫描——扫描完成后,你还可以选择扫描历史对话来加速学习。
展示完毕后建议进入 Step B,无需用户额外确认。
详细引导内容(用户说 /evolve help 时展示完整版):读取 references/onboarding.md
Step B — 初始化扫描(涉及文件写入,需用户确认)
python skills/byted-ark-evolve/scripts/workspace-init.py1. 全量扫描 workspace(排除 .git/ node_modules/ __pycache__/ evolution-data/) 2. 按 references/init-rules.md 判定每个文件状态(evolvable / user-owned / skill-owned / needs_review) 3. 按 references/file-semantic-map.md 标注已知文件语义,未知文件标 needs_review 4. 生成 evolution-data/file-registry.json 5. 如果 DB 不存在,执行 db-init.py 6. 输出摘要
needs_review 文件:进化分析时由 Agent 读取并补分类,写回 registry。
Step C — 历史对话扫描(可选,需用户确认)
Step B 完成后,提供历史对话扫描选项:
# 先估算成本
python skills/byted-ark-evolve/scripts/scan-history.py estimate --days 7
python skills/byted-ark-evolve/scripts/scan-history.py estimate --days 30展示给用户:
检测到 N 段历史对话。是否要扫描过去的对话来提取已有的反馈信号?
这可以让进化系统从你已有的使用习惯开始学习,而非从零开始。
>
1. 扫描最近 7 天(N 段对话,预计消耗 ~X tokens,约 $Y)
2. 扫描最近 30 天(N 段对话,预计消耗 ~X tokens,约 $Y)
3. 跳过,从零开始
>
你也可以随时用 /evolve scan 手动触发。用户选择后:
# 提取对话内容
python skills/byted-ark-evolve/scripts/scan-history.py extract --days 7Agent 逐段对话读取,按 references/signal-types.md 规则识别信号,调用 signal-record.py 记录(标记 context 为 history-scan)。
完成后展示摘要:"从 X 段对话中提取了 Y 条反馈(N 条纠正、M 条建议…)"
如果用户选择跳过,直接结束初始化。
---
〇、User-Direct 变更追踪
用户直接指令 Agent 修改 workspace 文件时,自动记录到 evolution.db。
追踪层 A(可选 Hook):用户启用 PostToolUse Hook 后,监听 Edit/Write 事件,目标在 workspace 内则记录 source='user-direct'(Hook 仅观察执行结果,不阻断工具调用)。 追踪层 B(快照兜底):进化分析启动时对比 snapshot.json,捕获 Hook 漏掉的变更(手动编辑、Bash 写入等),记录为 source='snapshot-diff'。
Mutation source 三种值:
evolution— 进化分析产生(走 proposed→approved→applied 流程)user-direct— Hook 实时捕获(跳过 proposed/approved,直接 applied)snapshot-diff— 快照对比发现(缺失意图,仅标注"检测到变更")
---
一、信号收集
在日常对话中,识别用户反馈信号并记录到 SQLite。
信号类型:correction / negative / positive / suggestion / preference / clarification Layer 归因:identity / context / protocol / capability / runtime
详细识别规则和示例:读取 references/signal-types.md
记录:
python skills/byted-ark-evolve/scripts/signal-record.py \
--type correction --layer protocol --severity high \
--text "用户原话" --context "当时在做什么"当用户说"记住这个"、"以后别这样"等,建议及时记录为信号。
---
二、轨迹存储
两种轨迹:
- Golden:做对了 →
evolution-data/trajectories/golden/ - Correction:做错了→修正 →
evolution-data/trajectories/corrections/
存储格式和模板:读取 references/trajectory-templates.md
执行任务前,检索相关 correction trajectory,主动复述修正要点。
---
三、Gate 检查
python skills/byted-ark-evolve/scripts/gate-check.py自动触发条件(满足任一):
- ≥5 条 correction/negative/clarification 信号
- ≥3 条 high severity 信号
- 同一 layer ≥3 条信号
- ≥7 天无进化且有新信号
约束:24h 冷却 + 饱和检测。 手动触发:/evolve 跳过 Gate。
---
四、进化分析(/evolve)
推荐流程:所有进化变更通过 orchestrator pipeline 执行,以保证 DB 状态与变更可追溯。
- 不建议 agent 自行分析信号后直接修改 workspace 文件
- 不建议跳过 DB 记录环节(Python sqlite3 是标准库内置模块,无需额外工具)
- 标准流程:
orchestrator.py timer-review→ 生成提案 JSON →ingest_outputs写入 DB → 用户确认 →apply-proposal应用变更 - 原因:绕过 DB 记录会导致 Dashboard 无数据、变更不可追溯、验证流程断裂
编排命令
# /evolve 手动触发(跳过 Gate,≥1 信号即可,完整 7 步):
python skills/byted-ark-evolve/scripts/orchestrator.py timer-review --skip-gate --worker-mode agent
# Gate 自动触发(阈值达标时):
python skills/byted-ark-evolve/scripts/orchestrator.py timer-review --worker-mode agent
# session 开始时自动检查:
python skills/byted-ark-evolve/scripts/orchestrator.py session-start
# 用户确认提案后应用:
python skills/byted-ark-evolve/scripts/orchestrator.py apply-proposal --group <proposal_group_id>说明:
--worker-mode agent:真实调用openclaw agent --local完成 review worker 分析--worker-mode mock:用于本地稳定测试,不依赖模型调用- orchestrator 会生成
pending-evolution.json/daily-digest.json,但 DB 仍是主状态源
Gene 集成(v0.3.1,本地静态库)
Gene 是 mutation 的标准化模板库。本版本随 skill 包发布静态库 references/gene-library.json,无云端调用。
边界:
- Gene 不是分析器,不负责根因归因
- Gene 不是执行器,不直接修改任何文件
- Gene 不是本地规则匹配器,本地脚本不负责做 gene 相关性判定
职责分工:
- Agent / subagent:读取信号、日志、轨迹,完成归因分析
- Gene 静态库:提供 mutation 模板候选(id / summary / pattern_key / rule_text)
- Evolution skill:编排 load → Agent 判断 gene 候选 → 生成 mutation → 用户确认 → 执行
何时调用 Gene
以下场景必须进入 Gene 流程: 1. /evolve 手动触发后,在 Step 2 归因完成后进入 Step 2.5 Gene 候选筛选 2. 自动进化触发后,在 mutation 设计前进入 Step 2.5 3. 用户说"记住这个模式 / 下次也这么做"时,优先浏览 gene 库,判断是否已有可复用模板
Gene 命令
python skills/byted-ark-evolve/scripts/gene.py --list
python skills/byted-ark-evolve/scripts/gene.py --show <gene_id>
python skills/byted-ark-evolve/scripts/gene.py --summary规则:
- 库随 skill 版本发布(更新需升级 skill 版本),无
--fetch命令 - Agent 在 Step 2.5 阅读 signal + attribution + gene 内容,判断"哪些 gene 的方案如果提前应用,能避免当前负向反馈或 error 再次发生"
- Step 3 中,Agent 基于选中的 gene template 生成 mutation proposal
详细流程(Step 0-7,含 Step 2.5 Gene 候选筛选):读取 references/evolution-steps.md
概要:快照 Diff → 扫描信号 → 轨迹聚类(Skill 候选检测) → 归因分析 → Gene 候选筛选(静态库 + Agent 判断) → 设计 Mutation → 用户确认 → 组装 JSON + 渲染报告/Dashboard → 输出进化总结 → 标记已处理 → 更新快照
轨迹 → Skill 涌现(Step 1.5)
进化分析时自动检测轨迹中的可提取模式:
- Golden 轨迹同类 ≥3 条 → 建议提取为独立 skill
- Correction 轨迹同因 ≥2 条 → 建议加入防护规则
流程:检测 → 呈现候选给用户 → 用户确认 → 调用 skill-creator 生成。 如果用户未安装 skill-creator,提示安装。
用户确认交互规范
设计完 mutation 后,必须先呈现方案给用户确认,未确认前不执行任何写入。
展示格式(逐条列出):
### 进化方案(共 N 条变更)
1. 📝 [AGENTS.md] — 新增「中文编码规范」
- 层归因:Protocol
- 变更类型:追加(user-owned 文件,仅允许追加)
- 帕累托检查:✅ 通过
- 摘要:在末尾新增 section,规定中文 API 调用使用 Python
2. 📝 [SOUL.md] — 修改「任务执行流程」
- 层归因:Identity
- 变更类型:修改(evolvable 文件)
- 帕累托检查:✅ 通过
- Before: 「直接执行用户指令」
- After: 「先检索相关 correction,再执行」
以上 N 条变更是否执行?你可以:
- 全部接受
- 全部拒绝
- 逐条选择(如"接受 1,拒绝 2")关键规则:
- 每条 mutation 必须列出目标文件、变更类型、帕累托结果
- user-owned 文件特别标注"仅追加"
- 用户明确回复前,不执行任何写入操作
- 被拒绝的 mutation 标记为
rejected,保留记录
进化完成总结
报告生成后,在对话中直接输出一段自然语言总结(不是文件路径,是让用户直接看懂的摘要):
本次进化处理了 X 条反馈,产生 Y 条待处理提案/变更:
- [AGENTS.md] 新增了「中文编码规范」— 因为你 4 次提到不要用 curl 发中文
- [SOUL.md] 调整了任务执行流程 — 增加了执行前检索修正记录的步骤
被拒绝:W 条
完整报告:evolution-data/reports/evolution-<date>.html
下一步:当你再次遇到相关场景时,我会观察这些改进是否生效。规则:
- 用自然语言,不用术语(不说 mutation / signal / layer)
- 每条变更说明"改了什么"+"为什么改"
- 告知报告路径,但不以路径为主要输出
- 提及后续验证计划
归因时参考 references/layer-model.md + evolution-data/file-registry.json 写入时参考 file-registry.json 中的 write_policy(evolvable→section-edit / user-owned→append-only) 帕累托检查参考 references/pareto-rules.md
报告生成流程(模板方式)
1. 组装报告数据为 JSON(参考 references/report-schema-example.json) 2. 写入 evolution-data/reports/evolution-<date>.json 3. 调用 python scripts/report-render.py <json> --output <html> 渲染 HTML 4. (可选)组装 Dashboard JSON(参考 references/dashboard-schema-example.json)→ dashboard-render.py
Agent 只负责填写纯数据 JSON,Python 脚本负责所有条件渲染逻辑(如:有无被拒绝的变更、是否饱和、高严重度信号展示等)。
---
Pending Proposal 生命周期(v0.2.2)
pending-evolution.json 的定位是给人看的待处理提案队列,不是“第二天早上专属提醒”。
规则:
- 夜间 timer review 只生成 proposal,不自动 apply
- 只要 proposal 仍然有效且未被用户处理,后续任意一次
session_start都应该可以再次展示 - 展示状态与决策状态分离:
pending -> presented -> accepted/rejected/stale/superseded pending-evolution.json/daily-digest.json是导出层;DB 才是主状态源
推荐命令:
python skills/byted-ark-evolve/scripts/pending-evolution.py export
python skills/byted-ark-evolve/scripts/pending-evolution.py show
python skills/byted-ark-evolve/scripts/pending-evolution.py present --group <proposal_group_id>
python skills/byted-ark-evolve/scripts/pending-evolution.py decide --group <proposal_group_id> --decision accepted
python skills/byted-ark-evolve/scripts/pending-evolution.py stale --older-than-hours 72五、验证
验证状态:待验证 → 已观察 → 已验证 / 已复发 / 部分生效
状态说明:
- 待验证:刚提出,等待在真实场景中验证
- 已观察:初步通过 1 次,但不可完全信赖
- 已验证:≥3 次跨 session 无复发,确认行为改变
- 已复发:验证后再次违反,需要加固
- 部分生效:部分场景有效,部分未覆盖
详细规则和 credit 权重:读取 references/pareto-rules.md 验证标准章节
---
六、快速命令
| 用户说 | Agent 做 |
|---|---|
/evolve | 执行 orchestrator.py timer-review --skip-gate --worker-mode agent,完整 7 步进化(≥1 条信号即可,不受 Gate 阈值和冷却限制) |
/evolve init | 首次安装时执行:初始化扫描 + 生成文件注册表 |
/evolve scan | 扫描历史对话,提取已有的反馈信号 |
/evolve detect | 批量检测历史对话中的信号候选(scan-history.py detect) |
/evolve dashboard | 生成并打开 Dashboard(dashboard-render.py → evolution-data/dashboard.html) |
/evolve help | 展示新手引导(读取 references/onboarding.md) |
| "记住这个模式" | 记录 positive 信号,作为后续进化分析的正向参考 |
| "以后别这样做" | 记录 correction 信号,标记需要改进的行为 |
| "进化状态" | 快速查看:query.py dashboard(文本);完整页面:组装 JSON → dashboard-render.py(HTML) |
| "gene 列表 / gene 概况" | 调用 gene.py --list / gene.py --summary |
| "进化报告" | 组装 JSON(参考 references/report-schema-example.json)→ report-render.py |
---
七、查询工具
python skills/byted-ark-evolve/scripts/query.py dashboard # Dashboard
python skills/byted-ark-evolve/scripts/query.py signals --unprocessed # 未处理信号
python skills/byted-ark-evolve/scripts/query.py signals --layer protocol # 按层
python skills/byted-ark-evolve/scripts/query.py mutations --status pending # 变异
python skills/byted-ark-evolve/scripts/query.py mutations --source user-direct # 用户直接变更
python skills/byted-ark-evolve/scripts/query.py trajectories # 轨迹统计
python skills/byted-ark-evolve/scripts/query.py gene-matches # 最近一次 gene 候选筛选结果
# 信号候选检测(v0.2.9 新增)
python skills/byted-ark-evolve/scripts/scan-history.py detect --days 7 # 检测近 7 天对话中的信号候选
python skills/byted-ark-evolve/scripts/scan-history.py detect --min-confidence high # 仅高置信候选Apply 闭环(v0.2.4)
用户接受提案后,建议通过以下命令完成应用,以保证 git commit / snapshot / dashboard 同步更新。
python skills/byted-ark-evolve/scripts/apply-proposal.py --group <proposal_group_id>该命令会自动完成全部闭环操作: 1. 应用该 group 下的 mutations 到目标文件 2. 执行 git commit(每个 proposal_group 一次 commit) 3. 调用 snapshot save 4. 写回 review/result 到 DB 5. 重新生成 dashboard.html
也可通过 orchestrator 调用(效果相同):
python skills/byted-ark-evolve/scripts/orchestrator.py apply-proposal --group <proposal_group_id>Dashboard 固定模板(v0.2.5+)
Dashboard 采用模板与数据分离架构,确保样式固定、不受模型影响。
架构
| 文件 | 职责 | 更新方式 |
|---|---|---|
scripts/dashboard-template.html | 固定 HTML/CSS/JS 模板 | 仅开发者手动迭代 |
scripts/dashboard-render.py | 查 DB + references/gene-library.json → JSON → 注入模板 | orchestrator 自动调用 |
evolution-data/dashboard.html | 最终输出,用户浏览器打开 | 每次 pipeline 自动生成 |
使用
# 手动生成
python skills/byted-ark-evolve/scripts/dashboard-render.py --db evolution-data/evolution.db --data-dir evolution-data/
# 自动生成(timer-review / apply-proposal 完成后自动触发)三个 Tab
- Overview:全局统计、趋势图、改动验证、层分布
- Activity:日期选择器 + 单次进化报告详情(来源反馈、改动 diff、状态)
- Genes:基因表格 + 手风琴展开关联改动
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for describing the origin of the Work and
reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Support. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or support.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright 2026 Beijing Volcano Engine Technology Co., Ltd.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
byted-ark-evolve
Agent self-evolution skill for OpenClaw / agentskills.io. Collects feedback signals from user interactions, accumulates execution trajectories (golden + correction pairs), and proposes mutations to your workspace files (SOUL.md / AGENTS.md / TOOLS.md / USER.md / MEMORY.md) through a Pareto-checked, user-approved evolution pipeline.
核心概念
- Signal — 从用户反馈中提取的反馈信号(correction / negative / positive / suggestion / preference / clarification)
- Trajectory — Golden(成功执行)/ Correction(错误→修正)对,作为后续进化的训练样本
- Mutation — 对 workspace 文件的建议变更,所有变更必须用户确认才会执行
- Pareto 约束 — 任何 mutation 如果导致 Quality / Reliability / Efficiency / Cost 维度退化,BLOCK 并请用户决策
- Gene — Mutation 标准化模板库(v0.3.1 起本地静态打包,无云端依赖)
快速开始
1. 首次安装初始化
/evolve init扫描 workspace 文件、生成 file-registry、初始化 SQLite DB(~/.{runtime}/workspace/evolution-data/evolution.db (runtime: arkclaw / openclaw / 自动检测))。
2. 日常使用
正常工作即可,agent 会自动从你的反馈中提取信号。也可以手动:
| 你说 | 效果 |
|---|---|
| "记住这个模式" | 存为 golden trajectory |
| "以后别这样做" | 存为 correction signal |
/evolve | 触发完整进化分析 + 报告 |
/evolve dashboard | 生成 HTML Dashboard |
/evolve scan | 扫描历史对话提取已有反馈 |
3. 查询状态
python skills/byted-ark-evolve/scripts/query.py dashboard
python skills/byted-ark-evolve/scripts/query.py signals --unprocessed
python skills/byted-ark-evolve/scripts/query.py mutations --status pending
python skills/byted-ark-evolve/scripts/gene.py --summary文件结构
byted-ark-evolve/
├── README.md # 本文件
├── SKILL.md # 主入口与触发规则(agent 加载点)
├── scripts/ # 19 个 Python 脚本(纯 stdlib,零外部依赖)
│ ├── orchestrator.py # /evolve 主流程(timer-review / session-start / apply-proposal)
│ ├── workspace-init.py # 初始化扫描 + file-registry 生成
│ ├── apply-proposal.py # 应用通过审核的 mutation(含 git commit)
│ ├── dashboard-render.py # 生成 dashboard.html
│ ├── gene.py # 静态 gene 库读取(list / show / summary)
│ ├── query.py # 查询 signals / mutations / trajectories
│ ├── signal-record.py # 记录信号到 SQLite
│ ├── scan-history.py # 扫描历史对话提取信号
│ └── ...
├── references/ # 详细规则文档(按需读取,不随 session 加载)
│ ├── layer-model.md # 5 层归因模型(Identity / Context / Protocol / Capability / Runtime)
│ ├── pareto-rules.md # 帕累托约束 + 验证标准
│ ├── signal-types.md # 信号识别规则
│ ├── evolution-steps.md # 进化分析详细流程(Step 0-7)
│ ├── gene-contract.md # Gene 库格式与使用约定
│ ├── gene-library.json # 静态 gene 模板库(v0.3.1 起本地打包)
│ └── ...
└── test-conversations/ # 学习样本对话数据存储
所有运行时数据写入 ~/.{runtime}/workspace/evolution-data/:
evolution-data/
├── evolution.db # SQLite(signals / mutations / reviews / trajectories / gene_matches)
├── file-registry.json # workspace 文件分类注册表
├── snapshot.json # 文件 hash 快照(兜底变更检测)
├── dashboard.html # 自动生成的 Dashboard
├── pending-evolution.json
├── daily-digest.json
├── trajectories/{golden,corrections}/
├── reports/ # 进化报告(JSON + HTML)
└── tmp/ # 临时 workset / candidates系统要求
- Python 3.9+(脚本使用 dataclass / type hints / pathlib)
- SQLite 3(Python stdlib
sqlite3) - 操作系统:Linux / macOS / Windows(脚本含 Windows UTF-8 修复)
- 零外部 Python 依赖
重要原则
1. Quality > Reliability > Efficiency > Cost — 不允许牺牲质量换效率 2. 写入即生效 — 修改 workspace 文件后,下次 session 立刻生效 3. 先对齐再执行 — 进化方案必须先呈现给用户确认 4. 所有进化变更必须通过 orchestrator pipeline 执行 — 禁止 agent 自行修改 workspace 文件 5. 所有 mutation 须经用户确认 — 即使通过帕累托检查也不自动 apply
详细原则与规则请阅读 SKILL.md 与 references/ 目录。
v0.3.1 主要改动
- Gene 库本地化:删除云端
--fetch/ API key / endpoint 配置,改为打包静态references/gene-library.json(64 gene) - 路径统一:所有 inline 命令使用
skills/byted-ark-evolve/ - 文件行尾统一为 LF(修复 Linux shebang 兼容性)
版本
v0.3.1 — 2026-04
{
"updated_at": "2026-03-12",
"date_range": {
"start": "2026-03-01",
"end": "2026-03-12"
},
"stats": {
"evolution_events": 9,
"total_signals": 66,
"unprocessed": 6
},
"gate": {
"should_evolve": true,
"message": "6 条未处理反馈(含 1 条高优先级),建议运行 /evolve"
},
"timeline": [
{
"date": "2026-03-12",
"description": "Agent 进化:6 信号 → 2 mutation(AGENTS.md + SOUL.md)"
},
{
"date": "2026-03-09",
"description": "Agent 进化:10 个 mutation,覆盖 6 session"
},
{
"date": "2026-03-08",
"description": "GitHub Trend Observer v0.1.0 → v0.2.0"
},
{
"date": "2026-03-05",
"description": "Agent 进化:3 项 Judgment 进化"
},
{
"date": "2026-03-04",
"description": "Agent 进化:6 项进化"
},
{
"date": "2026-03-01",
"description": "xhs v0.1→v0.2 + Evolution Coach v0.2.0"
}
],
"verification": {
"needs_attention": [
{
"name": "先建任务再执行",
"layer": "protocol",
"status": "regression",
"note": "03-05 通过 → 03-06/07 再次违反"
},
{
"name": "信号收集 Hook v2",
"layer": "capability",
"status": "partial",
"note": "手动运作;Hook 未部署"
}
],
"pending": [
{"name": "先调研再提问", "status": "pending", "date": "03-12"},
{"name": "记忆访问边界", "status": "pending", "date": "03-12"},
{"name": "反 AI 味检查单", "status": "pending", "date": "03-09"},
{"name": "验证标准升级", "status": "pending", "date": "03-09"},
{"name": "Session 开场任务检查", "status": "pending", "date": "03-09"}
],
"verified_observed": [
{"name": "Definition of Done", "status": "observed"},
{"name": "流程遵从规则", "status": "observed"},
{"name": "方案 trade-off 透明化", "status": "observed"},
{"name": "Session 结束检查单", "status": "observed"}
]
},
"signals": {
"sparkline": [
{"date": "03-01", "count": 8},
{"date": "03-02", "count": 15},
{"date": "03-03", "count": 8},
{"date": "03-04", "count": 19},
{"date": "03-05", "count": 9},
{"date": "03-06", "count": 6},
{"date": "03-07", "count": 3},
{"date": "03-09", "count": 6},
{"date": "03-12", "count": 6}
],
"recent": [
{"date": "03-12", "count": 6, "source": "evolution skill 测试"},
{"date": "03-09", "count": 6, "source": "multi-agent 系统设计"},
{"date": "03-04", "count": 19, "source": "2 sessions"}
],
"older_summary": "03-01: 8 · 03-02: ~15 · 03-03: ~8 · 03-05: 9 · 03-06: 6 · 03-07: 3"
},
"capabilities": {
"soul_principles": ["先对齐再执行", "不经确认不改动", "行动范围匹配", "Session 结束检查单"],
"strengths": ["搜索分析", "从零搭建基础设施"],
"weaknesses": ["多系统联调", "中文 prose 写作"],
"moderate": ["流程遵从", "基础设施诊断"]
},
"report_archive": [
{"date": "2026-03-12", "summary": "2 mutation · 6 信号"},
{"date": "2026-03-09", "summary": "10 mutation · 15 信号 · 6 sessions"},
{"date": "2026-03-05", "summary": "3 mutation · 9 信号"}
],
"cost_total": "~$39"
}
自动编排入口(v0.3.1)
timer-review
python skills/byted-ark-evolve/scripts/orchestrator.py timer-review --worker-mode agent夜间运行时完成: 1. 加载 references/gene-library.json(静态库) 2. 选择未处理 signal 3. 创建 review 记录(status=running) 4. 生成 workset 5. 调用本地 review worker(agent 模式)或 mock worker(测试模式) 6. 落库 gene_matches / mutations(proposal_status=pending) 7. 导出 pending-evolution.json / daily-digest.json 8. review 置为 completed / failed
session-start
python skills/byted-ark-evolve/scripts/orchestrator.py session-start用户进入 session 时完成: 1. 导出最新 pending/digest 2. 查询 proposal_status in ('pending','presented') 3. 将首次可见的 proposal 标记为 presented 4. 输出简短摘要,供 session_start 展示给用户
Evolution Analysis — 详细 7 步流程
/evolve 触发时执行。
Step 0: Snapshot Diff(兜底变更检测)
对比上次快照,捕获 Hook 漏掉的 workspace 变更(手动编辑、Bash 写入等)。
python skills/byted-ark-evolve/scripts/snapshot.py diff --record流程: 1. 加载 evolution-data/snapshot.json(上次进化分析后保存的快照) 2. 扫描当前 workspace 所有文件,计算 SHA256 hash 3. 对比差异:added / modified / removed 4. 排除已有 user-direct 记录的文件(Hook 已追踪的) 5. 剩余差异写入 mutations 表(source='snapshot-diff') 6. 这些变更缺失意图信息,进化分析时仅标注"检测到变更"
如果 snapshot.json 不存在(首次运行),跳过此步,直接进 Step 1。
---
Step 1: 扫描信号
python skills/byted-ark-evolve/scripts/query.py signals --unprocessed读取未处理的信号,按 layer 分组统计。 输出:每个 layer 的信号数量、severity 分布、高频信号。
Step 1.5: 轨迹聚类 → Skill 候选检测
检查已有轨迹中是否存在可提取为 skill 的模式。
python skills/byted-ark-evolve/scripts/trajectory-skill-check.pyGolden 轨迹 → 新 Skill 候选
1. 按 task_type 分组查询 golden 轨迹
2. 某个 task_type 下 ≥3 条 → 触发候选
3. 提取共同步骤、工具链、输入输出模式
4. 呈现给用户:
- "检测到 [task_type] 类任务已有 N 条成功轨迹,建议提取为独立 skill"
- 列出共同步骤摘要
5. 用户确认 → 调用 skill-creator 生成 SKILL.md
6. 用户未安装 skill-creator → 提示安装Correction 轨迹 → 防护规则候选
1. 按 root_cause 分组查询 correction 轨迹
2. 同一 root_cause ≥2 条 → 触发候选
3. 检查是否已有相关 skill:
a. 有 → 建议在该 skill 中加入 "Common Mistakes" section
b. 无 → 建议新建防护类 skill
4. 呈现给用户确认 → 确认后同样调用 skill-creator如果没有触发任何候选,跳过此步。
---
Step 2: 归因分析
对每组重复/相关信号,归因到具体层和文件。
归因流程
1. 读取 file-registry.json 获取当前文件状态
2. 读取 references/layer-model.md 获取归因规则
3. 对每组信号:
a. 确定根因所在的层(Identity/Context/Protocol/Capability)
b. 确定受影响的具体文件
c. 检查 file-registry.json 中该文件的 status 和 write_policy归因示例
"不要用 curl 发中文" × 4 次
→ 根因:AGENTS.md 缺少编码规则
→ 层归因:Protocol(Layer 3)
→ 文件状态:user-owned → 只能 append
→ 建议:在 AGENTS.md 末尾加入中文编码规范 sectionneeds_review 文件处理
如果归因指向一个 needs_review 文件: 1. 读取该文件内容 2. 推断其语义(layer、governs、write_tier) 3. 更新 file-registry.json 中的分类 4. 然后继续正常归因
Step 2.5: Gene 候选筛选
在完成归因后,加载本 skill 包内的静态 gene 库,由 Agent 基于 signal + attribution + gene 内容做候选判断。
python skills/byted-ark-evolve/scripts/gene.py --list规则
1. Gene 静态库(`references/gene-library.json`)是主事实源,随 skill 版本发布 2. gene.py 不做本地匹配逻辑;它只负责加载和展示 gene 3. Agent 阅读:
- 当前未处理 signal(尤其是 correction / negative / error)
- Step 2 的 attribution 结果
- gene 列表中每个 gene 的
id / summary / pattern_key / rule_text
4. Agent 对每个相关 signal 判断:
- 哪些 gene 的解决方案如果提前应用,能避免该负向反馈或 error 再次发生
- 哪些 gene 只能缓解表象,不能解决根因
- 哪些 gene 与当前问题无关
5. Agent 输出 evolution-data/tmp/gene-candidates.json,至少包含:
signal_idgene_idreason- 可选
confidence
输出示例
{
"selected_genes": [
{
"signal_id": 12,
"gene_id": "gene_abc123",
"reason": "该 gene 强调工具文档优先与环境预检;若提前应用,可避免在未确认环境状态下重复重试命令",
"confidence": 0.82
}
]
}Step 3: 设计 Mutation
Agent 基于 attribution + gene-candidates.json + 当前 workspace 可修改点生成 mutation。
每个 mutation 包含:
{
"target_file": "AGENTS.md",
"mutation_type": "add", // add / modify / remove
"layer": "protocol",
"description": "加入中文编码规范",
"before_text": null, // modify/remove 时填写
"after_text": "## 中文编码规范\n...",
"signal_ids": [1, 2, 5, 8],
"write_policy": "append-only", // 从 file-registry 获取
"pareto_check": "ACCEPT",
"verification_criteria": "下次涉及中文 API 调用时,自动使用 Python 而非 curl",
"gene_id": "gene_abc123",
"gene_reason": "该 gene 的方案可预防当前错误再次发生"
}Write Policy 约束
| File Status | 允许的 mutation_type |
|---|---|
| evolvable | add, modify, remove |
| user-owned | add only(append section) |
| skill-owned | 不允许(由 skill 升级流程管理) |
| needs_review | 先分类,再决定 |
Step 4: 用户确认
必须在对话中逐条展示所有 mutation,用户明确回复前不执行任何写入。
展示格式
### 进化方案(共 N 条变更)
1. 📝 [目标文件] — 变更描述
- 层归因:<layer>
- 变更类型:<add/modify/remove>(write_policy 说明)
- 帕累托检查:✅/❌
- Before/After 摘要(modify/remove 时展示)
...
以上 N 条变更是否执行?你可以:
- 全部接受
- 全部拒绝
- 逐条选择(如"接受 1,拒绝 2")展示规则
- 每条 mutation 列出:目标文件、变更类型、层归因、帕累托结果
- user-owned 文件标注"仅允许追加"
- modify/remove 类变更展示 Before → After 摘要
- 如果变更较长,展示关键行 + 省略号,完整内容在报告中查看
用户回复处理
- 全部接受 → 执行所有写入
- 全部拒绝 → 所有 mutation 标记
rejected - 逐条选择 → 按用户指定执行/拒绝
- 用户未回复 → 等待,不主动执行
Step 5: 生成报告 + Dashboard
5a. 组装报告数据(JSON)
基于前面步骤的分析结果,组装一个 JSON 文件。JSON 只包含纯数据,不包含任何 HTML 或渲染逻辑。
参考 schema:references/report-schema-example.json
必填字段:
date,summary,stats(signals / changes / rejected)changes(每条含 file / description / reason / status,可选 before/after)signals_by_layer,next_steps
可选字段:
high_severity_signals,all_signals,trajectory,saturation,cost,user_direct_changes
写入路径:evolution-data/reports/evolution-<date>.json
5b. 渲染报告 HTML
python skills/byted-ark-evolve/scripts/report-render.py \
evolution-data/reports/evolution-<date>.json \
--output evolution-data/reports/evolution-<date>.htmlPython 脚本处理所有条件渲染(有/无拒绝、高严重度信号、饱和状态等),Agent 不需要操心 HTML 结构。
5c. 更新 Dashboard(可选,有 dashboard 数据时执行)
组装 dashboard JSON,参考 schema:references/dashboard-schema-example.json
python skills/byted-ark-evolve/scripts/dashboard-render.py \
evolution-data/dashboard-data.json \
--output evolution-data/dashboard.htmlDashboard 包含:3 卡片统计、Gate 状态、时间线、验证状态(按优先级分组)、信号趋势图、能力概览、报告归档。
Step 5.4: 导出 pending proposal / digest
在夜间 review 或离线分析结束后,先将待处理提案导出为文件,供后续 session_start 展示。
python skills/byted-ark-evolve/scripts/pending-evolution.py export导出结果:
evolution-data/pending-evolution.json:给用户看的未处理提案队列evolution-data/daily-digest.json:夜间总结摘要
规则:
- 这两个文件只是导出层,DB 才是主状态源
- proposal 不是“次日晨报一次性提醒”,而是未处理队列;若第二天下午或第三天才打开 session,只要 proposal 仍然有效,就仍可展示
- 过期/被更新覆盖的 proposal 应标记为
stale/superseded,避免反复提醒旧结论
Step 5.5: 进化总结(对话输出)
报告生成后,在对话中直接输出一段自然语言总结。这不是写入文件,而是直接告知用户。
输出模板
本次进化处理了 X 条反馈,产生 Y 条变更:
- [文件A] 新增了「XXX」— 因为你 N 次提到 YYY
- [文件B] 修改了 ZZZ — 增加了某某逻辑
被拒绝:W 条
完整报告:evolution-data/reports/evolution-<date>.html
下一步:当你再次遇到相关场景时,我会观察这些改进是否生效。规则
- 用自然语言,不用术语(不说 mutation / signal / layer / severity)
- 每条变更说"改了什么" + "为什么改"(关联到用户原始反馈)
- 告知 HTML 报告路径,但不以路径为主要输出
- 提及后续验证计划(让用户知道改进会被跟踪)
- 如果有被拒绝的变更,简要说明
---
Step 5.6: session_start 展示 pending 提案
每次 session_start 时检查 DB / pending-evolution.json 是否存在未处理提案:
- 首次展示:完整摘要
- 再次展示:简短提醒 + 可展开详情
- 若用户接受:将 proposal / mutation 标记为
accepted,再进入 apply - 若用户拒绝:标记
rejected - 若超过阈值或被新提案覆盖:标记
stale/superseded
Step 6: 标记信号已处理
所有与本次进化相关的信号标记 processed = 1。 记录 evolution_run 到 DB。
Step 7: 更新快照
python skills/byted-ark-evolve/scripts/snapshot.py save保存当前 workspace 文件的 SHA256 快照到 evolution-data/snapshot.json。 下次进化分析的 Step 0 会对比此快照检测遗漏变更。
apply-proposal(v0.2.4)
python skills/byted-ark-evolve/scripts/orchestrator.py apply-proposal --group <proposal_group_id>流程: 1. 校验该 proposal_group 的状态 2. 逐条应用 mutation 3. 每个 proposal_group 执行一次 git commit 4. 保存 snapshot 5. 写回 review/result(commit hash / applied ids / failed ids)
File Semantic Map
已知 OpenClaw workspace 文件的语义映射。初始化和进化归因时引用。
Workspace 根目录文件
| File | Layer | Governs | Write Risk | Notes |
|---|---|---|---|---|
| SOUL.md | identity | tone, values, style, boundaries | high | Agent 的"灵魂",改动影响全局风格 |
| IDENTITY.md | identity | name, persona, appearance | high | Agent 自我认知,改动影响自称方式 |
| USER.md | context | user-prefs, timezone, conventions | medium | 用户模型,重大变更需确认 |
| MEMORY.md | context | long-term-memory | medium | 长期记忆,主会话才载入 |
| AGENTS.md | protocol | rules, permissions, workflows, red-lines | high | 行为准则,改动影响决策模式 |
| TOOLS.md | protocol | device-config, search-prefs, local-env | low | 环境配置,改动风险低 |
| HEARTBEAT.md | protocol | periodic-checks, proactive-tasks | low | 周期任务清单 |
| BOOTSTRAP.md | protocol | first-run-setup | low | 首次启动流程,通常一次性 |
子目录
| Path Pattern | Layer | Governs | Write Risk | Notes |
|---|---|---|---|---|
| memory/*.md | context | daily-logs, short-term-memory | low | 每日记忆,可自动写入 |
| skills/*/SKILL.md | capability | skill-definition, methods | medium | 技能定义,重大变更需确认 |
| skills//scripts/ | capability | skill-logic, automation | medium | 技能实现代码 |
| skills//references/ | capability | skill-knowledge, rules | low | 技能参考资料 |
| .{arkclaw,openclaw}/* | runtime | internal-state | high | Claw runtime 内部状态,不修改 |
配置文件(workspace 外)
| File | Layer | Governs | Write Risk | Notes |
|---|---|---|---|---|
| ~/.{arkclaw,openclaw}/{arkclaw,openclaw}.json | runtime | model, channels, gateway, plugins | high | 通常不通过进化修改 |
进化归因速查
用户说"语气不对" → SOUL.md (identity/tone)
用户说"你不了解我" → USER.md (context/user-prefs)
用户说"你不该这么做" → AGENTS.md (protocol/rules)
用户说"搜索方式不好" → TOOLS.md (protocol/search-prefs) 或 skills/ (capability)
用户说"这个skill有bug" → skills/*/scripts/* (capability/skill-logic)Gene Contract (v0.3.1, static-library)
定位
Gene 是 mutation 模板库。它不是分析器,不负责根因归因;也不是执行器,不直接修改文件。
Source of Truth
- 主事实源:本 skill 包内的静态库
references/gene-library.json - 库随 skill 版本一起发布(更新需升级 skill 版本)
- Agent 不应做联网拉取,gene.py 不再有云端调用路径
数据格式
references/gene-library.json 是一个 JSON 数组,每条记录至少包含:
id或gene_id— 唯一标识summary或name— 简短描述pattern_key— 失败模式分类(用于 summary 聚合)rule_text— 规则文本(前 200 字符在--list中展示)created_at— 创建时间(可选)
CLI 命令
python skills/byted-ark-evolve/scripts/gene.py --list # 列出全部 gene 摘要
python skills/byted-ark-evolve/scripts/gene.py --show <id> # 展开单条 gene 全部字段
python skills/byted-ark-evolve/scripts/gene.py --summary # 库统计 + 高频 pattern_key不再有 --fetch / --base-url / --api-key / --allow-cache-fallback 选项。
Agent Judgment Contract
Step 2.5 中 Agent 阅读:
- 未处理 signal
- attribution 结果
- gene 库中的
id / summary / pattern_key / rule_text
Agent 必须回答:
1. 这个 gene 针对什么失败模式? 2. 如果提前应用它的方案,能否避免当前 signal 对应的问题复发? 3. 它是在治根因,还是只缓解表象?
Candidate Output
推荐写入:evolution-data/tmp/gene-candidates.json
字段建议:
signal_idgene_idreasonconfidence(可选)rejected_genes(可选)
版本更新策略
Gene 库内容随 skill 版本发布。如需新 gene,发布新版 skill(v0.3.2 / v0.4.0 等)覆盖 references/gene-library.json 即可。
Initialization Rules
workspace-init.py 的文件状态判定规则。
扫描范围
- 根目录:
~/.{runtime}/workspace/(runtime 自动检测,默认 arkclaw) - 递归扫描所有文件
- 排除:
.git/、node_modules/、__pycache__/、evolution-data/、.{*claw*}/(任何 claw runtime 目录)
状态判定流程
对每个文件 F:
1. F 路径匹配 skills/byted-ark-evolve/* ?
→ status = "skill-owned"
2. F 路径匹配 .{*claw*}/* 或 {*claw*}.json ?
→ status = "runtime" (跳过,不纳入 registry)
3. F 在已知文件映射中(file-semantic-map.md)?
→ 检查是否为默认模板(Step 4)
4. 默认模板检测:
a. 包含占位符关键词? → evolvable
b. 行数 < 基准行数 × 1.2? → evolvable
c. 都不满足 → user-owned
5. F 不在已知文件映射中?
→ status = "needs_review"占位符关键词列表
以下关键词出现在文件中表示该文件仍为默认模板:
_(待定)_
_(optional)_
_(未设置)_
Fill this in
Add whatever helps
_(What do they care about?
Make it yours
This is a starting point已知文件基准行数
用于辅助判断文件是否被深度定制。
| File | Baseline Lines | 说明 |
|---|---|---|
| SOUL.md | 37 | OpenClaw 默认 SOUL 模板 |
| IDENTITY.md | 24 | 默认身份模板 |
| USER.md | 20 | 默认用户模板 |
| MEMORY.md | 10 | 默认空记忆 |
| AGENTS.md | 210 | 默认规则(较长) |
| TOOLS.md | 41 | 默认工具模板 |
| HEARTBEAT.md | 10 | 默认心跳模板 |
| BOOTSTRAP.md | 30 | 默认引导模板 |
Write Policy 定义
| Status | Policy | 含义 |
|---|---|---|
| evolvable | section-edit | 可以修改指定 section |
| user-owned | append-only | 只能在文件末尾追加新 section |
| skill-owned | skill-managed | 由 skill 自身升级流程管理 |
| needs_review | blocked-until-classified | 进化分析时 Agent 读取后补分类 |
版本变化检测
# 判断是否需要重新初始化
current_version = get_openclaw_version() # 从 openclaw --version 获取
registry_version = registry.get("agent_version")
if current_version != registry_version:
# 重新扫描,保留 user-owned 状态不降级
re_init(preserve_user_owned=True)OpenClaw 5-Layer Evolution Model
Layer Mapping
| Layer | 层级 | OpenClaw 文件 | 进化内容 | 写入权限 |
|---|---|---|---|---|
| L1 Identity | 身份 | SOUL.md, IDENTITY.md | 价值观、原则、角色定位 | 必须用户确认 |
| L2 Context | 上下文 | USER.md, MEMORY.md, memory/*.md | 用户偏好、环境知识、历史记忆 | USER 重大变更需确认,memory 自动 |
| L3 Protocol | 协议 | AGENTS.md, TOOLS.md | 行为规则、工具使用规范、决策模式 | 必须用户确认 |
| L4 Capability | 能力 | skills/, plugins/ | 技能定义、方法论 | 重大变更需确认 |
| L5 Runtime | 运行时 | openclaw.json | 模型选择、channel 配置 | 通常不修改 |
Attribution Rules
问自己:这个问题的根因在哪一层?
- 价值观/原则性问题 → L1 Identity
例:"该不该先对齐再执行"
- 用户偏好/个人知识 → L2 Context
例:"用户喜欢简洁回复"
- 跨任务决策模式/行为规则 → L3 Protocol
例:"收到模糊指令如何处理"、"编码规范"
- 特定任务方法论 → L4 Capability
例:"怎么系统性 debug"、"搜索策略"
- 模型/配置问题 → L5 Runtime
例:"这个任务需要更强的模型"
错误归因 = 治标不治本。
Protocol 层的问题包装成 Capability 不会真正解决。Context Injection Order
OpenClaw 每次 session 的 system prompt 注入顺序:
1. SOUL.md + IDENTITY.md → Agent 的身份和价值观
2. USER.md → 用户模型
3. MEMORY.md + memory/*.md → 持久化记忆
4. AGENTS.md → 行为规则
5. TOOLS.md → 工具使用指南
6. Skills list (names only) → 可用技能列表
7. Channel context → 当前通道信息进化写入任何文件后,下次 session 立刻生效(写入即生效)。
Evolution Priority
按层影响范围排序(从大到小):
1. L1 Identity — 影响所有行为,慎重修改 2. L3 Protocol — 影响决策模式,次优先 3. L2 Context — 影响知识储备,可频繁更新 4. L4 Capability — 影响特定任务,按需修改 5. L5 Runtime — 影响运行配置,极少修改
进化系统 — 新手引导 (v0.3.1)
首次安装进化 Skill 后,Agent 向用户展示此引导。
---
什么是进化系统?
进化系统让 Agent 能从你的反馈中学习并改进自己。
简单来说:你用 Agent 的过程中说的"以后别这样"、"这个方法不错"、"能不能改成 XX",都会被记录下来。攒够一定数量后,系统会分析这些反馈,提出具体的改进方案——但只有你确认后才会生效。
你需要做什么?
几乎不需要额外操作。 日常使用中:
- 正常交流即可 — 你的纠正、表扬、建议会被自动识别和记录
- 确认进化方案 — 当系统提出改进建议时,你需要审核并决定接受或拒绝
- (可选)主动触发 — 说
/evolve可以随时启动一次进化分析
你不需要担心什么?
- 不会偷偷改你的文件 — 所有变更必须经你确认后才执行
- 不会丢失数据 — 被拒绝的方案只是标记为 rejected,不会删除记录
- 不会越改越差 — 进化方案遵循帕累托原则,避免已有能力退化
- 随时可回退 — 每次变更自动创建 git commit,
git revert即可撤销
交互方式
命令
| 命令 | 说明 |
|---|---|
/evolve | 主动触发一次完整进化分析,查看改进建议 |
/evolve init | 首次安装时执行:初始化扫描 + 生成文件注册表 |
/evolve scan | 扫描历史对话,提取已有的反馈信号 |
/evolve dashboard | 生成并打开进化 Dashboard(evolution-data/dashboard.html,浏览器查看) |
/evolve help | 展示本引导内容 |
自然语言
| 你说 | Agent 做 |
|---|---|
| "别这样做" / "不要 XX" | 记录 correction 信号 |
| "这个好" / "就是这样" | 记录 positive 信号 |
| "记住这个模式" | 记录 positive 信号,作为后续进化分析的正向参考 |
| "以后别这样做" | 记录 correction 信号,标记需要改进的行为 |
| "我更喜欢简洁的" / "用 X 格式" | 记录 preference 信号(v0.3.1 新增) |
| "我说的 X 指的是 Y" / "你理解错了" | 记录 clarification 信号(v0.3.1 新增) |
| "进化状态" | 查看当前反馈积累量、上次进化时间、验证状态 |
| "进化 Dashboard" | 生成 HTML Dashboard 页面(evolution-data/dashboard.html,浏览器打开) |
| "gene 列表" / "gene 概况" | 查看基因模板库和命中情况 |
| "进化报告" | 生成 HTML 格式的进化报告 |
常见问题
Q: 它会改哪些文件? A: 主要修改 workspace 内标记为"可进化"(evolvable)的文件。用户自己管理的文件(user-owned)原则上只追加内容。初始化时会扫描并分类所有文件,分类结果保存在 evolution-data/file-registry.json。
Q: 进化多久发生一次? A: 有两种方式:
自动检查:当累积 ≥5 条 correction/negative/clarification 信号,或 ≥3 条 high severity 信号时,系统会在新对话开始时自动触发分析。
手动触发:说 /evolve 可以随时启动一次完整分析。
Q: 我可以回退吗? A: 可以。每次进化应用变更时会自动在 workspace 中创建 git commit(commit message 格式:evolution: apply proposal <group_id>)。回退方式:
git revert <commit>— 撤销指定的进化变更git log --oneline— 查看所有进化历史- 每次进化还会生成 HTML 报告,记录每条变更前后的对比,方便查看改了什么
Q: 如果我不想用了? A: 卸载 evolution skill 即可。已收集的数据保留在 evolution-data/ 目录(信号、轨迹、报告、Dashboard),不影响其他功能。
Pareto Constraint Rules
Core Principle
Quality > Reliability > Efficiency > Cost
不允许牺牲质量换效率。
Pareto Check for Mutations
对于任何进化变异 M:
IF M.quality_after < M.quality_before:
BLOCK — 不允许自动执行
→ 生成退化报告,交给用户决策
IF M.quality_after >= M.quality_before AND any_dimension_improves:
ACCEPT — 帕累托改进
IF M.quality_after >= M.quality_before AND no_dimension_improves:
SKIP — 无意义的变异Evaluation Dimensions
| Dimension | Weight | Description |
|---|---|---|
| Quality | 0.40 | 输出的准确性、完整性、适用性 |
| Reliability | 0.25 | 一致性、可重复性、无 regression |
| Efficiency | 0.15 | 完成速度、token 消耗 |
| Cost | 0.10 | 直接的 API/计算成本 |
| Reusability | 0.10 | 跨任务/跨 session 的复用价值 |
Verification Standards
Three-Level Verification
| 状态 | 条件 | 含义 |
|---|---|---|
| 待验证 | 刚提出 | 等待在真实场景中验证 |
| 已观察 | 1 credible credit | 初步验证,但不可信赖 |
| 已验证 | ≥3 credible credits, 跨 session | 可信的行为改变 |
| 已复发 | 已验证后再次违反 | 需要加固 |
| 部分生效 | 部分场景有效 | 部分未覆盖 |
Observation Source Weights
| Source | Weight | Notes |
|---|---|---|
| user_confirmed | 1 credit | 用户明确确认行为正确 |
| no_negative | 0.2 credit | 相关场景完成,用户未纠正 |
| self_reported | 0.2 credit | Agent 自己声称遵守(最弱) |
「已验证」需要 3 credible credits(非 3 次原始计数)。
Saturation Detection
IF 最近 3 次进化的 mutation_count <= 1:
→ 降低进化频率
→ 通知用户:"进化趋于饱和,建议关注新方向"Cost Tracking
每次进化分析必须记录成本:
evolution_cost: 本次分析消耗analyzed_sessions_cost: 被分析 session 的总消耗roi_ratio: evolution_cost / analyzed_sessions_cost
目标:ROI ratio < 0.25(进化成本不超过被分析内容成本的 25%)
Pending Proposal Lifecycle (v0.2.2)
核心语义
pending proposal 不是“第二天早上一次性提示”,而是未处理提案队列。
只要 proposal 仍然有效且未被用户确认/拒绝,就应在后续 session_start 中可见。
状态
pending:已生成,尚未展示给用户presented:已展示,但未决策accepted:用户接受,允许应用rejected:用户拒绝stale:过久未处理或环境已变化superseded:被更新的 proposal 覆盖
建议转移
- review worker 完成 →
pending - 首次 session_start 展示 →
presented - 用户确认应用 →
accepted - 用户拒绝 →
rejected - 超过 72 小时或出现更新 proposal →
stale/superseded
{
"date": "2026-03-12",
"summary": "本次处理了 6 条反馈,产生 2 条变更——主要改进了 Agent 的执行流程,让它在提问前先做调研、不主动调取用户记忆。",
"stats": {
"signals": 6,
"changes": 2,
"rejected": 0
},
"changes": [
{
"file": "AGENTS.md",
"description": "新增「先调研再提问」规则",
"reason": "你纠正了直接提问的行为——\"请你下次问我之前先搜索一下\"",
"status": "applied",
"before": "(无相关规则)",
"after": "## 调研优先原则\n收到创建类需求时,先搜索行业通用做法,再结合调研结果提问。"
},
{
"file": "SOUL.md",
"description": "新增「记忆访问边界」规则",
"reason": "你明确要求\"如果我不发起请求,不要调取我的记忆\"(高优先级)",
"status": "applied",
"before": "(无相关规则)",
"after": "## 记忆访问边界\n用户画像/记忆仅在用户主动请求时调取,Agent 不得主动引用。"
}
],
"signals_by_layer": {
"protocol": 4,
"capability": 2
},
"high_severity_signals": [
{
"text": "如果我不发起请求,不要调取我的记忆"
}
],
"all_signals": [
{"text": "请你下次问我之前先搜索一下", "type": "correction"},
{"text": "如果我不发起请求,不要调取我的记忆", "type": "correction"},
{"text": "只要中文,英文不要了", "type": "correction"},
{"text": "进化报告应该自动产出", "type": "suggestion"},
{"text": "变更前后标明文件名", "type": "suggestion"},
{"text": "术语要加灰色解释", "type": "suggestion"}
],
"next_steps": [
"下次创建类任务时 → 观察是否先调研再提问",
"下次分析类任务时 → 观察是否主动引用记忆"
],
"trajectory": {
"golden": 1,
"correction": 2
},
"saturation": {
"is_saturated": false,
"added": 2,
"modified": 0
},
"cost": {
"analysis_cost": 0.2,
"roi_percent": 25
},
"user_direct_changes": 1
}
Signal Types — 识别规则与示例
信号类型
| 类型 | 识别规则 | 示例 |
|---|---|---|
| correction | 用户纠正了你的行为/输出 | "不要这样做"、"我说的不是这个意思"、"应该用 X 而不是 Y" |
| negative | 用户表达不满 | "太长了"、"AI 味太重"、"又来了"、"这不是我要的" |
| positive | 用户明确表扬 | "这个做得好"、"就是这样"、"完美" |
| suggestion | 用户提出改进建议 | "以后可以先问我"、"这种情况应该..."、"能不能加个 X" |
自动识别触发词
Correction 信号
- "不要..."、"别..."、"不是这个"、"错了"
- "应该..."、"正确的做法是..."
- "我说的是 X 不是 Y"
- 用户紧跟着重复指令(意味着前一次执行有误)
Negative 信号
- "太长了"、"太短了"、"太慢了"
- "AI 味"、"机器味"、"套话"
- "又来了"、"跟上次一样的问题"
- 语气词:"唉"、"算了"、"行吧"(勉强接受 = negative)
Positive 信号
- "好的"、"对"、"就是这样"、"完美"
- "这个做得好"、"比上次好多了"
- 用户没有纠正就继续下一步(隐式 positive)
Suggestion 信号
- "以后可以..."、"下次..."
- "能不能..."、"要是能 X 就好了"
- "这种情况应该..."
Layer 归因速查
| 信号内容模式 | 归因 Layer |
|---|---|
| 语气/风格/态度 | identity |
| 用户偏好/习惯/叫法 | context |
| 行为规则/决策逻辑/流程 | protocol |
| 特定技能/方法/工具使用 | capability |
| 模型选择/配置 | runtime |
记录命令
python skills/byted-ark-evolve/scripts/signal-record.py \
--type correction \
--layer protocol \
--severity high \
--text "用户原话" \
--context "当时在做什么(~200字)"severity 判定:
- high:用户明确纠正 + 重复出现 + 影响信任
- medium:一般性反馈/建议
- low:轻微偏好调整
新增信号类型(v0.3.1)
| 类型 | 识别规则 | 示例 |
|---|---|---|
| preference | 用户表达偏好/习惯 | "我更喜欢简洁的"、"我习惯用 X"、"别给我用 emoji" |
| clarification | 用户纠正理解偏差 | "我说的 X 指的是 Y"、"你理解错了"、"准确来说是..." |
Preference 信号
- "我更喜欢..."、"我比较喜欢..."
- "我习惯..."、"我一般都..."
- "别给我..."、"不要用 X 格式"
- "简短一点"、"详细一些"
- "用 X 格式"、"我的风格是..."
Clarification 信号
- "我这里说的是..."、"我指的是..."
- "所谓 X 就是..."、"X 指的是..."
- "你理解错了"、"不是 X 而是 Y"
- "准确来说..."、"补充一下..."
- "我再解释一下"
隐式信号启发式规则
以下场景不一定有明确触发词,由 agent(非脚本)识别:
| 场景 | 推断类型 | 置信度 |
|---|---|---|
| 用户未纠正就继续下一步 | positive | low(仅大量累积时有效) |
| 用户重复同一指令(措辞几乎相同) | correction | medium |
| 用户中途放弃当前任务 | negative | low |
| 用户手动修改 agent 输出 | correction | medium |
| 用户多次在同类任务中给同样指示 | preference | medium |
注意:隐式信号不适合 pattern match 自动化,保留给 agent 判断。 这是 ~70% 召回率目标中剩余 30% 的部分。
Trajectory Templates
轨迹存储格式。Agent 写入轨迹时参考此文件。
Golden Trajectory(做对了)
存储到 evolution-data/trajectories/golden/<task-type>-<date>.md
---
type: golden
task_type: <任务类型,如 api-call, search, report>
created: <YYYY-MM-DD>
tags: [tag1, tag2]
---
## 场景
<简述用户需求和执行环境>
## 关键步骤
1. <步骤 1>
2. <步骤 2>
3. <步骤 3>
## 可复用 Pattern
- <提炼出的通用规律>示例
---
type: golden
task_type: api-call
created: 2026-03-11
tags: [encoding, chinese, api]
---
## 场景
用户要求调用 API 发送中文内容
## 关键步骤
1. 用 Python urllib 而非 curl
2. ensure_ascii=True
3. 返回结果用 UTF-8 解码
## 可复用 Pattern
- Windows 环境下涉及中文的 API 调用,走 PythonCorrection Trajectory(做错了→修正)
存储到 evolution-data/trajectories/corrections/<issue>-<date>.md
---
type: correction
task_type: <任务类型>
created: <YYYY-MM-DD>
signal_ref: "<触发此修正的用户原话>"
tags: [tag1, tag2]
---
## 错误做法
<描述错误行为>
## 正确做法
<描述修正后的行为>
## 根因
<为什么会犯这个错>
## 影响的文件
<哪些 workspace 文件需要更新>示例
---
type: correction
task_type: api-call
created: 2026-03-11
signal_ref: "不要用 curl 发中文"
tags: [encoding, curl, chinese]
---
## 错误做法
用 bash curl 发送包含中文的 JSON body → 乱码
## 正确做法
用 Python urllib + ensure_ascii=True → 正常
## 根因
Windows bash 环境下 curl 不支持 UTF-8 传参
## 影响的文件
AGENTS.md — 加入编码规则轨迹检索规则
执行任务前: 1. 根据当前任务类型,检索 corrections/ 下相关文件 2. 匹配方式:task_type 匹配 + tags 交集 3. 如果找到相关修正,在开始执行前主动复述修正要点 4. 复述格式:"上次类似任务中学到:<修正内容>,本次将遵循。"
# Copyright 2026 Beijing Volcano Engine Technology Co., Ltd.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Resolve the runtime workspace root for arkclaw / openclaw / other *claw* runtimes.
Resolution order:
1. CLAW_WORKSPACE env var (explicit override)
2. Scan ~/ for any ~/.{name}/workspace where 'claw' is in {name};
prefer dirs that already contain evolution-data/, then prefer
.arkclaw > .openclaw > others (alpha order).
3. Fallback: ~/.arkclaw/workspace (production default for new installs).
"""
import os
ENV_VAR = "CLAW_WORKSPACE"
_FALLBACK = "~/.arkclaw/workspace"
def _scan_home():
home = os.path.expanduser("~")
if not os.path.isdir(home):
return None
try:
entries = os.listdir(home)
except OSError:
return None
found = []
for name in entries:
if not (name.startswith(".") and "claw" in name.lower()):
continue
ws = os.path.join(home, name, "workspace")
if not os.path.isdir(ws):
continue
has_data = os.path.isdir(os.path.join(ws, "evolution-data"))
found.append((has_data, name, ws))
if not found:
return None
def sort_key(item):
has_data, name, _ws = item
priority = 0 if name == ".arkclaw" else (1 if name == ".openclaw" else 2)
return (0 if has_data else 1, priority, name)
found.sort(key=sort_key)
return found[0][2]
def resolve_workspace_root():
"""Return absolute path to the runtime workspace root."""
override = os.environ.get(ENV_VAR)
if override:
return os.path.expanduser(override)
found = _scan_home()
if found:
return found
return os.path.expanduser(_FALLBACK)
def resolve_runtime_home():
"""Return parent of the workspace (e.g. ~/.arkclaw)."""
return os.path.dirname(resolve_workspace_root())
def claw_exclude_dirs():
"""All ~/.X dirnames where 'claw' is in name (used for scan-exclude)."""
home = os.path.expanduser("~")
out = set()
try:
for name in os.listdir(home):
if name.startswith(".") and "claw" in name.lower():
out.add(name)
except OSError:
pass
out.update({".arkclaw", ".openclaw"})
return out
if __name__ == "__main__":
import json
print(
json.dumps(
{
"workspace_root": resolve_workspace_root(),
"runtime_home": resolve_runtime_home(),
"claw_exclude_dirs": sorted(claw_exclude_dirs()),
"env_var": ENV_VAR,
},
indent=2,
)
)
#!/usr/bin/env python3
# Copyright 2026 Beijing Volcano Engine Technology Co., Ltd.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Apply an accepted proposal group, commit snapshot, and write back result."""
import argparse
import json
import os
import sqlite3
import subprocess
import sys
from pathlib import Path
from _workspace import resolve_workspace_root
_WS = resolve_workspace_root()
DEFAULT_DB_PATH = os.path.join(_WS, "evolution-data/evolution.db")
WORKSPACE_ROOT = _WS
SNAPSHOT_SCRIPT = str(Path(__file__).with_name("snapshot.py"))
DASHBOARD_SCRIPT = str(Path(__file__).with_name("dashboard-render.py"))
for stream in [sys.stdout, sys.stderr, sys.stdin]:
if stream and hasattr(stream, "reconfigure") and stream.encoding != "utf-8":
stream.reconfigure(encoding="utf-8")
def conn(db_path):
c = sqlite3.connect(db_path)
c.row_factory = sqlite3.Row
return c
def ensure_git_repo(workspace_root):
git_dir = Path(workspace_root) / ".git"
if git_dir.exists():
return True
subprocess.run(
["git", "init"], cwd=workspace_root, check=True, capture_output=True, text=True
)
subprocess.run(
["git", "config", "user.email", "byted-ark-evolve@local"],
cwd=workspace_root,
check=True,
)
subprocess.run(
["git", "config", "user.name", "Evolution Skill"],
cwd=workspace_root,
check=True,
)
return True
def group_rows(c, group_id):
cur = c.cursor()
cur.execute(
"SELECT * FROM mutations WHERE proposal_group_id = ? ORDER BY id", (group_id,)
)
return [dict(r) for r in cur.fetchall()]
def apply_mutation(workspace_root, m):
target = Path(workspace_root) / m["target_file"]
target.parent.mkdir(parents=True, exist_ok=True)
before = target.read_text(encoding="utf-8") if target.exists() else ""
mutation_type = m.get("mutation_type")
if mutation_type == "add":
new_text = before
if before and not before.endswith("\n"):
new_text += "\n"
new_text += m.get("after_text") or ""
elif mutation_type == "modify":
before_text = m.get("before_text") or ""
after_text = m.get("after_text") or ""
if before_text and before_text in before:
new_text = before.replace(before_text, after_text, 1)
else:
raise RuntimeError(f"before_text not found in {m['target_file']}")
elif mutation_type == "remove":
before_text = m.get("before_text") or ""
if before_text and before_text in before:
new_text = before.replace(before_text, "", 1)
else:
raise RuntimeError(f"before_text not found in {m['target_file']}")
else:
raise RuntimeError(f"unknown mutation_type: {mutation_type}")
target.write_text(new_text, encoding="utf-8")
return str(target)
def check_positive_conflict(c, target_file):
"""检查待修改的文件是否有被 positive 信号验证过的行为。"""
cur = c.cursor()
cur.execute(
"""
SELECT id, description FROM mutations
WHERE target_file = ? AND status = 'verified' AND source = 'positive-anchor'
""",
(target_file,),
)
conflicts = [dict(r) for r in cur.fetchall()]
if conflicts:
return {
"has_conflict": True,
"verified_behaviors": conflicts,
"warning": f"target {target_file} has {len(conflicts)} verified positive behavior(s), modification may cause regression",
}
return {"has_conflict": False}
def commit_group(workspace_root, group_id, files):
ensure_git_repo(workspace_root)
subprocess.run(["git", "add", "--", *files], cwd=workspace_root, check=True)
msg = f"evolution: apply proposal {group_id}"
subprocess.run(
["git", "commit", "-m", msg],
cwd=workspace_root,
check=True,
capture_output=True,
text=True,
)
res = subprocess.run(
["git", "rev-parse", "HEAD"],
cwd=workspace_root,
check=True,
capture_output=True,
text=True,
)
return res.stdout.strip()
def save_snapshot(db_path):
subprocess.run(
[sys.executable, SNAPSHOT_SCRIPT, "save", "--db", db_path],
check=False,
capture_output=True,
text=True,
)
def render_dashboard(db_path, data_dir):
try:
subprocess.run(
[
sys.executable,
DASHBOARD_SCRIPT,
"--db",
str(db_path),
"--data-dir",
str(data_dir),
],
check=True,
capture_output=True,
text=True,
)
except Exception as exc:
print(f"[warn] dashboard render failed: {exc}", file=sys.stderr)
def main():
parser = argparse.ArgumentParser(description="Apply accepted proposal group")
parser.add_argument("--group", required=True)
parser.add_argument("--db", default=DEFAULT_DB_PATH)
parser.add_argument("--workspace-root", default=WORKSPACE_ROOT)
args = parser.parse_args()
c = conn(args.db)
cur = c.cursor()
rows = group_rows(c, args.group)
if not rows:
raise SystemExit("proposal group not found")
if any(
r.get("proposal_status") not in ("accepted", "presented", "pending")
for r in rows
):
raise SystemExit("proposal group has invalid state for apply")
applied_ids = []
failed = []
changed_files = []
positive_warnings = []
for row in rows:
try:
conflict = check_positive_conflict(c, row["target_file"])
if conflict.get("has_conflict"):
positive_warnings.append(
{
"mutation_id": row["id"],
"target_file": row["target_file"],
"warning": conflict["warning"],
}
)
path = apply_mutation(args.workspace_root, row)
changed_files.append(os.path.relpath(path, args.workspace_root))
cur.execute(
"UPDATE mutations SET status='applied', proposal_status='accepted', applied_at=datetime('now'), decision_at=COALESCE(decision_at, datetime('now')) WHERE id = ?",
(row["id"],),
)
applied_ids.append(row["id"])
except Exception as exc:
failed.append({"id": row["id"], "error": str(exc)})
commit_hash = None
result_status = "failed"
if applied_ids:
commit_hash = commit_group(args.workspace_root, args.group, changed_files)
save_snapshot(args.db)
result_status = "partial" if failed else "applied"
review_id = rows[0].get("review_id")
if review_id:
payload = {
"proposal_group_id": args.group,
"result_status": result_status,
"commit_hash": commit_hash,
"applied_mutation_ids": applied_ids,
"failed_mutation_ids": [x["id"] for x in failed],
"changed_files": changed_files,
}
cur.execute(
"UPDATE reviews SET pending_summary_json = ?, worker_finished_at = datetime('now') WHERE review_id = ?",
(json.dumps(payload, ensure_ascii=False), review_id),
)
c.commit()
c.close()
# Always render dashboard after applying
data_dir = str(Path(args.db).parent)
render_dashboard(args.db, data_dir)
print(
json.dumps(
{
"status": result_status,
"commit_hash": commit_hash,
"applied_mutation_ids": applied_ids,
"failed": failed,
"changed_files": changed_files,
"positive_warnings": positive_warnings,
},
ensure_ascii=False,
indent=2,
)
)
if __name__ == "__main__":
main()
#!/usr/bin/env python3
# ruff: noqa: E402
# Copyright 2026 Beijing Volcano Engine Technology Co., Ltd.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""
Render evolution dashboard from SQLite DB + bundled gene library.
Reads data from DB, injects JSON into the frozen HTML template.
The template handles all rendering via JS — this script never touches HTML/CSS.
Usage:
python dashboard-render.py --db evolution.db --output dashboard.html
python dashboard-render.py --db evolution.db --data-dir evolution-data/
"""
import argparse
import json
import sqlite3
import sys
from datetime import datetime, timedelta, timezone
from pathlib import Path
for stream in [sys.stdout, sys.stderr, sys.stdin]:
if stream and hasattr(stream, "reconfigure") and stream.encoding != "utf-8":
stream.reconfigure(encoding="utf-8")
TEMPLATE_PATH = Path(__file__).with_name("dashboard-template.html")
from _workspace import resolve_workspace_root
_WS = Path(resolve_workspace_root())
DEFAULT_DB = _WS / "evolution-data/evolution.db"
DEFAULT_DATA_DIR = _WS / "evolution-data"
def conn(db_path):
c = sqlite3.connect(str(db_path))
c.row_factory = sqlite3.Row
return c
def fmt_date(dt_str):
"""Convert '2026-03-22 10:15:00' to '3/22'."""
if not dt_str:
return ""
try:
dt = datetime.fromisoformat(dt_str.replace("Z", "+00:00"))
return f"{dt.month}/{dt.day}"
except Exception:
return dt_str[:10] if len(dt_str) >= 10 else dt_str
def fmt_date_cn(dt_str):
"""Convert to '3月22日 10:15' format."""
if not dt_str:
return ""
try:
dt = datetime.fromisoformat(dt_str.replace("Z", "+00:00"))
return f"{dt.month}月{dt.day}日 {dt.hour:02d}:{dt.minute:02d}"
except Exception:
return dt_str
def split_gene_ids(gene_id_str):
"""Split comma-separated gene_id string into individual IDs."""
if not gene_id_str:
return []
return [gid.strip() for gid in gene_id_str.split(",") if gid.strip()]
def week_label(dt_str):
"""Convert date to 'M/D' week label."""
try:
dt = datetime.fromisoformat(dt_str.replace("Z", "+00:00"))
# Monday of that week
monday = dt - timedelta(days=dt.weekday())
return f"{monday.month}/{monday.day}"
except Exception:
return ""
def build_overview(c):
"""Build overview tab data from DB."""
cur = c.cursor()
# Basic counts
cur.execute(
"SELECT COUNT(DISTINCT session_id) FROM signals WHERE session_id IS NOT NULL"
)
session_counts = cur.fetchone()[0] or 0
cur.execute("SELECT COUNT(*) FROM signals")
signals_total = cur.fetchone()[0] or 0
cur.execute(
"SELECT COUNT(*) FROM signals WHERE created_at >= date('now', '-7 days')"
)
signals_week = cur.fetchone()[0] or 0
cur.execute("SELECT COUNT(*) FROM reviews WHERE status = 'completed'")
evo_runs = cur.fetchone()[0] or 0
cur.execute("SELECT MAX(created_at) FROM reviews WHERE status = 'completed'")
last_evo_row = cur.fetchone()
last_evo = (
fmt_date_cn(last_evo_row[0]) if last_evo_row and last_evo_row[0] else "无"
)
cur.execute("SELECT COUNT(*) FROM mutations WHERE status = 'applied'")
applied = cur.fetchone()[0] or 0
cur.execute(
"SELECT COUNT(*) FROM mutations WHERE status IN ('proposed', 'approved', 'applied', 'verified')"
)
total_proposed = cur.fetchone()[0] or 1
hit_rate = round(applied / total_proposed * 100) if total_proposed > 0 else 0
cur.execute("SELECT COUNT(DISTINCT gene_id) FROM gene_matches")
genes_matched = cur.fetchone()[0] or 0
cur.execute("SELECT COUNT(*) FROM mutations WHERE status = 'verified'")
verified = cur.fetchone()[0] or 0
verify_rate = round(verified / applied * 100) if applied > 0 else 0
cards = [
{
"label": "累计会话数",
"value": session_counts,
"delta": f"+{signals_week} 本周反馈",
"delta_up": signals_week > 0,
},
{
"label": "收集反馈",
"value": signals_total,
"cls": "accent",
"delta": f"+{signals_week} 本周",
"delta_up": signals_week > 0,
},
{"label": "进化执行", "value": evo_runs, "delta": f"上次 {last_evo}"},
{
"label": "已应用改动",
"value": applied,
"cls": "accent",
"delta": f"命中率 {hit_rate}%",
"delta_up": True,
},
{"label": "关联基因", "value": genes_matched},
{
"label": "验证通过",
"value": verified,
"cls": "accent",
"delta": f"通过率 {verify_rate}%",
"delta_up": verify_rate > 0,
},
]
# Trend: signals and mutations per week (last 8 weeks)
trend = []
for i in range(7, -1, -1):
now_utc = datetime.now(timezone.utc)
week_start = (now_utc - timedelta(weeks=i)).strftime("%Y-%m-%d")
week_end = (
(now_utc - timedelta(weeks=i - 1)).strftime("%Y-%m-%d")
if i > 0
else (now_utc + timedelta(days=1)).strftime("%Y-%m-%d")
)
cur.execute(
"SELECT COUNT(*) FROM signals WHERE created_at >= ? AND created_at < ?",
(week_start, week_end),
)
sig_count = cur.fetchone()[0] or 0
cur.execute(
"SELECT COUNT(*) FROM mutations WHERE created_at >= ? AND created_at < ?",
(week_start, week_end),
)
mut_count = cur.fetchone()[0] or 0
dt = datetime.fromisoformat(week_start)
trend.append(
{
"label": f"{dt.month}/{dt.day}",
"signals": sig_count,
"mutations": mut_count,
}
)
# Verified items
cur.execute(
"SELECT target_file, description FROM mutations WHERE status = 'verified' ORDER BY applied_at DESC LIMIT 20"
)
verified_items = [
{"file": r["target_file"], "desc": r["description"] or ""}
for r in cur.fetchall()
]
# Pending items
cur.execute(
"SELECT target_file, description FROM mutations WHERE status IN ('applied', 'proposed', 'approved') AND status != 'verified' ORDER BY created_at DESC LIMIT 20"
)
pending_items = [
{"file": r["target_file"], "desc": r["description"] or ""}
for r in cur.fetchall()
]
# Layer distribution
layers = {"identity": 0, "context": 0, "protocol": 0, "capability": 0, "runtime": 0}
cur.execute(
"SELECT layer, COUNT(*) as cnt FROM signals WHERE layer IS NOT NULL GROUP BY layer"
)
for r in cur.fetchall():
if r["layer"] in layers:
layers[r["layer"]] = r["cnt"]
return {
"cards": cards,
"trend": trend,
"verified_items": verified_items,
"pending_items": pending_items,
"layers": layers,
}
def build_reviews(c):
"""Build activity tab data — one entry per completed review."""
cur = c.cursor()
cur.execute(
"SELECT * FROM reviews WHERE status = 'completed' ORDER BY created_at DESC LIMIT 20"
)
reviews_rows = [dict(r) for r in cur.fetchall()]
reviews = []
for rev in reviews_rows:
review_id = rev["review_id"]
date_label = fmt_date(rev["created_at"])
# Get mutations for this review
cur.execute(
"SELECT * FROM mutations WHERE review_id = ? ORDER BY id", (review_id,)
)
mutations_rows = [dict(r) for r in cur.fetchall()]
# Get signals linked to these mutations
signal_ids = set()
for m in mutations_rows:
try:
sids = json.loads(m.get("signal_ids") or "[]")
signal_ids.update(sids)
except Exception:
pass
signals = []
if signal_ids:
placeholders = ",".join("?" for _ in signal_ids)
cur.execute(
f"SELECT * FROM signals WHERE id IN ({placeholders}) ORDER BY id",
list(signal_ids),
)
for s in cur.fetchall():
meta_parts = []
if s["layer"]:
meta_parts.append(s["layer"].capitalize())
sev_cn = {"high": "高", "medium": "中", "low": "低"}.get(
s["severity"], ""
)
if sev_cn:
meta_parts.append(sev_cn)
signals.append(
{
"type": s["type"],
"text": s["raw_text"],
"meta": " \u00b7 ".join(meta_parts),
}
)
# Build mutations list
mutations = []
applied_count = 0
rejected_count = 0
genes_linked = set()
for m in mutations_rows:
status = m.get("status", "proposed")
if status in ("applied", "verified"):
applied_count += 1
if m.get("proposal_status") == "rejected" or status == "rejected":
status = "rejected"
rejected_count += 1
if m.get("gene_id"):
for _gid in split_gene_ids(m["gene_id"]):
genes_linked.add(_gid)
diff_lines = []
if m.get("after_text"):
for line in m["after_text"].split("\n"):
if line.strip():
diff_lines.append(f"+ {line}")
if m.get("before_text") and m.get("mutation_type") in ("modify", "remove"):
for line in m["before_text"].split("\n"):
if line.strip():
diff_lines.insert(0, f"- {line}")
mutations.append(
{
"target_file": m["target_file"],
"status": status,
"description": m.get("description") or "",
"reason": m.get("gene_reason") or "",
"diff_lines": diff_lines,
"gene_id": m.get("gene_id") or "",
"gene_name": "", # filled later from gene cache
}
)
# Summary
total_signals = len(signals)
total_mutations = len(mutations)
stats = [
{"value": total_signals, "label": "反馈处理"},
{"value": total_mutations, "label": "提出改动"},
{"value": applied_count, "label": "已应用"},
]
if rejected_count:
stats.append({"value": rejected_count, "label": "被拒绝"})
if genes_linked:
stats.append({"value": len(genes_linked), "label": "关联基因"})
summary_parts = [
f"处理了 <strong>{total_signals} 条反馈</strong>,产生 <strong>{total_mutations} 条改动</strong>"
]
if applied_count and rejected_count:
summary_parts.append(
f"其中 {applied_count} 条已应用、{rejected_count} 条被拒绝。"
)
elif applied_count:
summary_parts.append("全部已应用。")
elif total_mutations:
summary_parts.append("等待确认。")
reviews.append(
{
"review_id": review_id,
"date_label": date_label,
"summary_html": ",".join(summary_parts),
"stats": stats,
"signals": signals,
"mutations": mutations,
}
)
return reviews
def build_genes(c, data_dir):
"""Build genes tab data from gene_matches + bundled gene-library.json."""
cur = c.cursor()
# Load static gene library (bundled with the skill release)
gene_cache = {}
library_path = (
Path(__file__).resolve().parent.parent / "references" / "gene-library.json"
)
if library_path.exists():
try:
raw = json.loads(library_path.read_text(encoding="utf-8"))
genes_list = raw if isinstance(raw, list) else raw.get("genes", [])
for g in genes_list:
gid = g.get("id") or g.get("gene_id") or ""
gene_cache[gid] = g
except Exception:
pass
# Get hit counts per gene
cur.execute("""
SELECT gene_id, COUNT(*) as hits, MAX(created_at) as last_used
FROM gene_matches
WHERE gene_id IS NOT NULL AND gene_id != ''
GROUP BY gene_id
ORDER BY hits DESC
""")
hit_data = {
r["gene_id"]: {"hits": r["hits"], "last_used": fmt_date(r["last_used"])}
for r in cur.fetchall()
}
# Get mutations per gene
cur.execute("""
SELECT gene_id, target_file, description, status, created_at
FROM mutations
WHERE gene_id IS NOT NULL AND gene_id != ''
ORDER BY created_at DESC
""")
gene_mutations = {}
for r in cur.fetchall():
# gene_id may be comma-separated (e.g. "PG-D1-001,PG-C3-001")
for gid in split_gene_ids(r["gene_id"]):
if gid not in gene_mutations:
gene_mutations[gid] = []
gene_mutations[gid].append(
{
"file": r["target_file"],
"desc": r["description"] or "",
"date": fmt_date(r["created_at"]),
"status": r["status"] or "proposed",
}
)
# Merge all gene IDs
all_gene_ids = set(hit_data.keys()) | set(gene_cache.keys())
items = []
for gid in sorted(all_gene_ids):
cache = gene_cache.get(gid, {})
hits_info = hit_data.get(gid, {"hits": 0, "last_used": ""})
items.append(
{
"id": gid,
"name": cache.get("name")
or cache.get("gene_name")
or cache.get("summary", "").split(":")[0][:10]
or gid,
"summary": cache.get("summary")
or cache.get("action", {}).get("action_description")
or cache.get("description")
or "",
"rule_text": cache.get("rule_text")
or cache.get("action", {}).get("action_template")
or cache.get("pattern_key")
or "",
"fitness": cache.get("fitness")
or cache.get("fitnessScore")
or cache.get("metadata", {}).get("fitness_score")
or None,
"layer": cache.get("layer") or "",
"mutation_space": cache.get("mutation_space")
or cache.get("mutationSpace")
or "",
"gene_status": cache.get("status")
or ("active" if hits_info["hits"] > 0 else "unused"),
"hits": hits_info["hits"],
"last_used": hits_info["last_used"],
"mutations": gene_mutations.get(gid, []),
}
)
# Sort: hits > 0 first (desc), then hits == 0
items.sort(key=lambda x: (-x["hits"], x["id"]))
matched = sum(1 for i in items if i["hits"] > 0)
mutations_linked = sum(len(i["mutations"]) for i in items)
total = len(items)
return {
"total": total,
"matched": matched,
"mutations_linked": mutations_linked,
"unused": total - matched,
"items": items,
}
def enrich_gene_names(reviews, genes_data):
"""Fill gene_name in review mutations from genes data."""
gene_names = {g["id"]: g["name"] for g in genes_data.get("items", [])}
for review in reviews:
for m in review.get("mutations", []):
if m.get("gene_id") and not m.get("gene_name"):
# gene_id may be comma-separated
ids = split_gene_ids(m["gene_id"])
names = [gene_names.get(gid, gid) for gid in ids]
m["gene_name"] = ", ".join(n for n in names if n)
def build_dashboard_data(db_path, data_dir):
"""Build complete dashboard JSON."""
c = conn(db_path)
try:
overview = build_overview(c)
reviews = build_reviews(c)
genes = build_genes(c, data_dir)
enrich_gene_names(reviews, genes)
finally:
c.close()
now_utc = datetime.now(timezone.utc)
return {
"generated_at": f"{now_utc.month}月{now_utc.day}日 {now_utc.hour:02d}:{now_utc.minute:02d} UTC",
"overview": overview,
"reviews": reviews,
"genes": genes,
}
def render(db_path, data_dir, output_path, template_path=None):
"""Main render: DB → JSON → inject into template → write HTML."""
tpl = Path(template_path) if template_path else TEMPLATE_PATH
if not tpl.exists():
print(f"ERROR: template not found: {tpl}", file=sys.stderr)
sys.exit(1)
template = tpl.read_text(encoding="utf-8")
data = build_dashboard_data(db_path, data_dir)
data_json = json.dumps(data, ensure_ascii=False, indent=None)
html = template.replace("__DASHBOARD_DATA__", data_json, 1)
out = Path(output_path)
out.parent.mkdir(parents=True, exist_ok=True)
out.write_text(html, encoding="utf-8")
return str(out)
def main():
parser = argparse.ArgumentParser(description="Render evolution dashboard from DB")
parser.add_argument("--db", default=str(DEFAULT_DB), help="Path to evolution.db")
parser.add_argument(
"--data-dir",
default=str(DEFAULT_DATA_DIR),
help="Path to evolution-data directory",
)
parser.add_argument(
"--output",
default=None,
help="Output HTML path (default: <data-dir>/dashboard.html)",
)
parser.add_argument(
"--template", default=None, help="Path to dashboard-template.html"
)
args = parser.parse_args()
db_path = Path(args.db)
data_dir = Path(args.data_dir)
output = args.output or str(data_dir / "dashboard.html")
if not db_path.exists():
print(
json.dumps(
{"status": "error", "reason": f"DB not found: {db_path}"},
ensure_ascii=False,
)
)
sys.exit(1)
result_path = render(db_path, data_dir, output, args.template)
print(json.dumps({"status": "ok", "path": result_path}, ensure_ascii=False))
if __name__ == "__main__":
main()
#!/usr/bin/env python3
# Copyright 2026 Beijing Volcano Engine Technology Co., Ltd.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Initialize the evolution SQLite database."""
import sqlite3
import os
import sys
import argparse
from _workspace import resolve_workspace_root
DEFAULT_DB_DIR = os.path.join(resolve_workspace_root(), "evolution-data")
# Windows UTF-8 fix
for stream in [sys.stdout, sys.stderr, sys.stdin]:
if stream and hasattr(stream, "reconfigure") and stream.encoding != "utf-8":
stream.reconfigure(encoding="utf-8")
DEFAULT_DB_PATH = os.path.join(DEFAULT_DB_DIR, "evolution.db")
def get_db_path():
parser = argparse.ArgumentParser(description="Initialize evolution SQLite database")
parser.add_argument(
"db_path_positional", nargs="?", default=None, help="Legacy positional DB path"
)
parser.add_argument("--db", default=None, help="Path to evolution.db")
args = parser.parse_args()
return args.db or args.db_path_positional or DEFAULT_DB_PATH
def init_db(db_path):
os.makedirs(os.path.dirname(db_path), exist_ok=True)
base_dir = os.path.dirname(db_path)
os.makedirs(os.path.join(base_dir, "trajectories", "golden"), exist_ok=True)
os.makedirs(os.path.join(base_dir, "trajectories", "corrections"), exist_ok=True)
os.makedirs(os.path.join(base_dir, "reports"), exist_ok=True)
os.makedirs(os.path.join(base_dir, "tmp"), exist_ok=True)
conn = sqlite3.connect(db_path)
cur = conn.cursor()
cur.executescript("""
CREATE TABLE IF NOT EXISTS signals (
id INTEGER PRIMARY KEY AUTOINCREMENT,
session_id TEXT,
type TEXT NOT NULL CHECK(type IN ('correction','negative','positive','suggestion','preference','clarification')),
layer TEXT CHECK(layer IN ('identity','context','protocol','capability','runtime')),
severity TEXT DEFAULT 'medium' CHECK(severity IN ('low','medium','high')),
raw_text TEXT NOT NULL,
context TEXT,
processed INTEGER DEFAULT 0,
created_at TEXT DEFAULT (datetime('now'))
);
CREATE TABLE IF NOT EXISTS trajectories (
id INTEGER PRIMARY KEY AUTOINCREMENT,
type TEXT NOT NULL CHECK(type IN ('golden','correction','candidate')),
task_type TEXT,
tags TEXT,
key_steps TEXT,
error_approach TEXT,
correct_approach TEXT,
root_cause TEXT,
file_path TEXT,
session_id TEXT,
verified INTEGER DEFAULT 0,
verify_count INTEGER DEFAULT 0,
created_at TEXT DEFAULT (datetime('now'))
);
CREATE TABLE IF NOT EXISTS reviews (
id INTEGER PRIMARY KEY AUTOINCREMENT,
review_id TEXT UNIQUE NOT NULL,
status TEXT DEFAULT 'prepared' CHECK(status IN ('prepared','running','completed','failed')),
run_reason TEXT,
daily_digest_json TEXT,
pending_summary_json TEXT,
worker_started_at TEXT,
worker_finished_at TEXT,
worker_error TEXT,
created_at TEXT DEFAULT (datetime('now'))
);
CREATE TABLE IF NOT EXISTS mutations (
id INTEGER PRIMARY KEY AUTOINCREMENT,
review_id TEXT,
proposal_group_id TEXT,
proposal_summary TEXT,
proposal_status TEXT DEFAULT 'pending' CHECK(proposal_status IN ('pending','presented','accepted','rejected','stale','superseded')),
presented_at TEXT,
decision_at TEXT,
expires_at TEXT,
target_file TEXT NOT NULL,
mutation_type TEXT CHECK(mutation_type IN ('add','modify','remove')),
layer TEXT CHECK(layer IN ('identity','context','protocol','capability','runtime')),
description TEXT,
before_text TEXT,
after_text TEXT,
signal_ids TEXT,
source TEXT DEFAULT 'evolution' CHECK(source IN ('evolution','user-direct','snapshot-diff','positive-anchor')),
status TEXT DEFAULT 'proposed' CHECK(status IN ('proposed','approved','applied','verified','rejected')),
pareto_check TEXT,
verification_criteria TEXT,
layer_reason TEXT,
gene_id TEXT,
gene_reason TEXT,
session_id TEXT,
created_at TEXT DEFAULT (datetime('now')),
applied_at TEXT
);
CREATE TABLE IF NOT EXISTS gene_matches (
id INTEGER PRIMARY KEY AUTOINCREMENT,
review_id TEXT,
signal_id INTEGER,
gene_id TEXT NOT NULL,
reason TEXT NOT NULL,
judgment_type TEXT DEFAULT 'root_cause_fit' CHECK(judgment_type IN ('root_cause_fit','surface_relief','fallback_only')),
confidence REAL,
created_at TEXT DEFAULT (datetime('now'))
);
CREATE TABLE IF NOT EXISTS evolution_runs (
id INTEGER PRIMARY KEY AUTOINCREMENT,
run_id TEXT UNIQUE NOT NULL,
signals_analyzed INTEGER DEFAULT 0,
mutations_proposed INTEGER DEFAULT 0,
mutations_applied INTEGER DEFAULT 0,
sessions_analyzed INTEGER DEFAULT 0,
sessions_cost_usd REAL DEFAULT 0,
evolution_cost_usd REAL DEFAULT 0,
report_path TEXT,
saturation_note TEXT,
created_at TEXT DEFAULT (datetime('now'))
);
CREATE INDEX IF NOT EXISTS idx_signals_type ON signals(type);
CREATE INDEX IF NOT EXISTS idx_signals_processed ON signals(processed);
CREATE INDEX IF NOT EXISTS idx_signals_layer ON signals(layer);
CREATE INDEX IF NOT EXISTS idx_trajectories_type ON trajectories(type);
CREATE INDEX IF NOT EXISTS idx_mutations_status ON mutations(status);
CREATE INDEX IF NOT EXISTS idx_mutations_proposal_status ON mutations(proposal_status);
CREATE INDEX IF NOT EXISTS idx_mutations_proposal_group ON mutations(proposal_group_id);
CREATE INDEX IF NOT EXISTS idx_gene_matches_signal ON gene_matches(signal_id);
CREATE INDEX IF NOT EXISTS idx_gene_matches_gene ON gene_matches(gene_id);
""")
conn.commit()
conn.close()
print(f"Database initialized: {db_path}")
if __name__ == "__main__":
init_db(get_db_path())
#!/usr/bin/env python3
# ruff: noqa: E402
# Copyright 2026 Beijing Volcano Engine Technology Co., Ltd.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""
Migration: Add source and session_id columns to mutations table.
Safe to run multiple times — uses IF NOT EXISTS logic.
Usage:
python db-migrate-source.py [path-to-evolution.db]
"""
import sqlite3
import os
import sys
for stream in [sys.stdout, sys.stderr, sys.stdin]:
if stream and hasattr(stream, "reconfigure") and stream.encoding != "utf-8":
stream.reconfigure(encoding="utf-8")
from _workspace import resolve_workspace_root
DEFAULT_DB_PATH = os.path.join(resolve_workspace_root(), "evolution-data/evolution.db")
def migrate(db_path):
if not os.path.exists(db_path):
print(f"DB not found: {db_path}")
sys.exit(1)
conn = sqlite3.connect(db_path)
cur = conn.cursor()
# Check existing columns
cur.execute("PRAGMA table_info(mutations)")
columns = {row[1] for row in cur.fetchall()}
added = []
if "source" not in columns:
cur.execute("""
ALTER TABLE mutations
ADD COLUMN source TEXT DEFAULT 'evolution'
CHECK(source IN ('evolution','user-direct','snapshot-diff'))
""")
added.append("source")
if "session_id" not in columns:
cur.execute("ALTER TABLE mutations ADD COLUMN session_id TEXT")
added.append("session_id")
conn.commit()
conn.close()
if added:
print(f"Migration complete. Added columns: {', '.join(added)}")
else:
print("No migration needed — columns already exist.")
if __name__ == "__main__":
db_path = sys.argv[1] if len(sys.argv) > 1 else DEFAULT_DB_PATH
migrate(db_path)
#!/usr/bin/env python3
# ruff: noqa: E402
# Copyright 2026 Beijing Volcano Engine Technology Co., Ltd.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Database migration for v0.3.1: expand signal types + add positive-anchor source."""
import sqlite3
import os
import sys
import argparse
for stream in [sys.stdout, sys.stderr, sys.stdin]:
if stream and hasattr(stream, "reconfigure") and stream.encoding != "utf-8":
stream.reconfigure(encoding="utf-8")
from _workspace import resolve_workspace_root
DEFAULT_DB_PATH = os.path.join(resolve_workspace_root(), "evolution-data/evolution.db")
MIGRATION_SQL = """
-- v0.3.1: Expand signal types to include preference and clarification
CREATE TABLE IF NOT EXISTS signals_v029 (
id INTEGER PRIMARY KEY AUTOINCREMENT,
session_id TEXT,
type TEXT NOT NULL CHECK(type IN ('correction','negative','positive','suggestion','preference','clarification')),
layer TEXT CHECK(layer IN ('identity','context','protocol','capability','runtime')),
severity TEXT DEFAULT 'medium' CHECK(severity IN ('low','medium','high')),
raw_text TEXT NOT NULL,
context TEXT,
processed INTEGER DEFAULT 0,
created_at TEXT DEFAULT (datetime('now'))
);
INSERT INTO signals_v029 (id, session_id, type, layer, severity, raw_text, context, processed, created_at)
SELECT id, session_id, type, layer, severity, raw_text, context, processed, created_at
FROM signals;
DROP TABLE signals;
ALTER TABLE signals_v029 RENAME TO signals;
CREATE INDEX IF NOT EXISTS idx_signals_type ON signals(type);
CREATE INDEX IF NOT EXISTS idx_signals_processed ON signals(processed);
CREATE INDEX IF NOT EXISTS idx_signals_layer ON signals(layer);
-- v0.3.1: Expand mutations source to include positive-anchor
CREATE TABLE IF NOT EXISTS mutations_v029 (
id INTEGER PRIMARY KEY AUTOINCREMENT,
review_id TEXT,
proposal_group_id TEXT,
proposal_summary TEXT,
proposal_status TEXT DEFAULT 'pending' CHECK(proposal_status IN ('pending','presented','accepted','rejected','stale','superseded')),
presented_at TEXT,
decision_at TEXT,
expires_at TEXT,
target_file TEXT NOT NULL,
mutation_type TEXT CHECK(mutation_type IN ('add','modify','remove')),
layer TEXT CHECK(layer IN ('identity','context','protocol','capability','runtime')),
description TEXT,
before_text TEXT,
after_text TEXT,
signal_ids TEXT,
source TEXT DEFAULT 'evolution' CHECK(source IN ('evolution','user-direct','snapshot-diff','positive-anchor')),
status TEXT DEFAULT 'proposed' CHECK(status IN ('proposed','approved','applied','verified','rejected')),
pareto_check TEXT,
verification_criteria TEXT,
layer_reason TEXT,
gene_id TEXT,
gene_reason TEXT,
session_id TEXT,
created_at TEXT DEFAULT (datetime('now')),
applied_at TEXT
);
INSERT INTO mutations_v029 SELECT * FROM mutations;
DROP TABLE mutations;
ALTER TABLE mutations_v029 RENAME TO mutations;
CREATE INDEX IF NOT EXISTS idx_mutations_status ON mutations(status);
CREATE INDEX IF NOT EXISTS idx_mutations_proposal_status ON mutations(proposal_status);
CREATE INDEX IF NOT EXISTS idx_mutations_proposal_group ON mutations(proposal_group_id);
-- Record migration
CREATE TABLE IF NOT EXISTS schema_migrations (
version TEXT PRIMARY KEY,
applied_at TEXT DEFAULT (datetime('now'))
);
INSERT OR IGNORE INTO schema_migrations (version) VALUES ('v0.3.1');
"""
def migrate(db_path=None):
path = db_path or DEFAULT_DB_PATH
if not os.path.exists(path):
print(f"DB not found: {path}", file=sys.stderr)
sys.exit(1)
conn = sqlite3.connect(path)
try:
cur = conn.cursor()
cur.execute("SELECT version FROM schema_migrations WHERE version = 'v0.3.1'")
if cur.fetchone():
print("Migration v0.3.1 already applied.")
conn.close()
return
except sqlite3.OperationalError:
pass
conn.executescript(MIGRATION_SQL)
conn.close()
print(f"Migration v0.3.1 applied to {path}")
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Migrate evolution DB to v0.3.1")
parser.add_argument("--db", default=None)
args = parser.parse_args()
migrate(args.db)
#!/usr/bin/env python3
# ruff: noqa: E402
# Copyright 2026 Beijing Volcano Engine Technology Co., Ltd.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Detect evolution signal candidates from user messages via Hook.
Can be used as:
1. PostToolUse hook — reads JSON from stdin, auto-records high-confidence signals
2. Library — import detect_signals() for batch detection in scan-history.py
"""
import json
import os
import re
import sys
for stream in [sys.stdout, sys.stderr, sys.stdin]:
if stream and hasattr(stream, "reconfigure") and stream.encoding != "utf-8":
stream.reconfigure(encoding="utf-8")
from _workspace import resolve_workspace_root
DEFAULT_DB_PATH = os.path.join(resolve_workspace_root(), "evolution-data/evolution.db")
# ── Pattern 定义 ──────────────────────────────────────────────
PATTERNS = {
"correction": {
"high_confidence": [
r"不[要是]这[样个么]",
r"错了",
r"不对[,。!\s]",
r"应该[用是]",
r"正确的[做方]法",
r"我说的是.{1,20}不是",
r"别[再这]",
r"不是这个意思",
r"你搞[错混]了",
r"重[做来]",
],
"low_confidence": [
r"我刚[才说]的是",
r"再[试说]一[次遍]",
r"看清楚",
],
},
"negative": {
"high_confidence": [
r"太[长短慢]了",
r"AI[味感]",
r"机器[味感]",
r"套话",
r"又来了",
r"跟上次一样",
r"不[是要]我[想要]的",
r"废话",
r"没用",
],
"low_confidence": [
r"^唉",
r"^算了",
r"^行吧",
r"^凑合",
r"emmm",
r"无语",
],
},
"positive": {
"high_confidence": [
r"[完太]美了?",
r"就[是这][这样]",
r"做得[好不]错",
r"比上次好",
r"nice|great|perfect",
],
"low_confidence": [],
},
"suggestion": {
"high_confidence": [
r"以后[可能]以",
r"下次",
r"能不能",
r"要是能.{1,30}就好了",
r"这种情况应该",
r"建议",
r"最好[能是]",
r"希望你",
],
"low_confidence": [
r"有没有办法",
r"怎么才能",
],
},
"preference": {
"high_confidence": [
r"我[更比较]喜欢",
r"我习惯",
r"我[一通]般[都会]",
r"我的风格",
r"[别不][要用]给?我",
r"用.{1,10}格式",
r"[简详][短细][一点些]",
],
"low_confidence": [],
},
"clarification": {
"high_confidence": [
r"我[的这]里[说的指]的是",
r"[所其]谓.{1,15}[就指]的?是",
r"你[理搞]解错了",
r"不是.{1,20}而是",
r"准确[来地]说",
r"补充一下",
r"我[再解]释一下",
],
"low_confidence": [],
},
}
def detect_signals(text):
"""对文本做 pattern 匹配,返回候选信号列表。"""
candidates = []
for signal_type, patterns in PATTERNS.items():
high_matches = []
low_matches = []
for pat in patterns.get("high_confidence", []):
if re.search(pat, text, re.IGNORECASE):
high_matches.append(pat)
for pat in patterns.get("low_confidence", []):
if re.search(pat, text, re.IGNORECASE):
low_matches.append(pat)
if high_matches or low_matches:
confidence = "high" if high_matches else "low"
candidates.append(
{
"type": signal_type,
"confidence": confidence,
"matched_patterns": high_matches + low_matches,
"high_count": len(high_matches),
"low_count": len(low_matches),
}
)
return candidates
def auto_record(db_path, signal_type, raw_text, session_id, context):
"""Write a signal directly to DB. Returns signal_id or None."""
if not os.path.exists(db_path):
return None
import sqlite3
conn = sqlite3.connect(db_path)
cur = conn.cursor()
cur.execute(
"""
INSERT INTO signals (session_id, type, severity, raw_text, context)
VALUES (?, ?, 'medium', ?, ?)
""",
(session_id, signal_type, raw_text[:500], context),
)
conn.commit()
signal_id = cur.lastrowid
conn.close()
return signal_id
def handle_hook_input():
"""从 Hook stdin 读取 JSON,提取用户消息做检测。"""
try:
raw = sys.stdin.read()
if not raw.strip():
sys.exit(0)
data = json.loads(raw)
except (json.JSONDecodeError, IOError):
sys.exit(0)
messages = data.get("messages", [])
user_text = ""
for msg in reversed(messages):
if msg.get("role") == "user":
content = msg.get("content", "")
if isinstance(content, list):
texts = [
b.get("text", "")
for b in content
if isinstance(b, dict) and b.get("type") == "text"
]
user_text = "\n".join(texts)
elif isinstance(content, str):
user_text = content
break
if not user_text or len(user_text) < 2:
sys.exit(0)
candidates = detect_signals(user_text)
if not candidates:
sys.exit(0)
auto_recorded = []
needs_confirmation = []
db_path = DEFAULT_DB_PATH
session_id = (data.get("session_id") or "")[:12]
for cand in candidates:
if cand["confidence"] == "high":
sid = auto_record(
db_path,
cand["type"],
user_text,
session_id,
f"auto-detected by hook, patterns: {cand['matched_patterns']}",
)
if sid:
auto_recorded.append({"signal_id": sid, "type": cand["type"]})
else:
needs_confirmation.append(cand)
else:
needs_confirmation.append(cand)
context_parts = []
if auto_recorded:
ids_str = ", ".join(f"#{r['signal_id']}({r['type']})" for r in auto_recorded)
context_parts.append(
f"[Evolution] Auto-recorded {len(auto_recorded)} signal(s): {ids_str}"
)
if needs_confirmation:
types_str = ", ".join(c["type"] for c in needs_confirmation)
context_parts.append(
f"[Evolution] Candidate signal(s) need confirmation: {types_str}. "
f"Consider recording via signal-record.py if appropriate."
)
if context_parts:
output = {
"hookSpecificOutput": {
"hookEventName": "PostToolUse",
"additionalContext": " | ".join(context_parts),
}
}
print(json.dumps(output, ensure_ascii=False))
if __name__ == "__main__":
handle_hook_input()
测试对话 1:先调研再提问
场景:用户要求创建一个日报生成 Skill,Agent 没有先调研就直接问用户需求细节。
预期信号:correction / protocol / medium
---
对话
User: 帮我创建一个产品经理的日报生成 skill
Agent: 好的!我来帮你设计。请问你的日报需要包含哪些模块?比如: 1. 今日完成的工作 2. 明日计划 3. 项目状态 4. 阻塞与风险
你希望用什么格式输出?Markdown 还是 HTML?
User: 请你下次问我之前能不能先搜索一下一般产品经理的日报关注什么
Agent: 你说得对,我应该先调研行业通用做法再来问你。让我搜索一下……
(Agent 搜索后发现 PM 日报通常包含:今日完成、项目状态、阻塞与风险、明日计划、关键数据指标、洞察与反思)
Agent: 搜索结果显示,PM 日报通常包含这些模块:
- 今日完成(必选)
- 项目状态 + 风险标记(必选)
- 阻塞与风险(必选)
- 明日计划(必选)
- 关键数据指标(可选)
- 洞察与反思(可选)
基于这个调研,我建议我们的 skill 采用 4 必选 + 2 可选的结构。你觉得这个框架怎么样?需要调整吗?
---
预期信号提取
{
"type": "correction",
"layer": "protocol",
"severity": "medium",
"raw_text": "请你下次问我之前能不能先搜索一下一般产品经理的日报关注什么",
"context": "用户要求创建日报 skill,Agent 未调研就直接询问用户需求细节"
}预期归因
- 根因:Agent 工作流程缺少"先调研再提问"步骤
- 层归因:Protocol(执行流程问题)
- 潜在 Mutation:在 AGENTS.md 或 SOUL.md 中加入规则——收到创建类需求时,先搜索行业通用做法,再结合调研结果向用户提问
测试对话数据
3 段真实对话片段(已脱敏),用于测试 Evolution Skill 的信号收集和进化分析流程。
每个文件包含:
- 对话上下文
- 用户反馈信号(标注)
- 预期信号类型和归因
使用方法:OpenClaw Agent 读取对话片段,模拟信号收集 → Gate 检查 → 进化分析。