AIAgent长期任务执行技术全解析
AI Agent长期任务执行技术深度解析:任务分解、持久化与恢复
当前大多数Agent只能执行单轮任务或短对话。当任务执行时间从1分钟增长到1小时甚至数小时,Agent需要解决任务分解、子Agent调度、状态持久化、错误恢复、中间结果验证等一系列工程问题。本文分析这些技术的实现原理。
一、Agent执行时间的数量级划分
Agent的能力范围可以根据执行时间大致划分:
| 时间量级 | 示例任务 | 典型技术栈 | 已成熟度 |
|---|---|---|---|
| 秒级 | 问答、代码补全、简单翻译 | 单轮对话 | ✅ 成熟 |
| 分钟级 | 单文件编辑、简单接口调用 | 工具调用、ReAct循环 | ✅ 成熟 |
| 10分钟级 | 多文件重构、单元测试、代码审查 | Agent循环+多工具链 | ⚠️ 部分成熟 |
| 小时级 | PR提交、批量数据处理、完整功能开发 | 任务分解+子Agent调度 | ❌ 发展中 |
| 天级 | 代码库迁移、全量测试修复、文档体系搭建 | 多Agent协作+状态持久化 | ❌ 早期 |
10分钟以内的任务,单Agent+ReAct循环基本够用。超过1小时的任务,需要专门的长期任务执行架构。
这个时间分界由大模型的上下文窗口决定。一个Agent运行10分钟产生的中间结果和工具调用记录通常不超过8K token。运行1小时,中间结果可以达到30-50K token。运行半天,中间结果可以超过128K token。
上下文窗口的膨胀有两个问题:一是推理成本线性增长,二是长上下文中的注意力分散导致准确率下降。所以长期任务执行的关键不在于扩大上下文窗口,而在于减少每轮循环中的无效信息。
二、任务分解(Task Decomposition)
2.1 分解粒度
一个"给项目添加用户登录功能"的任务,不同分解方式:
粗粒度分解(3步): └─ 写用户模型 → 写认证逻辑 → 写前端页面 问题:每一步仍需模型自主拆解,中间步骤过多 中等粒度分解(12步): └─ 设计数据库Schema → 实现User模型和迁移 → 实现注册接口 → 实现登录接口 → 实现JWT签发和验证 → 实现密码加密 → 实现token刷新 → 实现中间件拦截 → 实现登录页面UI → 实现注册页面UI → 实现路由守卫 → 集成测试 优点:每步可独立验证,子任务间无隐藏依赖 细粒度分解(30步): └─ ...(过于细碎,调度开销大于实际运行开销)
分解粒度的经验值:每个子任务的执行时间在3-10分钟之间。小于3分钟说明分解太细,调度和上下文切换的开销超过了任务本身的开销。大于10分钟说明分解太粗,子任务内部可能包含未预期的复杂性。每步的输入和输出都应该有清晰的接口约定。
2.2 分解策略
分层分解(Top-down):先写出总体目标,再拆成3-5个大阶段,每个大阶段再拆成子任务。每个子Agent只看到自己所处的子目标,看不到全局任务。可以减少每个Agent的上下文负担,但需要保证子目标的正确性传递。
逐步分解(Bottom-up):Agent执行第一步,根据第一步的结果决定第二步做什么。这是多数ReAct循环的自带能力。优点是不需要预先制定完整计划,但问题是——如果Agent执行到第5步时发现第2步做错了,回退成本很高。
混合策略:先分层分解制定计划,每层子任务内部用逐步分解执行。实际项目中90%用这个方案。
2.3 依赖图
子任务之间不一定是串行关系。以"给项目添加用户登录功能"为例,依赖图是:
任务0:设计数据库Schema / \ 任务1:实现User模型 任务2:实现密码加密工具 \ / 任务3:实现注册接口 | 任务4:实现JWT签发工具 | 任务5:实现登录接口 / \ 任务6:中间件 任务7:登录页面UI \ / 任务8:集成测试
任务1和任务2可以并行执行。任务3依赖任务1和任务2的结果。任务6和任务7可以并行执行,但都依赖任务5。
并行子任务可以减少整体执行时间。在上面的例子中,如果所有任务串行需要8个时间单位,并行优化后只需要5个时间单位(时间单位=一个子任务的平均执行时间)。
依赖图解析本身也是一个Agent任务。将自然语言描述的需求转化为依赖图,需要模型理解任务之间的数据依赖关系。当前最好的做法是让Agent先生成依赖图JSON,再由调度器解析执行。依赖图的验证规则:如果子任务A的输入依赖子任务B的输出,则B必须在A之前完成。
三、子Agent调度(Sub-Agent Dispatch)
3.1 调度粒度
| 调度模式 | 说明 | 适用场景 | 额外开销 |
|---|---|---|---|
| 串行调度 | 一个Agent完成任务后传给下一个Agent | 线性依赖任务 | 低 |
| 并行调度 | 多个Agent同时执行独立子任务 | 并行依赖任务 | 上下文切换 |
| 异步调度 | 子Agent在后台运行,主Agent继续工作 | 长时间运行的子任务 | 状态合并 |
| 动态调度 | 根据子任务进度实时调整分配 | 不确定时长的任务 | 高 |
3.2 上下文隔离
每个子Agent不应该看到整个任务的全部上下文。上下文隔离的原则:
子Agent A(写单元测试)只需要知道:被测函数的接口签名、被测函数的输入输出规范、项目的测试框架配置。
子Agent A(写单元测试)不需要知道:数据库连接配置、其他模块的代码、部署配置。
上下文传递机制:
# 简化的上下文传递
class ContextBuilder:
def build_context(self, task_info, global_context):
"""根据子任务的要求,从全局上下文中筛选相关信息"""
needed_keys = task_info.get("context_keys", [])
filtered = {k: global_context[k] for k in needed_keys if k in global_context}
# 项目级上下文(项目结构、依赖文件、编码规范)
filtered["project_skeleton"] = self._get_project_skeleton(task_info)
# 任务级上下文(前序步骤的输出、本任务的输入接口)
filtered["task_input"] = task_info.get("input_spec")
return filtered
3.3 结果合并
并行子Agent执行完成后,需要将各自产生的结果合并到主任务中。
结果冲突:子Agent A修改了文件X的第10行,子Agent B修改了文件X的第20行。如果两个修改在同一行,需要冲突检测。文件级别的冲突可以用git merge-base检测,行级别的冲突需要更细粒度的diff检测。
2026年开源项目中的做法:每个子Agent工作在独立的git分支上,主Agent在合并时执行git merge,有冲突时由主Agent(或另启一个resolution Agent)解决。这比在同一分支上直接写入文件更安全——即使合并失败也不会污染主工作区。
四、状态持久化(State Persistence)
4.1 持久化什么
Agent长期运行中,需要持久化的状态包括:
| 状态类型 | 大小 | 保存频率 | 恢复需求 |
|---|---|---|---|
| 已完成子任务列表 | 小(几百字节) | 每步完成时 | 高 |
| 当前Agent上下文 | 中(几K-几十K) | 每N步 | 高 |
| 所有中间结果 | 大(文件系统) | 每步完成时 | 中 |
| Agent执行日志 | 大 | 持续 | 低 |
| 错误记录 | 小 | 发生错误时 | 高 |
4.2 检查点(Checkpoint)机制
检查点是在执行关键步骤时保存Agent状态的快照。检查点的触发时机:
按步保存:每完成一个子任务保存一次。优点是恢复粒度细,缺点是IO开销与子任务数量线性相关。10小时的任务如果包含200个检查点,每次保存耗时0.5秒,总开销100秒(约2.8%的额外开销)。
按时间保存:每5分钟保存一次。优点是IO开销固定,不随任务复杂增长。缺点是两次检查点之间如果Agent崩溃,最多损失5分钟的工作。
按风险保存:在"修改文件前"、"执行命令前"、"调用外部API前"等操作前保存。优点是IO开销最小化,缺点是需要Agent自己识别什么是"高风险操作"。
4.3 序列化格式
状态序列化的核心问题:大模型的上下文不是简单的文本,而是消息数组(user/assistant/tool消息交替)。每个tool调用的结果可能很大。序列化时需要做压缩:
# 序列化压缩策略
def serialize_session(messages):
# 只保留user和assistant消息,移除tool消息(结果太大)
# tool调用的结果可以用文件路径引用替代
condensed = []
for msg in messages:
if msg["role"] not in ("user", "assistant"):
continue # 跳过tool消息
if msg["role"] == "assistant" and msg.get("tool_calls"):
# 记录调用了哪些工具,但不保存结果
condensed.append({
"role": "assistant",
"tool_calls": [tc["name"] for tc in msg["tool_calls"]],
"content": msg.get("content", "")
})
else:
condensed.append({
"role": msg["role"],
"content": msg["content"]
})
return json.dumps(condensed)
这种压缩方式可以将一个运行1小时的Agent状态从30K token压缩到8K token左右。恢复时,主Agent根据压缩后的状态重建上下文,然后重新请求中间结果(如果中间结果文件还在的话)。
五、错误恢复(Error Recovery)
5.1 错误类型
Agent长期执行中可能遇到的错误:
| 错误类型 | 示例 | 发生概率(长期任务) | 恢复策略 |
|---|---|---|---|
| API错误 | 模型API返回500 | 中 | 重试(指数退避) |
| 工具调用失败 | 文件写入失败、Git冲突 | 高 | 回滚到检查点 |
| 输出格式错误 | JSON解析失败 | 中 | 重新生成 |
| 逻辑错误 | Agent产生了错误代码 | 高 | 人工验证(最复杂) |
| 上下文溢出 | 超过token限制 | 中 | 压缩上下文 |
| 模型退化 | 模型输出质量下降 | 低 | 重启会话 |
5.2 重试策略
固定重试不适用于Agent长期任务。一个子任务失败后,简单重试往往因为相同的原因再次失败。
指数退避的改进:每次重试时,不仅要等待时间加倍,还要修改重试策略。例如第一次失败是因为上下文太长,重试时压缩上下文。第二次失败因为是JSON格式错误,重试时加上format instruction。
def retry_with_strategy(task, attempt, error):
if attempt == 1:
# 第一次失败:简单重试
return execute_task(task, max_tokens=4096)
elif attempt == 2:
# 第二次失败:压缩上下文
task = compress_context(task)
return execute_task(task, max_tokens=4096)
elif attempt == 3:
# 第三次失败:改用更小的模型执行该子任务
task.model = "qwen-turbo"
return execute_task(task, max_tokens=8192)
elif attempt >= 4:
# 第四次及以上:标记为人工介入
return {"status": "skip", "reason": "failed 4 times, needs human review"}
5.3 降级策略
部分子任务失败时,整体任务不一定失败。降级策略:跳过该子任务(如果它是可选的),或使用简化版本的实现替代。
降级判断规则:
-
该子任务是否是关键路径上的?如果是,不可降级,只能重试或报错。
-
该子任务是否有默认/简化实现?如果有,使用简化实现。例如"写单元测试",如果写不出完整的测试,改为"只写核心功能的2-3个测试case"。
-
降级后的结果是否仍能满足整体目标的80%?如果能,降级是可接受的。
六、中间结果验证(Intermediate Verification)
6.1 验证层次
每个子任务完成后,需要验证输出是否正确。验证分为三个层次:
语法验证:子任务的输出是否符合预期的格式。如果预期输出是JSON,验证是否可解析。如果预期是Python代码,验证语法是否正确。这个层次不需要理解语义,成本最低。
功能验证:子任务的输出是否实现了预期的功能。如果预期是写一个函数,运行它看返回值是否正确。如果预期是修改文件,检查diff是否匹配预期。这个层次需要运行代码,成本中等。
逻辑验证:子任务的输出是否符合整体架构设计。如果写了一个新模块,检查它是否和现有模块的接口兼容。如果做了代码重构,检查是否破坏了已有功能。这个层次需要理解全局上下文,成本最高。
6.2 验证失败处理
验证失败不等于子任务完成失败。需要区分失败的原因:
验证失败 ├── 输出格式问题(语法错误、JSON格式错误) │ └─ 修改输出格式,不重新执行子任务 │ ├── 功能未满足(函数的输出和预期不匹配) │ └─ 将验证结果作为反馈,让子Agent重新执行 │ ├── 和依赖冲突(子任务B的修改覆盖了子任务A的修改) │ └─ 需要主Agent介入,协调两个子任务的修改 │ └── 架构设计不合理(新代码和现有架构不一致) └─ 需要重新设计,回退到任务分解阶段
6.3 验证的自动化程度
| 验证内容 | 自动化程度 | 实现方式 | 误报率 |
|---|---|---|---|
| 语法检查 | 完全自动化 | 编译器/解释器 | 低 |
| 单元测试 | 完全自动化 | 测试框架 | 低 |
| 接口兼容性 | 自动化 | 类型检查器 | 中 |
| 代码风格 | 自动化 | linter | 低 |
| 设计一致性 | 半自动化 | LLM评审 | 高 |
| 性能达标 | 自动化 | 基准测试 | 中 |
语法检查、单元测试、linter可以完全自动运行,不需要LLM参与。设计一致性检查需要LLM评审,但LLM评审的误报率较高(约20-30%)。所以在长期任务中,设计评审建议加一个"如果LLM评审报错,让子Agent说明理由后再决策"的缓冲。
七、2026年的技术现状
| 项目 | 时间范围 | 任务分解 | 持久化 | 错误恢复 | 验证 |
|---|---|---|---|---|---|
| Claude Code | 分钟-10分钟 | 内置 | 无 | 有限 | 有 |
| Codex CLI | 分钟-10分钟 | 内置 | 无 | 有限 | 有 |
| OpenHands | 10分钟-1小时 | 内置 | 有限 | 有 | 有 |
| AutoGPT 2.0 | 1小时+ | 内置 | 有 | 有 | 有限 |
| Omnigent | 1小时+ | 外部配置 | 有 | 有 | 有 |
| Hermes Agent | 10分钟-1小时 | 插件化 | 有 | 有 | 有限 |
Claude Code和Codex CLI主要面向分钟级任务,状态只在单次会话中保持,Agent退出后状态丢失。OpenHands可以运行1小时左右的任务,有基本的检查点和重试机制。AutoGPT 2.0和Omnigent支持小时级任务,有完整的持久化和恢复机制。
长期任务执行在2026年仍然是一个正在解决的工程问题,没有标准方案。大部分实现是项目特定的,通用框架还在开发中。
八、总结
-
Agent执行时间从秒级到小时级的跨越,核心挑战是上下文管理和状态持久化
-
任务分解的推荐粒度:每个子任务3-10分钟,输入输出有清晰接口约定
-
依赖图解析让子任务可以并行执行,缩短整体执行时间
-
上下文隔离:子Agent只接收执行本任务所需的信息,不共享全量上下文
-
检查点按步保存(每子任务完成一次)或按风险保存(操作前),序列化时需要压缩中间结果
-
错误恢复需要多阶段重试策略:前2次简单重试,第3次压缩上下文,第4次降级模型或标记人工介入
-
三个验证层次:语法验证(成本最低)→ 功能验证(需运行代码)→ 逻辑验证(需LLM评审)
-
语法检查、单元测试、linter可以完全自动运行,不需要LLM参与
-
设计一致性验证的LLM评审误报率约20-30%,建议增加缓冲机制
-
2026年长期任务执行没有标准方案,OpenHands和Omnigent的实现较为完整
-
从秒级任务到小时级任务,核心不是模型能力提升,而是工程架构的升级
更多推荐


所有评论(0)