


从本文开始,系列进入 SkillSentry 的架构可靠性专题。
前一组文章解决的是效率:更少的 LLM 调用、更少的切换、更清楚的入口、更可控的 token 用量。效率问题处理完,AI Skill 测评系统会暴露出另一个更底层的问题:长流程能不能稳定跑完,断了之后能不能恢复,多个工具之间的契约会不会分叉。
这一组会依次讲五件事:
状态机 → 断点续跑 → 代码化 → 工具箱 → 演进史本文先讲第一件事:为什么 SkillSentry 需要 Pipeline 状态机,以及为什么 session.json 、 grader-report 、 sentry-static 、 authoritative_pass_rate 、 PASS / CONDITIONAL PASS / FAIL 这些名字必须收敛成统一契约。
在做这次架构收敛之前,SkillSentry 在 standard/full 模式下反复遇到同一类问题:
AI 编排器的上下文疲劳。
具体表现:
这不是 prompt 写得不好的问题——是 LLM 应用的结构性问题: 长对话中,编排器的注意力会衰减,导致非强制路径的步骤被静默跳过。
在做这次架构收敛之前,我们反复被同一类问题困扰:流程被跳过、产物被遗忘、口径发生分叉。最后我们发现,这三个问题其实对应了 LLM 编排系统架构收敛的三个判断条件:
以下记录了 SkillSentry 如何用具体改动逐一解决这三个问题。
我们从这次重构中提炼了三条对任何 LLM 编排系统都适用的教训:
1. 文件状态 > 上下文记忆
LLM 的上下文窗口不是数据库。长对话中任何依赖「AI 记得之前做了什么」的设计都不可靠。用文件(session.json、active-pipeline.json)作为 single source of truth。
2. 强制路径 > 提醒标记
⛔ 禁止跳过 这种 prompt 级约束,在短对话中有效,长对话中失效。要保证某个步骤一定执行,唯一靠谱的方式是把它放进状态机的合法转移路径里。
3. 降级 > 终止
AI 系统的超时和错误是常态。设计时把「失败后怎么办」想清楚(一级→二级→三级降级),比设计「怎么不失败」更有工程价值。
SkillSentry 当前这次收敛不是一次继续加功能的版本,而是一次架构收敛:
Pipeline 状态机已经有了,下一步是让所有文档、工具和文章说同一套 contract。当前版本的当前契约:
grader-report | |
sentry-report | |
sentry-static | |
authoritative_pass_rate | |
PASS / CONDITIONAL PASS / FAIL | |
S / A / B / C / D / F |
所以这篇文章现在应按"旧版奠定架构基础,当前版完成契约收敛"来读。
旧版架构(「薄编排器 + 5 个原子工具 + 文件契约」)在短测评(smoke/quick)中运行良好,但在 standard/full 模式下暴露了前文描述的上下文疲劳问题——跳步、凭记忆执行、sync 遗忘。
每个步骤有独立的输入/输出契约。步骤间不能跳线——缺失前置产物时触发降级,而不是静默跳过。
旧版的做法:
编排器凭记忆决定下一步是什么当前 Pipeline 架构的做法:
session.json.pipeline = ["static", "cases", "case-quality-check", "sync-pull", "sync-push-cases", "executor-with", "grader-report", "sync-push-results", "publish"]Pipeline 数组在 Step 2 写入后就是铁律 ——不在数组里的步骤 = 不存在。编排器不需要"记得"下一步是什么,只需要查数组。
session.json.last_step 是唯一的流转依据。不靠上下文记忆。
合法状态转移:idle → step-0 → step-1 → step-2 → [pipeline per mode] → publish → idle非法转移(如从 cases 直接跳到 report)→ abort + 告警。
超时和失败有三级降级机制:
[DEGRADED-STAT] |
降级产物仍然推进 Pipeline ——标注降级标记,但不终止。
同 SKILL.md hash + 同模式 + 同天 → 提示复用已有结果,跳过重复执行。
这避免了用户多次说「测评」导致的重复消耗。
当前 Pipeline 架构的主编排器做了一个根本性的改变: 每一轮调度都是无状态的。
每一轮循环:1. 读 session.json → 取 last_step2. 查 pipeline 数组 → 确定 next_step3. 读取 next_step 对应子工具的 SKILL.md(必须 read,不能凭记忆)4. spawn subagent5. 写入 active-pipeline.json(断点持久化)6.yield 等待7.resume 后验收产物(检查文件是否存在)8. 通知用户 + 更新 session.json9. 删除 active-pipeline.json10. 回到 1(不靠记忆,靠文件状态)核心原则:主会话只调度,不执行。 所有复杂逻辑都委派给 subagent。契约收敛后,主流程里的评分和报告生成统一叫 grader-report ;独立 sentry-report 只用于已有 grading 后重新生成报告。
历史上有三个工具处理静态分析:
当前稳定入口是 sentry-static ,包含三步:
Sub-step 1: Lint(静态规则检查)Sub-step 2: Trigger(TP/TN 触发率评估)Sub-step 3: Summary(综合两者,输出发布建议)这里的 Lint 是历史命名延续,当前对外不再使用 L0-L5 作为质量等级。静态检查关注的是 description、HiL、复杂度、冗余规则、可测试性和触发风险。
支持 --lint-only 和 --trigger-only 子模式,用于兼容旧命令和局部验证。
为什么不继续叫 sentry-check?因为增加了 Sub-step 3 的综合判断逻辑——不再是简单的「两个工具合在一起跑」,而是「分析两者的交叉信号后给出一个整合判断」。
这是从旧版到当前架构阶段最容易被忽视但最重要的改造。
旧版的问题 :飞书同步是「步骤间隙的附加动作」,用 ⛔ 标记提醒编排器「别忘了」。但 ⛔ 标记 ≠ 执行保障——AI 在长对话中照样忘。
当前解法:把 sync 提升为 Pipeline 正式步骤。
Pipeline 数组:["static", "cases", "case-quality-check", "sync-pull", "sync-push-cases", "executor-with", "grader-report", "sync-push-results", "publish"]状态机强制执行——sync 步骤必须被状态机「走过」,哪怕结果是 skipped_no_config 。
另一个大改动是 Grader 和 Report 合并为单个 subagent:
历史分离口径 :grader 评完 → 主编排器读结果 → spawn report subagent 稳定契约 : grader-report subagent 完成评审、汇总和报告生成
为什么合并?
独立的 sentry-report 仍然保留,但只用于「grading 已完成,只想重新生成报告」的场景。它不在主 pipeline 中。
当前版新增每步 token 消耗记录:
spawn 前:before_usage = session_status().usage.total_tokenssubagent 完成后:after_usage = session_status().usage.total_tokens差值写入:cost[step_name] = after_usage - before_usage写入 token 诊断结构,最终可以汇总到 diagnostics、timing 或报告产物中。
这解决了「测评到底消耗了多少 token」的透明度问题——用户可以看到每步的 token 消耗,再决定是否需要调整模式或用例数。
(以下为内部版本演进记录,正文中统一用"旧版/当前版"描述)
三条架构教训(详见文章前半部分): 文件状态 > 上下文记忆 、 强制路径 > 提醒标记 、 降级 > 终止 。
这三条教训在 SkillSentry 的每一个具体设计改动中都有体现:Pipeline 数组解决跳步(强制路径)、session.json 解决记忆丢失(文件状态)、三级降级解决超时(降级优先终止)。
Q:为什么这篇还要单独讲旧版状态机阶段?
旧版状态机阶段解决"流程会不会被漏掉",当前版解决"工具和文章是否说同一套契约"。前者是架构基础,后者是口径收敛,不是替代关系——详见文章正文。
Q:这里的 Pipeline 状态机和传统 DAG 调度器(如 Airflow)有什么本质区别?
关键差异一句话:Airflow 节点是确定性函数,SkillSentry 节点是 AI subagent——输出非确定性,因此必须设计三级降级和产物完整性验收。这是传统调度器框架不内置的。
Q:为什么 sync 步骤要走 Pipeline,而不是主编排器顺手执行?
核心教训:任何"顺手附加"的动作在长对话中都会被遗忘。放进 Pipeline = 状态机强制经过。详见正文"Sync 步骤纳入 Pipeline"一节。
Q:active-pipeline.json 的 watchdog 多久检查一次?会不会有性能问题?
每 10 分钟一次心跳。检查动作只是读一个 JSON 文件(< 1KB),如果文件不存在(无活跃 Pipeline)直接跳过。性能影响可忽略。
Q:这套方案在 Claude Code CLI 模式下完全适用吗?有哪些差异?
基本适用。主要差异在于 CLI 模式下没有 websocket 长连接维持主会话,session.json 的持久化尤为重要——每次重新启动 CLI 进程时,主编排器必须先读 session.json 恢复状态,而不能依赖对话历史。active-pipeline.json 的 watchdog 在 CLI 模式下同样有效,因为它基于文件而非对话上下文。
状态机解决的是"流程不分叉"。如果继续追问长任务中断后怎么恢复,就会进入另一个工程问题:如何用 active-pipeline.json 记录当前步骤、前置产物和恢复策略,避免 executor 已经跑完却因为对话中断而全量重跑。
《Token 计量与效率优化:每次测评消耗了多少 token(20)》《MD5 缓存让测评系统学会了推断,而不是询问(19)》《sentry-static测评入口收敛:从多能力检查到1个静态分析入口(18)》《grader-report:为什么AI测评里的评分和报告应该是同一个subagent(17)》《SkillSentry 提速实战:三个改动让 AI 测评少调 70% 的 LLM(16)》《从离线门禁到线上回流:Skill 上线后的质量怎么守(15)》


链接:https://juejin.cn/post/7655148316922986531
本文为51Testing经授权转载,转载文章所包含的文字来源于作者。如因内容或版权等问题,请联系51Testing进行删除


