你的 Agent 能干活,但它能可靠地干活吗?当工作流从 3 步扩展到 15 步,从 demo 走进生产,"Prompt 里写了规则"就不再够用。本文拆解 Agent 工作流引擎的核心设计模式、生产陷阱与选型决策框架。
一个真实的生产事故
凌晨 2 点,客服 Agent 在处理退款争议时崩溃了。
不是因为模型出了问题,而是因为它在第 7 步------调用支付系统 API 确认退款金额------收到了一个网络超时。没有断点续跑机制,整个工作流从头开始。但"从头开始"意味着它重新发起了一次退款请求。客户收到了两笔退款。
这不是假设场景。2026 年 Q3,我们团队在处理的一个 Agent 系统就踩了这个坑。根因很简单:工作流的状态只活在内存里,进程挂了,状态就没了。
这个问题在 Agent 工程社区里有一个正式的名字:Durable Execution 缺失。而围绕它的更大问题是:Agent 的工作流到底应该怎么管理?
Prompt 清单的天花板
大多数团队的 Agent 工作流管理方式是这样的:
text
# Agent 工作流规则(写在 System Prompt 里)
1. 先分析用户需求,生成分析计划
2. 按计划执行每一步
3. 每步执行后验证结果
4. 如果验证失败,重试最多 3 次
5. 所有步骤完成后生成总结报告
6. 遇到敏感操作(删除、支付、发邮件),等待人工确认
这种方式能跑,但它有三个致命问题:
第一,Agent 会"忘记"规则。 当 context window 被工具调用的输出塞满,System Prompt 里的规则会被注意力机制"稀释"。我们实测过一个 7 步工作流:在步骤 5 之后,Agent 跳过验证步骤直接给出结论的概率从 2% 上升到 34%。
第二,没有强制的状态转移。 Prompt 说"先分析再执行",但 Agent 完全可以跳过分析直接动手------它只是在文本层面"同意"了规则,运行时没有任何东西阻止它偏离流程。
第三,失败路径不可控。 "重试 3 次"写在 Prompt 里,但重试的计数器也在 Prompt 管理的 context 里。Agent 可能记错重试次数,或者把不同任务的重试次数混在一起。
这不是 Prompt 的错。Prompt 擅长描述意图,不擅长强制执行流程。
状态机:把工作流从"建议"变成"法律"
2026 年 Agent 工程社区的一个明确趋势是:用有限状态机(FSM)来管理 Agent 的工作流。
核心思路很直接:把工作流的每个阶段定义为一个状态,状态之间的转移需要满足特定条件(gate),每个状态产出必须附带可验证的证据(evidence)。
一个典型的 Agent 工作流状态机:
ruby
┌─────────┐ task contract ┌──────────┐ scoped plan ┌─────────────┐
│ INTAKE │ ─────────────────→ │ PLAN │ ────────────────→ │ IMPLEMENT │
└─────────┘ └──────────┘ └──────┬──────┘
↑ ↑ revise │
│ reject │ ▼
│ │ ┌─────────────┐
│ └─────────────────────────── │ VERIFY │
│ └──────┬──────┘
│ │
│ ┌──────────┐ retry count < 3 ┌─────────────┐ │ fail
└─── │ END │ ←──────────────────── │ REPAIR │ ←─────────┘
└──────────┘ └──────┬──────┘
↑ │ retry count ≥ 3
│ evidence attached │
└────────────────────────────────────┘ escalate
每个状态的定义包括三个要素:
- 允许的动作(allowed actions):在这个状态下,Agent 被允许做什么
- 转移条件(transition gate):离开这个状态需要提交什么证据
- 失败路径(failure routing):如果当前步骤失败,工作流走向哪里
用 TypeScript 来表达:
typescript
interface WorkflowState {
name: string;
allowedActions: string[];
transitionGate: (evidence: Evidence) => GateResult;
failureRoute: string; // 失败时转移到的状态
maxRetries?: number;
}
interface Evidence {
type: 'plan' | 'code_diff' | 'test_result' | 'approval' | 'report';
payload: unknown;
timestamp: number;
checksum: string;
}
// 状态机定义
const refundWorkflow: WorkflowState[] = [
{
name: 'intake',
allowedActions: ['analyze_request', 'request_clarification', 'reject'],
transitionGate: (evidence) => {
if (evidence.type !== 'plan') return { pass: false, reason: '需要任务合同' };
const plan = evidence.payload as TaskContract;
if (!plan.userId || !plan.orderId || !plan.reason) {
return { pass: false, reason: '任务合同缺少必填字段' };
}
return { pass: true };
},
failureRoute: 'reject',
},
{
name: 'verify_refund',
allowedActions: ['check_transaction', 'check_policy', 'calculate_amount'],
transitionGate: (evidence) => {
if (evidence.type !== 'test_result') return { pass: false, reason: '需要验证结果' };
const result = evidence.payload as VerificationResult;
if (!result.transactionFound) return { pass: false, reason: '交易记录未找到' };
if (!result.policyMatch) return { pass: false, reason: '不符合退款政策' };
return { pass: true };
},
failureRoute: 'repair',
maxRetries: 2,
},
{
name: 'execute_refund',
allowedActions: ['call_payment_api'],
transitionGate: (evidence) => {
if (evidence.type !== 'test_result') return { pass: false, reason: '需要执行确认' };
const result = evidence.payload as RefundResult;
if (!result.refundId) return { pass: false, reason: '退款未成功创建' };
return { pass: true };
},
failureRoute: 'repair',
maxRetries: 1, // 支付操作只允许重试 1 次
},
];
这段代码的关键不在于具体的字段设计,而在于它把"Agent 应该怎么做"从自然语言描述变成了可执行的约束。Agent 不能在 intake 状态下调用支付 API,因为 call_payment_api 不在它的 allowedActions 里。它不能跳过 verify_refund 直接进入 execute_refund,因为状态机没有定义这条转移路径。
LangGraph vs Temporal vs 自建 FSM:选型决策框架
2026 年主流的 Agent 工作流方案大致分三类:
1. LangGraph:图即工作流
LangGraph 把工作流表达为有向图,每个节点是一个处理步骤,边定义了条件转移。它跟主流 Agent 框架集成最紧密。
python
from langgraph.graph import StateGraph, START, END
def analyze(state: AgentState) -> dict:
plan = llm.invoke("分析用户需求:" + state["input"])
return {"plan": plan, "messages": [plan]}
def execute(state: AgentState) -> dict:
result = execute_plan(state["plan"])
return {"result": result}
def should_repair(state: AgentState) -> str:
if state.get("verify_passed"):
return "report"
if state.get("retry_count", 0) >= 3:
return "escalate"
return "repair"
graph = StateGraph(AgentState)
graph.add_node("analyze", analyze)
graph.add_node("execute", execute)
graph.add_node("verify", verify)
graph.add_node("repair", repair)
graph.add_node("report", report)
graph.add_edge(START, "analyze")
graph.add_edge("analyze", "execute")
graph.add_edge("execute", "verify")
graph.add_conditional_edges("verify", should_repair)
graph.add_edge("repair", "verify")
graph.add_edge("report", END)
app = graph.compile(checkpointer=MemorySaver())
优势: 可视化直观,checkpoint 机制内置。 劣势: 状态持久化依赖外部 checkpointer,生产级 durability 需要额外集成。图的条件边越多,调试越困难。
2. Temporal / Restate:代码即工作流
Temporal 和 Restate 这类 code-first 工作流引擎,让你在普通代码里用 await 写工作流,引擎自动处理状态持久化和断点续跑。
python
from temporalio import workflow, activity
@activity.defn
async def call_payment_api(refund_request: RefundRequest) -> RefundResult:
return await payment_client.refund(refund_request)
@workflow.defn
class RefundWorkflow:
@workflow.run
async def run(self, request: RefundRequest) -> str:
# 步骤 1:分析请求(持久化)
analysis = await workflow.execute_activity(
analyze_request, request,
start_to_close_timeout=timedelta(seconds=30)
)
# 步骤 2:验证(持久化)
verification = await workflow.execute_activity(
verify_refund, analysis,
start_to_close_timeout=timedelta(seconds=30)
)
if not verification.approved:
return "rejected"
# 步骤 3:执行退款(持久化,幂等)
result = await workflow.execute_activity(
call_payment_api, request,
start_to_close_timeout=timedelta(seconds=60),
retry_policy=RetryPolicy(maximum_attempts=2)
)
return result.refund_id
优势: 代码即工作流,没有 DSL 学习曲线;内置状态持久化、断点续跑、超时管理;适合长时任务(小时级甚至天级)。 劣势: 需要部署 Server(运维成本);与 Agent 框架的集成需要额外适配层。
3. 自建 FSM + Checkpoint:轻量但够用
对于中小规模的 Agent 工作流,一个自建的状态机加上 checkpoint 存储就够用了。
typescript
class WorkflowEngine {
private state: WorkflowState;
private context: Record<string, unknown>;
private checkpointStore: CheckpointStore;
private retryCounters: Map<string, number>;
async start(workflowId: string, input: unknown) {
const saved = await this.checkpointStore.load(workflowId);
if (saved) {
this.state = saved.state;
this.context = saved.context;
this.retryCounters = saved.retryCounters;
} else {
this.state = this.states[0];
this.context = { input };
this.retryCounters = new Map();
}
while (this.state.name !== 'end') {
try {
const evidence = await this.executeState(this.state, this.context);
const gateResult = this.state.transitionGate(evidence);
if (gateResult.pass) {
this.context = { ...this.context, ...evidence.payload };
this.state = this.getNextState(this.state, 'success');
} else {
const retries = this.retryCounters.get(this.state.name) || 0;
const maxRetries = this.state.maxRetries || 3;
if (retries < maxRetries) {
this.retryCounters.set(this.state.name, retries + 1);
} else {
this.state = this.getNextState(this.state, 'failure');
}
}
await this.checkpointStore.save(workflowId, {
state: this.state,
context: this.context,
retryCounters: this.retryCounters,
timestamp: Date.now(),
});
} catch (error) {
console.error(`Workflow ${workflowId} failed:`, error);
this.state = this.getNextState(this.state, 'failure');
}
}
return this.context;
}
}
优势: 完全可控,没有外部依赖,容易跟现有系统集成。 劣势: 需要自己处理并发、分布式锁、超时等底层问题。
选型对照表
| 维度 | LangGraph | Temporal/Restate | 自建 FSM |
|---|---|---|---|
| 学习曲线 | 低(图即流程) | 中(SDK 概念) | 低(代码即流程) |
| 状态持久化 | 需外接 checkpointer | 内置 | 需自建 |
| 断点续跑 | 通过 checkpoint | 原生支持 | 需自建 |
| 可视化 | 内置 | Web UI | 无(需自建) |
| Agent 框架集成 | 主流生态原生 | 需适配层 | 完全自定义 |
| 适合场景 | 中等复杂度 | 长时任务,高可靠性 | 简单流程,快速迭代 |
| 运维成本 | 低 | 中-高 | 低 |
生产环境的 5 个工程陷阱
陷阱 1:Checkpoint 粒度不对
最常见的错误是只在状态转移时 checkpoint。如果某个步骤执行了 5 分钟,在第 4 分钟崩溃了,那 4 分钟的工作就白做了。
正确做法是区分 粗粒度 checkpoint (状态转移时保存)和 细粒度 checkpoint(关键操作前后保存):
typescript
async function executeWithFineCheckpoint(state: WorkflowState, context: Context) {
await checkpoint('state_enter', { state: state.name, context });
for (const action of state.allowedActions) {
const result = await executeAction(action, context);
await checkpoint('action_complete', {
state: state.name,
action,
result,
context: mergeContext(context, result),
});
}
return context;
}
一个真实数据:我们把 checkpoint 粒度从"每状态"改为"每操作"后,工作流的平均恢复时间从 3.2 分钟降到 12 秒。
陷阱 2:状态爆炸
当工作流的条件分支超过 8 个时,状态数量会以指数级增长。一个原本 6 步的线性流程,加上各种异常路径后,可能膨胀到 40+ 个状态。
解法是 分层状态机:把复杂工作流拆成子工作流,每个子工作流有自己的状态机,父工作流只管理子工作流之间的转移。
typescript
const parentWorkflow: WorkflowState[] = [
{ name: 'triage', failureRoute: 'escalate' },
{ name: 'run_verification_subflow', failureRoute: 'manual_review' },
{ name: 'run_refund_subflow', failureRoute: 'manual_review' },
{ name: 'notify_customer', failureRoute: 'log_and_continue' },
{ name: 'end' },
];
const verificationSubflow: WorkflowState[] = [
{ name: 'check_transaction', failureRoute: 'repair' },
{ name: 'check_policy', failureRoute: 'repair' },
{ name: 'calculate_amount', failureRoute: 'repair' },
{ name: 'end' },
];
陷阱 3:证据验证变成了形式主义
状态机要求每个状态转移时提交证据,但如果证据验证太松,Agent 就会学会"应付检查"------生成一份看起来像回事但实际上没用的证据。
我们见过一个案例:Agent 在验证步骤提交了一个 test_result 类型的证据,payload 是 {"passed": true}。状态机的 gate 检查了 evidence.type === 'test_result',就放行了。但实际的测试根本没跑。
解法:证据必须包含可验证的执行痕迹。
typescript
const strictEvidenceGate: TransitionGate = (evidence) => {
if (evidence.type !== 'test_result') return { pass: false, reason: '需要测试结果' };
const result = evidence.payload as TestResult;
if (!result.commandOutput || result.commandOutput.length < 50) {
return { pass: false, reason: '命令输出太短,疑似伪造' };
}
if (!result.timestamp || result.timestamp > Date.now()) {
return { pass: false, reason: '时间戳无效' };
}
if (computeChecksum(result.commandOutput) !== evidence.checksum) {
return { pass: false, reason: '证据完整性校验失败' };
}
return { pass: true };
};
陷阱 4:重试风暴
当一个步骤失败并触发重试时,如果上游有多个实例同时重试,可能形成"重试风暴"------所有实例在同一时刻对同一个下游服务发起请求。
这在退款场景中尤其危险。解法是 指数退避 + jitter + 全局去重:
typescript
async function retryWithBackoff<T>(
fn: () => Promise<T>,
maxRetries: number,
baseDelayMs: number,
dedupKey: string
): Promise<T> {
const lock = await acquireDistributedLock(dedupKey, baseDelayMs * 2);
if (!lock.acquired) {
return await waitForResult(dedupKey);
}
for (let attempt = 0; attempt < maxRetries; attempt++) {
try {
const result = await fn();
await releaseLock(lock);
return result;
} catch (error) {
const delay = baseDelayMs * Math.pow(2, attempt) + Math.random() * 1000;
await sleep(delay);
const existingResult = await checkIdempotentResult(dedupKey);
if (existingResult) {
await releaseLock(lock);
return existingResult;
}
}
}
await releaseLock(lock);
throw new Error(`重试 ${maxRetries} 次后仍然失败`);
}
陷阱 5:工作流版本管理的缺失
这是最容易被忽视的陷阱。工作流定义修改了(比如新增了一个验证步骤),但还有 50 个正在进行中的工作流实例运行的是旧版本。新版本部署后,这些实例从 checkpoint 恢复时,发现状态机结构变了,直接崩溃。
解法是 工作流版本化 + 兼容性迁移:
typescript
interface WorkflowVersion {
version: string;
states: WorkflowState[];
migrations: Record<string, (context: unknown) => unknown>;
}
const workflowV2: WorkflowVersion = {
version: '2.0',
states: [/* 新版本的状态定义 */],
migrations: {
'1.0': (context: unknown) => {
const ctx = context as V1Context;
return {
...ctx,
risk_score: ctx.risk_score || calculateRiskScore(ctx),
compliance_check: ctx.compliance_check || 'pending',
};
},
},
};
async function resumeWorkflow(workflowId: string) {
const saved = await checkpointStore.load(workflowId);
const savedVersion = saved.version;
const currentVersion = workflowV2.version;
if (savedVersion !== currentVersion) {
const migration = workflowV2.migrations[savedVersion];
if (!migration) {
throw new Error(`无法从版本 ${savedVersion} 迁移到 ${currentVersion}`);
}
saved.context = migration(saved.context);
saved.version = currentVersion;
}
return engine.resume(saved);
}
决策框架:什么时候用状态机,什么时候不用
不是所有 Agent 任务都适合状态机管理。过度工程化同样有害。
适合状态机的场景:
- 流程步骤确定,可以提前枚举
- 步骤之间有明确的先后依赖
- 失败的代价高(支付、数据删除、外部通知)
- 需要审计日志和合规记录
- 多步骤的 CRUD 操作链
不适合状态机的场景:
- 探索性研究("帮我调研 XX 领域的最新进展")
- 开放式头脑风暴
- 调试和问题诊断(失败模式未知)
- 单步骤的简单任务
- 需要 Agent 自主决定流程走向的创新任务
一个实用的判断标准:如果你能画出这个任务的工作流图,它就适合状态机管理;如果你画不出来,就别硬画。
可观测性:工作流引擎不是黑盒
工作流引擎的每一步都应该产生可观测性数据。三个关键指标:
1. 状态转移延迟(State Transition Latency)
每个状态的平均停留时间。如果某个状态的 p99 延迟突然飙升,说明这个步骤的下游可能出了问题。
typescript
metrics.histogram('workflow.state_duration_ms', duration, {
workflow_id: workflowId,
state: state.name,
version: workflowVersion,
});
2. 转移成功率(Transition Success Rate)
每个状态的转移成功/失败比例。如果某个状态的成功率低于阈值,触发告警。
3. Checkpoint 大小增长率(Checkpoint Growth Rate)
如果 checkpoint 数据增长过快,说明 context 在膨胀,可能需要做 context 裁剪。我们见过一个案例,checkpoint 从 2KB 增长到 12MB,导致恢复时间从毫秒级变成秒级。
写在最后
Agent 工作流引擎不是一个新概念------传统工作流引擎(Airflow、Temporal、Camunda)已经存在了很多年。但 Agent 时代的工作流引擎需要解决一个传统引擎不需要面对的问题:执行者(LLM)是非确定性的。
传统工作流引擎假设每个步骤的执行结果是确定的(给定相同的输入)。Agent 不是。同一个 Prompt、同一个上下文,Agent 可能产出不同的结果。这意味着工作流引擎的 gate 验证、证据检查、重试策略都需要为这种不确定性做额外设计。
2026 年的趋势很明确:Agent 工作流正在从"Prompt 描述"走向"代码强制"。LangGraph 提供了图结构,Temporal 提供了持久化执行,新兴方案提供了可验证执行。选择哪个取决于你的场景复杂度、可靠性要求和团队技术栈。
但无论选择哪个方向,核心原则不变:流程应该在代码里,而不是在 Prompt 的想象里。
参考来源:
- Aharness (GitHub): 工作流的有限状态机实现
- LangGraph 文档:Graph-based agent orchestration
- Temporal 文档:Durable execution for long-running workflows
- Developer's Digest: "Agent Workflows as Code: Why State Machines Beat Prompt Checklists" (2026-06)
- LangChain Blog: "How to Build a Custom Agent Harness" (2026)