学习飞轮自动闭环(Flywheel Auto-Capture)
August 20, 2026 · View on GitHub
P0-1 / P0-2:让学习飞轮无需宿主 agent 主动调用
graphflow_report_outcome也能自动闭环。
背景
GraphFlow 的飞轮(episode + skill)依赖宿主 agent 主动调用
graphflow_report_outcome / graphflow outcome report <episodeId> <success>
才会把 pending 的 episode 回填为真实结局(pass/fail)并触发 skill 分数更新。
如果宿主 agent 忘了调用(或没有 MCP 连接),episode 永远停留在 pending,
飞轮退化为只有 context 运行、没有任何学习回填的"空转"状态。
本特性提供四条互补的自动闭环路径(auto-capture 默认开启,
GRAPHFLOW_AUTO_CAPTURE=0 可显式关闭;hooks 需安装、backfill 为一次性操作):
- 自动记录(auto-capture):run 完成路径上自动生成
pendingepisode 并写入 会话日志(.graphflow/session-journal.jsonl)。 - Claude Code hooks:
SessionStart / SessionEnd / Stop时自动调用graphflow outcome report <episodeId> <success>回填真实结局。 - DeepSeek Harness glue(
dsh/plugin.mjs):仅在agent/disposed(Claude Code SessionEnd 对应)时尝试关闭 pending episode。默认不报 success;只有GRAPHFLOW_HOOK_SUCCESS=true|false才 spawngraphflow outcome report。DSH 的session/flush是 live checkpoint, 不是会话结束,不监听、更不能在此默认outcome report true。 - 一次性回填(backfill):从历史事件(learning-events.jsonl)或 git log 挖掘 episode 记录,补上此前"没回填"的数据。
机制
host agent ──(run/context 完成)──▶ orchestrator
│
maybeAutoCaptureEpisode (默认开启,GRAPHFLOW_AUTO_CAPTURE=0 关闭)
├─ 结局未知 (DELEGATED / HUMAN_REVIEW_REQUIRED)
│ └─ 写入 pending episode(绝不伪造 COMPLETED)
└─ 追加会话日志 .graphflow/session-journal.jsonl
│
Claude Code SessionEnd / Stop ──▶ session.sh ──▶ 读取最新一条 pending
│
\$2 为空 ──▶ 跳过回填(episode 保持 pending)
\$2 为 true/false ──▶ graphflow outcome report <episodeId> <success>
│
updateEpisodeOutcome(pending → pass/fail);有质量 lessons 才 applySkillLearning
自动记录(src/hooks/auto-capture.ts)
- 开关:默认开启。环境变量未设置或设置为
1 / true / on / yes / enabled时开启; 设置GRAPHFLOW_AUTO_CAPTURE=0(接受0 / false / off / no / disabled)时关闭, 或在调用点显式传入enabled: false。 - 时机:
finalizeEpisode(run 完成路径)对结局未知的 run(DELEGATED、HUMAN_REVIEW_REQUIRED)自动生成outcome: "pending"的 episode, 并把{ episodeId, task, taskKey, status, createdAt }追加到<workspace>/.graphflow/session-journal.jsonl。 - 原则:
- 不伪造结局 —— 自动生成的记录一律
pending;COMPLETED / FAILED等 结局已知的 run 直接跳过,不产生 pending 噪音。 - 幂等去重 —— 同一任务在 30 分钟窗口内的重复 run 复用最近一条记录。
- 不阻断 —— 任何失败仅 log warn,绝不中断编排主流程。
- 不伪造结局 —— 自动生成的记录一律
- 已启用 episodic memory 时复用其已记录的 episodeId(不重复写节点)。
Claude Code hooks(src/integrations/claude-code-hooks.ts)
生成器产出 ~/.claude/settings.json 风格的 hooks 片段 + 一个 bash 脚本
(~/.claude/graphflow-hooks/session.sh,默认目录可覆盖):
{
"hooks": {
"SessionStart": [
{ "type": "command", "command": "bash '/home/you/.claude/graphflow-hooks/session.sh' start", "timeout": 30 }
],
"SessionEnd": [
{ "type": "command", "command": "bash '/home/you/.claude/graphflow-hooks/session.sh' end", "timeout": 30 }
],
"Stop": [
{ "type": "command", "command": "bash '/home/you/.claude/graphflow-hooks/session.sh' end", "timeout": 30 }
]
}
}
由 buildClaudeCodeHooksConfig({ hooksDir, settingsPath }) 生成(三个事件均带
timeout: 30,SessionEnd 与 Stop 共用 end 命令)。
行为:
start:仅准备会话日志目录(不写图)。end/Stop:读取日志中最新一条pending-episode。成功值不默认 true:$2为空则跳过回填、episode 保持 pending;显式true/false才调用graphflow outcome report <episodeId> <success>回填(与手动 report_outcome 等价:pending → pass/fail;skill 分数更新还需质量 lessons):session.sh end true/session.sh end false。- 安全:脚本内所有路径/命令均做 shell 转义(单引号包裹 + 双引号内 fallback 转义); 合并 settings.json 时保留用户已有 hooks;JSON 损坏时拒绝覆盖。
- 运行时覆盖(无需重新生成):
GRAPHFLOW_HOOK_JOURNAL、GRAPHFLOW_HOOK_BIN、GRAPHFLOW_HOOK_CONFIG。
安装 / 卸载
推荐一键注册(检测到 ~/.claude 时自动写入 hooks,并由 doctor 自检):
graphflow install --json # InstallReport.claudeCodeHooks + doctor.checks[category=hooks]
graphflow doctor --json # missing hooks → ok=false + remediation
graphflow uninstall # 同步移除 GraphFlow 写入的 hooks 条目与 session.sh
也可直接调用 API:
import { installClaudeCodeHooks, uninstallClaudeCodeHooks } from "@roarpeng/graphflow";
installClaudeCodeHooks({
graphflowBin: "graphflow", // 默认;也可用绝对路径
configPath: undefined, // 需要时传工作区 graphflow.config.json
});
uninstallClaudeCodeHooks(); // 只移除本生成器写入的条目
注意:hook 进程需要 graphflow CLI 在 PATH 中
(npm i -g @roarpeng/graphflow),否则用 GRAPHFLOW_HOOK_BIN 指向绝对路径。
开启 pending 自动记录仍需 GRAPHFLOW_AUTO_CAPTURE 未被设为 0(hooks 只负责回填已有 pending)。
DeepSeek Harness glue(dsh/plugin.mjs)
DSH 没有 Claude Code 的 SessionEnd 文件 hook。Bundle 行 id: graphflow-dsh
(name: '@roarpeng/graphflow/dsh')只在 agent/disposed 时读取同一份
.graphflow/session-journal.jsonl,spawn graphflow outcome report <episodeId> true。
不监听 session/flush:那是进行中的耐久性 checkpoint,不是会话结束;此时
outcome report true 会把飞轮标成成功,而 agent 还在工作。
失败吞掉,不进入 harness 循环。GRAPHFLOW_AUTO_CAPTURE=0 时跳过。
dsh plugin --profile web add @roarpeng/graphflow 即挂上该 glue(不必先
graphflow install)。graphflow install 仍会把同一 insert 写入
$DSH_HOME/cordis.patch.yml,供已有 ~/.dsh 的用户做 home overlay。
一次性回填(scripts/backfill-episodes.cjs)
把此前缺失的学习信号补进图存储(默认 graphflow-out/graphflow-graph.json,
transport=file):
node scripts/backfill-episodes.cjs # 自动选择数据源
node scripts/backfill-episodes.cjs --root /path/to/repo # 指定仓库根目录
node scripts/backfill-episodes.cjs --dry-run # 只看不动
node scripts/backfill-episodes.cjs --events /path/events.jsonl # 强制指定事件文件
数据源优先级:
.graphflow/learning-events.jsonl(约定位置)→ 回退graphflow-out/learning-events.jsonl(默认 eventsPath)→ 回退<root>/learning-events.jsonl。解析真实反馈事件{query, passed, tokenCost, retries},passed → pass / fail。- 均不存在时,从
git log最近 200 条 commit 的 subject/body 挖掘 (已合入历史的 commit 视为真实完成证据,outcome 用pass)。
特性:幂等(episode id 由内容/commit hash 确定性生成,重复执行不重复写入)、 容错(坏行/无 git/存储损坏均不覆盖数据)、原子写(temp + rename)。
诊断
graphflow diagnose 中 0 skill / 0 episode 的常见原因与本特性的对应关系:
| 现象 | 原因 | 解法 |
|---|---|---|
| episode 全为 pending 且数量不变 | 宿主 agent 未调 report_outcome,或 SessionEnd / dsh agent/disposed 未传入显式 success | 显式 graphflow_report_outcome / `session.sh end true |
pendingPercent / fidelity.pendingRatio 高 | 大量 episode 仍待显式结局 | diagnose/flywheel 的 memoryAttribution.confidence.pendingPercent 与 fidelity.pendingRatio |
| 0 episode | 从未启用 episodic memory / 无回填 | 确认 GRAPHFLOW_AUTO_CAPTURE 未被设为 0 + 安装 hooks,或运行 backfill 脚本 |
| 0 skill | 飞轮无 episode 可学 | 同上,先有 episode 才有 skill 学习 |
局限与风险
- hooks 只回填最新一条 pending(
tail -n 1);同一会话多条 pending 时, 较早的保持 pending(可审计,不伪造)。 Stop触发时机早于SessionEnd(Stop在用户中断/结束前触发,SessionEnd在会话正式结束时触发),两者共用 end 脚本,重复回填会被updateEpisodeOutcome自然覆盖(幂等)。- backfill 的 git 分支把 commit 视为
pass,仅作为无事件数据时的兜底估计。 - auto-capture 默认开启(飞轮自证);如需恢复旧行为,设置
GRAPHFLOW_AUTO_CAPTURE=0显式关闭。开启时也只新增 pending 记录与日志, 不影响现有 episode/skill 语义。