一、错误类型分类
在子代理系统中,错误并非铁板一块。根据错误的性质和可否自动恢复,我们将错误分为两大类:可恢复错误和不可恢复错误。正确区分这两种错误是构建健壮子代理系统的第一步。
1.1 可恢复错误(Transient Errors)
可恢复错误是指那些由临时性、非根本性原因导致的异常,通常在重试后能够成功处理。这类错误不需要修改代码逻辑或人工介入即可自行解决。
常见的可恢复错误包括:网络超时——子代理在调用外部API或远程资源时连接暂时中断,短暂等待后可恢复。临时资源不足——服务器负载过高、内存短暂吃紧或数据库连接池满,稍后重试即可。冲突(Conflict)——并发操作导致的乐观锁冲突或版本号不匹配,重新读取最新状态后重试可解决。服务限流——目标服务返回429状态码(Too Many Requests),等待指数退避后重试即可。
系统对可恢复错误的处理策略是自动重试。重试时通常配合指数退避算法,避免对下游服务造成二次压力。
设计原则: 可恢复错误的判定标准——"同样的输入,换个时间再执行是否可能成功?"如果答案是肯定的,则为可恢复错误。
1.2 不可恢复错误(Fatal Errors)
不可恢复错误是指那些即使重试也无法解决的、由根本性原因导致的异常。这类错误需要修改代码、调整配置或人工介入才能解决。
常见的不可恢复错误包括:语法错误——子代理生成的代码或配置文件存在语法问题(如JSON格式错误、Python缩进错误),重试一千次也无法通过编译或解析。逻辑错误——子代理理解任务意图有误,导致生成了错误的算法或业务逻辑路径,重试只会得到相同的结果。权限拒绝——子代理没有访问某个资源(文件、API、数据库)的权限,不修改授权配置则无法解决。输入参数校验失败——传入的参数类型错误、值超出范围或缺少必填字段。资源不存在——引用的文件、表、服务已被删除或从未存在过。
系统对不可恢复错误的处理策略是立即失败并上报主代理,由主代理决定是否终止整个任务、降级处理或转交给人工处理。
1.3 错误严重程度分级
为了更精细地控制错误处理行为,系统对错误定义了严重程度分级:
| 级别 | 名称 | 说明 | 处理方式 |
| L1 | 轻微(Minor) | 不影响核心功能的警告类错误 | 记录日志,继续执行 |
| L2 | 一般(Major) | 影响部分功能但可降级 | 自动重试,重试失败后降级 |
| L3 | 严重(Critical) | 导致单个子代理任务失败 | 重试+上报主代理重新分配 |
| L4 | 致命(Fatal) | 导致整个任务链路不可用 | 立即终止,通知用户介入 |
二、错误传播机制
错误传播是指子代理在执行任务过程中发生错误时,将错误信息逐层传递到主代理的过程。一个设计良好的错误传播机制应当保证:错误信息完整不失真、传播路径清晰可追溯、主代理能够根据错误信息做出正确决策。
2.1 子代理错误的捕获与封装
子代理在执行其被分配的子任务时,如果遇到异常(无论是可恢复还是不可恢复),首先会将原始异常捕获并封装为结构化的错误对象。这个错误对象包含以下关键字段:
- 错误类型(type):标识错误所属的类别,如 NetworkTimeout、SyntaxError、PermissionDenied、ValidationError 等。该字段用于快速判断错误是否可恢复。
- 错误位置(location):指明错误发生在哪个子代理的哪个步骤或函数中。包含子代理 ID、任务步骤编号、代码行号等信息。
- 堆栈跟踪(stack_trace):完整的调用堆栈信息,用于开发和调试时定位问题根源。
- 错误消息(message):人类可读的错误描述,包含具体的失败原因和上下文信息。
- 时间戳(timestamp):错误发生的时间点,用于时序分析。
- 上下文(context):错误发生时的现场数据快照,如输入参数、部分输出结果、环境状态等。
{
"type": "NetworkTimeout",
"severity": "Major",
"recoverable": true,
"location": {
"agent_id": "subagent-data-fetcher-03",
"step": "fetch_external_data",
"retry_count": 2
},
"stack_trace": "Error: connect ETIMEDOUT 203.0.113.42:443\n at ...",
"message": "请求外部数据源超时,已重试2次,等待时间4秒",
"timestamp": "2026-05-08T10:15:30.123Z",
"context": {
"url": "https://api.example.com/data",
"timeout_ms": 30000,
"partial_result": null
}
}
2.2 传播路径:子代理 → 主代理
错误从子代理传播到主代理的路径是单向且明确的。子代理在捕获异常并封装为错误对象后,通过预定义的通信通道(如消息队列、共享内存、HTTP回调或进程退出码)将错误对象发送给主代理。
传播过程遵循以下规则:子代理不应自行决定终止还是继续——所有错误(包括可恢复错误)都必须上报主代理。可恢复错误的自动重试由主代理或专用的错误管理器触发,而非由子代理自己决定。错误对象在整个传播过程中保持不可变性,中间层不得修改、丢弃或简化错误信息。传播延迟应尽可能低——错误信息应当实时到达主代理,避免因批量发送导致错误响应滞后。
最佳实践: 主代理维护一个全局的错误汇总表(Error Registry),记录所有子代理上报的错误及其当前状态(待处理、重试中、已解决、已升级)。这为主代理的整体决策提供了数据基础。
2.3 主代理的错误决策
主代理收到子代理的错误后,执行以下决策流程:
- 错误分类:根据错误类型和严重程度,判断是否可恢复。
- 重试判定:对于可恢复错误,检查重试次数是否已达上限(默认3次)。未达上限则触发自动重试;已达上限则将错误升级为不可恢复。
- 任务重新分配:如果子代理任务失败且不可恢复,检查任务是否可以分配给其他健康的子代理执行。
- 降级决策:如果无法重新分配,评估是否可以使用部分结果进行降级处理。
- 错误升级:所有降级路径都无法走通时,将错误升级到用户层,请求人工介入。
核心要点: 错误传播不是简单的"出了问题就上报",而是一个完整的生命周期管理过程——从错误发生、错误封装、错误传递到错误决策和错误恢复,每个环节都应有清晰的定义和处理逻辑。
三、错误隔离
错误隔离是子代理系统中最重要的设计原则之一。其核心理念是:一个子代理的失败不应该污染或影响其他子代理的正常工作。良好的错误隔离机制可以保证系统的局部故障不会演变为全局灾难。
3.1 进程级隔离
每个子代理在独立的进程中运行,拥有独立的进程空间、内存堆栈和环境变量。当一个子代理因为段错误、内存泄漏或无限循环而崩溃时,操作系统会独立回收该进程的资源,其他子代理完全不受影响。这种隔离是最基础但也最可靠的屏障。
在实现层面,主代理通过进程管理器(如 supervisord、pm2 或容器编排工具)来创建和管理子代理进程。每个子代理进程的生命周期是独立的,主代理可以随时启动、停止或重启任意子代理而不会波及其他人。
3.2 文件系统隔离(Worktree 隔离)
子代理在执行任务时通常需要读写文件系统(生成代码、下载数据、写入日志等)。如果多个子代理共享同一个工作目录,很容易产生文件冲突和污染。为此,系统采用 Worktree 隔离机制。
每个子代理在启动时被分配一个独立的临时工作目录(Worktree),该目录的生命周期与子代理的任务生命周期一致。子代理的所有文件操作都被限制在这个隔离的 Worktree 内。任务完成后,Worktree 被清理回收。如果子代理崩溃,其 Worktree 可作为"事故现场"保留用于调试,而不会影响全局文件结构。
# 每个子代理获得独立的工作目录
subagent_01_worktree/
├── temp/
├── output/
└── logs/
subagent_02_worktree/
├── temp/
├── output/
└── logs/
# 一个子代理的脏数据不会出现在另一个的目录中
3.3 网络与资源隔离
子代理对网络资源和外部服务的访问也应互相隔离。具体措施包括:每个子代理使用独立的 API 密钥或令牌,避免一个子代理的密钥泄露影响全局。请求超时和重试参数彼此独立,一个子代理的网络阻塞不会阻塞其他子代理的网络请求。数据库连接使用独立的连接池,会话和事务互不干扰。
3.4 故障子代理的任务重分配
当一个子代理被判定为失败(不可恢复错误或超出重试次数)后,主代理会将其未完成的任务重新分配给一个健康的空闲子代理。重分配过程包括:
- 从任务队列中取出失败子代理的待办任务列表。
- 检查任务是否具有幂等性(即重复执行是否会产生相同结果)。
- 将任务重新入队并分配给下一个可用的健康子代理。
- 如果分配成功,通知失败子代理的管理模块清理其资源。
注意: 任务重新分配仅适用于无状态或可重入的任务。对于有副作用的操作(如已发送的邮件、已扣款的订单),不能简单重分配,而应设计对应的补偿事务(Saga模式)。
四、自动重试策略
自动重试是处理可恢复错误的首选策略。一个设计良好的重试机制能够在最大限度地提高任务成功率的同时,避免对系统和下游服务造成过大的负担。
4.1 重试条件判定
并非所有错误都适合重试。系统在触发重试前会进行以下检查:
- 错误类型检查:只有标记为 recoverable=true 的错误才进入重试流程。
- 重试次数检查:同一错误最多重试3次,超过后升级为不可恢复错误。
- 重试价值评估:重试前评估任务是否仍有执行价值——例如,如果数据源已过期或依赖的服务已下线,重试也没有意义。
- 时间预算检查:如果任务已经接近截止时间,重试可能导致整体超时,此时放弃重试直接降级。
4.2 指数退避算法
重试不能是无间隔的密集重试——这会造成"重试风暴",进一步压垮本已脆弱的系统。系统采用指数退避算法来控制重试间隔:
重试间隔序列:
第1次重试:等待 1秒 后重试
第2次重试:等待 2秒 后重试
第3次重试:等待 4秒 后重试
如果3次均失败:停止重试,错误升级
每次重试间隔 = 基础等待时间 × 2^(重试次数-1)
加入随机抖动(jitter):实际等待时间 = 计算间隔 × (0.5 ~ 1.5 之间的随机数)
引入随机抖动(jitter)是为了避免"惊群效应"——当多个子代理同时遇到同一个服务故障时,如果它们的退避时间完全一致,会在同一时刻发起重试,形成流量尖峰。随机抖动将重试时间打散,让流量平滑恢复。
4.3 幂等性保障
自动重试的前提是操作具有幂等性——即同一个操作执行多次与执行一次的效果相同。如果某个操作不是天然幂等的(如创建订单、发送通知),系统需要在上层增加幂等性保障,例如:为每个任务分配唯一请求ID(Idempotency Key),服务端根据请求ID去重。在执行前先检查操作是否已经完成,避免重复执行。使用数据库乐观锁(Optimistic Locking),通过版本号防止重复提交。
核心原则: 不能保证幂等性的操作不应自动重试。如果一个操作无法设计为幂等的,应该放弃自动重试,改为人工确认。
五、优雅降级策略
优雅降级是当系统无法完美完成任务时的备选方案。与"硬失败"(直接报错终止)不同,优雅降级力求在能力范围内提供尽可能多的有用输出,同时诚实告知用户当前的局限。
5.1 部分结果利用
当子代理任务中途失败时,其已经产生的部分结果不应被丢弃。主代理会从失败的子代理处回收其已完成的中间产物,并尝试整合到最终输出中。
例如,一个数据分析任务需要查询10个数据源,假设第8个数据源超时失败。系统不会简单地返回"失败",而是:
- 使用前7个数据源的成功结果进行分析。
- 在报告中明确标注"以下数据源未获取到数据"并列出失败的数据源。
- 提示用户可以手动补充缺失的数据源或稍后重试。
这种"尽力而为"的策略在大多数场景下比全有全无的方式更有实际价值。
5.2 透明通知机制
降级后的结果必须附带清晰的降级说明。用户(或调用方)有权知道他们拿到的结果是不完整的或经过降级处理的。系统的通知原则是:
- 不静默失败:降级发生时必须在输出中明确提示,不得悄然返回不完整的结果。
- 说明原因:告知用户导致降级的具体原因(哪个子代理、什么错误类型)。
- 标注范围:清晰界定已成功完成的部分和未完成的部分。
- 提供建议:给出用户可以采取的行动建议(如手动补数据、稍后重试、联系管理员)。
{
"status": "degraded",
"message": "任务已完成,但部分数据因外部服务超时而缺失",
"completed_parts": ["用户画像分析", "行为路径分析", "留存率分析"],
"failed_parts": ["实时推荐分析"],
"failure_reason": "推荐引擎API超时,已重试3次后放弃",
"user_action": "请稍后手动刷新推荐分析模块,或联系系统管理员检查推荐服务状态"
}
5.3 降级后的功能保证
降级不是"随便返回一些东西",它需要遵循明确的契约:
- 核心功能优先:系统设计时应明确哪些是核心功能、哪些是增强功能。降级时优先保证核心功能可用,裁剪增强功能。例如,一个内容生成任务,文本内容为核心功能,配图和排版为增强功能。当图片服务不可用时,只返回纯文本并注明配图缺失。
- 降级行为可预期:每个服务的降级行为应当有明确的定义和文档描述,调用方可以据此做出对应的处理。
- 提供回退数据:当实时数据不可用时,使用缓存数据或默认值作为回退。回退数据应有明确标识,避免用户将其当作实时数据使用。
- 恢复后同步:降级期间产生的数据变更,在服务恢复后应能同步回正常状态,保证最终一致性。
核心要点: 优雅降级的精髓不在于"不出错",而在于"出错时仍然有用"。一个降级后能提供60%功能的系统,远胜于一个追求100%但在出错时完全停摆的系统。
六、最佳实践总结
综合以上五个方面的内容,以下是子代理系统错误处理的最佳实践总结:
清晰分类
建立完整的错误类型和严重程度分类体系,确保每个错误都能被快速归类并触发对应的处理策略。
完整传播
错误信息在传播过程中保持结构化和完整性,不丢失任何有用的调试上下文。
严格隔离
通过进程级、文件系统级和网络级的隔离,确保一个子代理的故障不会扩散到其他子代理。
智能重试
对可恢复错误实施指数退避自动重试,加入随机抖动避免重试风暴,严格限定重试次数上限。
优先降级
在无法完美完成任务时,优先选择提供部分结果而非直接失败,同时透明告知用户降级状态。
人工兜底
所有自动处理路径都走不通时,必须有清晰的人工介入通道和完整的现场信息供排查。