面向 Agent 的轻量 Workflow 设计
在 Agent Loop 中嵌入确定性执行层 里记录的 BOM 工作流引擎,状态机核心六百多行、SQLite 存储六百多行,DAG 步骤 YAML 声明,步骤间靠持久化 Artifact 传递。那套设计对光缆 BOM 是合理的——那个领域恰好极端规则驱动,材料匹配、用量校核背后都有确定性算法——但它不可推广:换成知识入库这类判断密集的任务,DAG 里那些确定性步骤根本写不出来,YAML 状态机是在用代码模拟本该由模型做的判断。
这篇文章聊更轻的一档:当推进权可以交给模型时,工作流该怎么设计。结论先放在这里:轻量工作流不是”没有工作流”,而是把显式流程换成了 spec 加验收——流程交给模型的常识,“完成”被 spec 固定住。它跟最近常说的 spec engineering 是同一个东西的两面,这个联系在第 2 节展开。
先把线划清楚:这不是 Temporal、Airflow 或 Dify 那类生产级工作流平台,执行器就是 Agent 会话本身,状态就是任务目录里的几个文件。
1. 三档工作流
两个刻度
Building Effective Agents 里 Workflow 和 Agent 的区分(预定义代码路径编排 vs 模型动态主导)早就不是新鲜事了,真正要决策的是推进权给谁——从一步走到下一步由谁决定。用两个刻度可以把工作流分成三档:流程是谁定义的(不定义 / 模型 / 人),以及步骤与步骤之间模型是否在场。
| 档位 | 流程定义者 | 步骤间模型是否在场 | 形态 |
|---|---|---|---|
| Spec-gated(轻) | 不定义显式流程,spec 即终点 | 在,每步之间都重新决策 | spec + state 文件 + check 命令 |
| Plan-checked(中) | 模型运行时生成 Plan | 不在,plan 区间内代码执行 | 动态 Plan + Plan Executor |
| Rule-driven(重) | 人设计期写好的 DAG | 大部分节点不在 | 状态机引擎 + Artifact 传递 |
轻档:Spec-gated
没有显式流程。任务的终点和检查点写在 spec 里(验收标准 A1…An),模型自主决定先做什么后做什么,每步之间都重新读状态、重新决策,验收由 check 命令机械判定。这是这篇文章的主角,第 3 节讲具体设计。
中档:Plan-checked
模型在运行时发现”接下来这一段路径是固定的”,于是自己生成一份 Plan 交给 Plan Executor 执行。nonoka 框架的 execute_plan 就是这个形态:步骤是参数填好的工具调用,步骤间用 {"$ref": "step_id.path"} 传数据,提交后纯代码按依赖序执行,模型在 plan 区间内完全不在场,拿到结构化的 completed_steps / failed_steps 结果报告后才重新介入。
回头复盘,这个设计作为通用工具是鸡肋的:它唯一的价值是省掉每步一次 LLM 往返,但”模型能提前把全部参数填好”这个前提在开放任务里很少成立——真到步骤机械得不需判断的程度,直接顺序调工具即可;步骤需要判断时,plan 有语义错误执行器又无法自愈。它真正的问题是把执行权也从模型手里拿走了,而机械执行恰恰是它唯一擅长的窄场景。合理的中间形态应该是:推进权给代码(按 plan 逐步发放步骤、校验 IO、标记完成),执行权还给模型(每步内反思怎么做),机械步骤再单独标 direct 走代码执行——这样 Plan 就变成模型生成的、带 IO 契约的节点图,由薄 runtime 调度。这也是”图由模型生成”的起点。
重档:Rule-driven
流程由人在设计期写成持久化的 DAG,代码状态机持有推进权,模型只在声明的节点(比如 skill: 步骤)介入写结构化输出,其余步骤由确定性逻辑直接执行,节点间靠持久化 Artifact 传递。BOM 引擎就是这个形态。它是”轻档不够用”时该长成的样子,而不是默认答案——第 5 节讲什么时候该往这档走。
三条底线
无论哪档,有三条底线不变:
- 验收权在代码层。“是否完成”由命令退出码、schema 校验、独立 verifier 判定,模型输出”已完成”三个字不算数;
- 状态在模型上下文之外。任务现场可持久化、可恢复,模型失忆、会话中断都不是致命伤;
- 完成标准前置。做什么、怎么算完,执行前定义成 spec,而不是做完之后补写。
Anthropic 有句话说得很准:harness 的每个组件都编码了一个”模型做不到这件事”的假设,模型在持续变强,这些假设会过期——组件应该退化,而不是固化成架构。三档的迁移,本质上就是这些假设一条条放宽的过程。
2. 轻量工作流与 spec engineering
我原来对 spec engineering 的理解就是”写一份 spec 文档”,读完 Comet 和 Trellis 的相关实践才发现完全不是:spec 文档只是一环,它实际是一套循环系统——spec 定义完成标准,状态文件记录进度,runtime 真实执行 check 做验收,archive 和 journal 把经验沉淀回 spec,spec 自身动态演进、走 PR review。
对照轻量工作流的组件,几乎是逐一对应的:
| Spec engineering 概念 | 轻量工作流组件 |
|---|---|
| Spec(完成标准) | spec.md + checks/ |
| Task / 任务现场 | .workflow/<task-id>/ 任务目录 |
| Workflow State(进度) | state.yaml |
| Verification(独立验收) | check 命令 + 只读 verifier + 有界 repair |
| Journal / 复盘沉淀 | archive 目录 + 经验回写 |
所以两者的关系可以精确地表述为:轻量工作流是 spec engineering 思想在 Agent 运行时上的落地形态。spec 回答”做什么、怎么算完成”,模型自主推进回答”过程怎么走”,runtime 验收回答”做完了没有”。模型够强时 spec 本身就替代了流程定义——验收标准 A1…An 定义了终点和检查点,顺序交给模型的常识去排,不需要 DAG 来表达”先做什么后做什么”。
公开的参照实现有两个:Comet 的 native 模式(shape 定验收标准 → build 模型自主实现 → verify 由 runtime 真实执行 check、独立只读 verifier 重验 → archive 合并 spec),和 Trellis 的 spec coding(spec 层、任务层、工作区层三分,workflow-state 每轮注入,复盘沉淀回 spec)——一个在任务运行时侧,一个在项目规范侧,哲学是同一个。
3. 轻量 Workflow Package 的设计
一个 skill 附带一个 workflow package:SKILL.md 是行为契约(这类活怎么干),任务目录是任务现场(这次活干得怎样)。一个 skill 可以同时开多个任务目录,互不干扰。
目录结构
.workflow/<task-id>/
├── brief.md # 需求澄清的产物:这次要干什么
├── spec.md # 验收标准 A1…An:怎么算完(执行前定义)
├── state.yaml # 运行事实:phase、iteration、验收项判定、blockers
├── artifacts/ # 步骤间传递的产物,文件即 artifact
└── checks/ # 可执行的验收命令(脚本或命令清单)
spec.md:完成标准前置
# Knowledge 入库验收标准
- A1: 文档分类结果写入 artifacts/classification.json,
每条含 {doc_id, category, confidence},符合 classification.schema.json
- A2: 提取条目数 >= 原始文档段落数 × 80%,
写入 artifacts/extracted.jsonl,符合 extracted.schema.json
- A3: 已归档文件的 git commit hash 记录在 state.yaml 的 archive 字段
验收标准要能被机械检查。“结构正确、内容合理”没法验,“JSON 里有这些字段、数量达到这个阈值、git 有这条提交”可以。
state.yaml:运行事实
task_id: kb-2026-09-27-001
phase: extract # clarify / extract / verify / archive
iteration: 2
checks:
A1: {status: pending}
A2: {status: failed, last_error: "extracted 114 < required 128", attempts: 1}
A3: {status: pending}
blockers: []
next: 修复 extraction prompt 后重跑 A2
字段只回答两个问题:现在走到哪,什么算过了。
checks/:可执行的验收
#!/usr/bin/env bash
# checks/A2.sh —— 退出码即判定
set -euo pipefail
python -m kb.check_extracted \
--input artifacts/extracted.jsonl \
--schema extracted.schema.json \
--min-ratio 0.8
runtime 真实执行 check 脚本,退出码为准。模型报告”已通过”不能覆盖非零退出。
一次任务的闭环
clarify → 写 brief.md + spec.md
↓
build → 模型自主干活,产物落 artifacts/,每步更新 state.yaml
↓
verify → 顺序跑 checks/,全部通过进 archive;
失败则把失败项结构化回传,进入 repair(有上限)
↓
archive → 整个任务目录移走归档
4. 工程决策
YAML 而不是 JSON,更不是 SQLite
JSON 严格无歧义,但每次读写都是噪音,人没法随手改,diff 也不好看。YAML 人可读、可手改、diff 友好,模型读起来也快,还能带注释标注字段语义——对”人和模型都是主要读者”的文件,这个优势是决定性的。
SQLite 是另一个量级的问题。它的价值在并发写、多表关联、跨会话审计,而轻档的任务是单 Agent 串行推进的,连文件锁都用不上。更根本的区别是读写者不同:轻档的状态文件是给人和模型读的,重档的状态库是给代码查的。
按任务分目录,不搞集中式 state
集中式 state(一个文件管所有进行中的任务)在任务多了之后一定会长出”任务分区、索引、清理策略”这些机制——它们都是数据库的雏形,而我已经决定不用数据库了。任务目录则是文件系统免费给的隔离:artifact 和 state 同目录,恢复现场只需读一个目录,归档就是移动目录。
state 只存事实,不存过程
模型的思考过程、和用户的讨论不进 state——要么进 journal,要么直接丢弃。state 当聊天日志是最常见的死法:文件迅速膨胀,而每次注入都在付上下文的税。这和上下文工程里”把工作流状态搬出上下文窗口、按需取回”是同一个原则。
验收协议三要素
- check 命令前置可执行,runtime 真实跑,退出码为准;
- 独立验收:最低成本是开一个只读子会话重验全部验收项,重点不在并行而在隔离——让验收发生在没有被”我已经做完了”污染过的上下文里;实在做不到独立执行的平台,至少应明确记录”语义验收已降级”并等待人工确认,而不是让同一段上下文自己给自己盖章;
- 有界 repair loop:验收不过,把失败项结构化回传给模型修复,重试设上限(Comet 默认
max_verify_failures = 5,我觉得合理)。无界的 build/verify 循环是 Agent 系统最经典的死法——烧掉几千 token 之后模型开始对着空气道歉。
Artifact 就是文件
步骤之间传东西 = 上一步写文件,下一步读路径,契约靠 schema 校验兜底。轻档不做 artifact 表、版本号、checksum——那些是重档为幂等和溯源付的税。这个决策背后是个一般性原则:步骤间传递的媒介决定架构的重量。文件是最松的耦合,数据库表是最紧的,中间隔着一长串可以按需添加的机制。从文件开始,需要的再加。
推进没有协议,模型每轮读 state
BOM 引擎里有一套结构化的 next_actions 协议:引擎计算”下一步谁该做什么”,模型只有可见性、没有推进权。轻档这套整个不要——没有引擎、没有 next_actions,模型每轮自己读 state.yaml,靠 SKILL.md 的流程说明和 spec.md 的验收标准决定下一步。Trellis 用 hook 把 workflow-state 注入每轮上下文,轻档连注入都可以省:文件就在那里,模型自己会读。
代价是推进的正确性从”代码保证”降级成”模型大概率做对”。这是刻意的取舍,选轻档选的就是相信模型排得动顺序。
5. 什么时候该变重
轻档是默认值,不是信仰。出现下面这些信号,说明该为具体步骤下沉到更重的机制——注意是局部下沉,不是整个推翻重来:
- 需要跨会话断点续跑,且状态复杂到读文件重建现场已经很吃力;
- 并行任务多到文件系统管不过来,开始出现冲突和孤儿目录;
- 步骤间产物需要版本化、审计、追溯(合规驱动);
- 某一步真的有确定性算法。这是最关键的信号:不是”流程固定”就值得写引擎,而是”这一步存在不依赖模型判断的算法”——BOM 的用量校核就是这种,为它单独写一个确定性模块、在 DAG 里作为确定性节点存在,是合理的。
真到了需要显式图的程度,通用组件的形态是类型化 DAG runner:YAML 声明节点和依赖,节点带类型(LLM / Agent / tool / code),节点间用 IO 契约传数据,一个薄执行循环按依赖序调度。它重不过是因为产品化(可视化编辑器、队列、分布式、多租户),这个内核本身是几百行级别的事。BOM 引擎可以看作它的一个”加满可靠性”的特化实例——两者共享同一套验收和状态哲学,区别只在推进权在谁手里。
反过来,下面这些不是变重的理由:任务重要(重要的任务更需要验收前置,不是更需要引擎)、流程步骤多(步骤多不等于步骤有确定性规则)、怕模型跑飞(那是验收和 repair 上限的事)。
6. 小结
三档工作流的分界线是推进权,不动的底线是验收权、状态外置、完成标准前置。模型在变强,光谱整体向轻的方向移动,今天需要 Plan-checked 的任务明天可能 spec-gated 就够——BOM 引擎那种重设计,应该是”某一步出现了确定性算法”之后的产物,而不是第一天就躺在架构图里。
接下来打算拿 knowledge 入库这个 skill 当试金石:它没有任何一步有确定性算法,挂上这套 package 跑一段时间,看轻档在哪漏水。漏水的点如果恰好是第 5 节列的某条信号,再局部变重。