为什么状态管理是核心问题
一个 Agent Workflow 运行到第 5 个 Phase 时崩溃了。重启之后,它从头开始还是从第 6 个 Phase 继续?
没有状态持久化,只能从头开始:之前的 LLM 调用、工具执行、人工确认全部作废。
状态管理的作用:工作流在任意时刻中断,从最近的检查点精确续接。这也是人工确认门的基础,门被触发意味着工作流暂停在某个确定状态,人工响应后从该状态继续。
Durable Execution 模式
将执行过程序列化为可恢复的检查点,任何时候中断,从最近检查点重新执行,结果与不中断一致。Temporal.io 是这个模式的代码层典型实现,但用 JSON 文件同样可以实现相同的语义。
状态文件结构
json
{
"workflow_id": "wf-bug-e2e-AE-33995-20260601",
"workflow_version": "1.3.0",
"jira_key": "AE-33995",
"started_at": "2026-06-01T10:00:00+08:00",
"phase": "phase_4",
"phases": {
"phase_1": {
"status": "done",
"completed_at": "2026-06-01T10:02:30+08:00",
"output_file": "bug_info.json"
},
"phase_2": {
"status": "done",
"completed_at": "2026-06-01T10:05:00+08:00",
"output_file": "log_analysis.json"
},
"phase_3": {
"status": "done",
"completed_at": "2026-06-01T10:18:00+08:00",
"output_file": "analysis_final.json"
},
"phase_4": {
"status": "in_progress",
"step": "step_4_1",
"steps": {
"step_4_1": {"status": "done", "output_file": "candidate_a.json"},
"step_4_2": {"status": "in_progress"},
"step_4_3": {"status": "pending"}
}
}
}
}
续接协议
python
def resume_workflow(state_file: Path) -> None:
state = json.loads(state_file.read_text())
for phase_id, phase_data in state["phases"].items():
if phase_data["status"] == "done":
continue # 跳过已完成的 Phase
if phase_data["status"] == "in_progress":
# in_progress 视同 pending 重新执行(幂等性保证安全)
execute_phase(phase_id, state)
return
if phase_data["status"] == "pending":
execute_phase(phase_id, state)
return
关键设计:续接时只信状态文件,不信记忆 。主 Agent 不记得自己做过什么,只读取状态文件里的 status 字段。in_progress 的 Phase 会被重新执行,这要求每个 Phase 的操作是幂等的。
两端写入
状态文件要在 Phase 开始前 和完成后都写入,不能只写完成时:
python
def execute_phase(phase_id: str, state: dict) -> None:
# 开始前:标记 in_progress(即使崩溃,续接时也能找到这个 Phase)
state["phases"][phase_id]["status"] = "in_progress"
write_state(state)
try:
result = run_phase_logic(phase_id, state)
# 完成后:标记 done,记录输出文件路径
state["phases"][phase_id]["status"] = "done"
state["phases"][phase_id]["output_file"] = result.output_file
write_state(state)
except Exception as e:
state["phases"][phase_id]["status"] = "failed"
state["phases"][phase_id]["error"] = str(e)
write_state(state)
raise
幂等性设计
续接协议里说"in_progress 视同 pending 重新执行",意味着一个 Phase 可能被执行两次。如果 Phase 里的操作不幂等,就会产生重复副作用:两条 Jira 评论、两个 git commit、两封通知邮件。
各类操作的幂等性分析
文件写入(天然幂等)
python
# 覆盖写是幂等的------多次执行结果相同
output_file.write_text(json.dumps(result)) # ✅
Jira 评论(不幂等,需要检测)
python
# ❌ 错误:直接写评论,重复执行会写两条
jira.add_comment(issue_key, comment_text)
# ✅ 正确:写前先检查是否已有本次处理的评论
def add_comment_idempotent(issue_key: str, comment_text: str, run_id: str) -> None:
existing = jira.get_comments(issue_key)
marker = f"[run_id:{run_id}]" # 每次工作流运行用唯一 ID 标记
if any(marker in c.body for c in existing):
return # 已写过,跳过
jira.add_comment(issue_key, f"{marker}\n{comment_text}")
Git commit(不幂等,需要检测)
python
# ❌ 错误:直接提交,重复执行会创建两个 commit
git.commit(message)
# ✅ 正确:提交前检查 commit result 文件是否已存在且 passed=true
def commit_idempotent(message: str, output_file: Path) -> dict:
if output_file.exists():
result = json.loads(output_file.read_text())
if result.get("passed"):
return result # 已提交成功,直接返回之前的结果
# 执行提交
commit_sha = git.commit(message)
result = {"passed": True, "sha": commit_sha}
output_file.write_text(json.dumps(result))
return result
外部 API 触发(条件幂等)
python
# Gerrit 添加 Reviewer:重复添加不报错,天然幂等 ✅
gerrit.add_reviewer(change_id, reviewer)
# 创建定时任务:重复创建会出现重复任务 ❌
# → 修复:创建前先 list,检查是否已存在
def create_cron_idempotent(job_config: dict) -> None:
existing_jobs = cron.list_jobs()
if any(j["name"] == job_config["name"] for j in existing_jobs):
return # 已存在,跳过
cron.create_job(job_config)
幂等性自检表
每个新增的 Step,在实现前回答这三个问题:
vbnet
□ 这个 Step 如果执行两次,会产生副作用吗?
□ 如果会,如何检测"已执行过"并跳过?
□ 检测逻辑本身是否也是幂等的?
最后一个问题容易被忽略。检测逻辑如果依赖内存状态或有副作用,在续接场景下同样会失效。
状态文件版本绑定
工作流运行到一半时,如果修改了工作流定义(比如新增了一个 Step),续接时会出现不一致:旧的状态文件没有新 Step 的记录,主 Agent 不知道该如何处理。
在状态文件里绑定工作流版本,续接时校验版本一致性。
python
def start_or_resume(state_file: Path, current_version: str) -> dict:
if state_file.exists():
state = json.loads(state_file.read_text())
saved_version = state.get("workflow_version")
if saved_version != current_version:
# 版本不一致,告知用户选择
raise WorkflowVersionMismatch(
f"State file version: {saved_version}\n"
f"Current workflow version: {current_version}\n"
f"Options:\n"
f" 1. Continue with saved state using old workflow ({saved_version})\n"
f" 2. Start fresh with new workflow ({current_version})\n"
f" 3. Manually migrate state file"
)
return state # 版本一致,正常续接
# 新建状态文件
state = {
"workflow_version": current_version,
"started_at": datetime.now(timezone.utc).isoformat(),
"phases": {}
}
write_state(state, state_file)
return state
版本号规则(MAJOR.MINOR.PATCH)
markdown
MAJOR:Phase 结构变化(新增/删除 Phase,路由逻辑重大变更)
→ 对正在运行的工作流有断裂风险,必须处理版本不一致
→ 一般不可直接续接,需要人工决策
MINOR:新增 Step、模板优化、新增确认门选项
→ 向后兼容,正在运行的工作流可以继续用旧版本完成
→ 新触发的工作流用新版本
PATCH:措辞优化、配置调整、不影响行为
→ 安全更新,旧状态文件可以无感升级
设计 Checklist
状态持久化
- 每个 Phase/Step 在开始前写入
in_progress,完成后写入done - 续接协议只读状态文件,不依赖主 Agent 的对话历史
- 状态文件包含
workflow_version字段
幂等性
- 所有外部写操作(Jira 评论、git commit、API 调用)都经过幂等性检查
- 幂等性检测用唯一标识符(run_id 或 output_file 存在性)
- 检测逻辑本身不产生副作用
版本绑定
- 续接时校验状态文件版本与当前工作流版本是否一致
- MAJOR 版本变更有明确的续接策略
- 版本不一致时给用户提供可操作的选项,不是直接报错退出
总结
- Durable Execution 的关键是两端写入 :Phase 开始前写
in_progress,完成后写done,崩溃在任何位置都能精确续接 - 续接要求幂等性 :
in_progress会被重新执行意味着每个外部写操作都要能安全执行两次,文件写入天然幂等,Jira 评论和 git commit 需要显式检测 - 版本绑定防止静默错误:工作流修改后,旧状态文件和新工作流版本不匹配时应该告知用户,而不是用新逻辑处理旧状态,产生难以排查的错误
欢迎访问 PrimeSkills ------ 一个精心策划的 AI Agent 与技能市场,所有内容均经过真实企业级工作流验证。没有噱头,只有真正有效的东西。
更多实用知识和有趣产品,欢迎访问我的个人主页