子代理的错误传播与恢复

子代理错误处理和恢复

一、错误类型分类

在子代理系统中,错误并非铁板一块。根据错误的性质和可否自动恢复,我们将错误分为两大类:可恢复错误和不可恢复错误。正确区分这两种错误是构建健壮子代理系统的第一步。

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", "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 主代理的错误决策

主代理收到子代理的错误后,执行以下决策流程:

  1. 错误分类:根据错误类型和严重程度,判断是否可恢复。
  2. 重试判定:对于可恢复错误,检查重试次数是否已达上限(默认3次)。未达上限则触发自动重试;已达上限则将错误升级为不可恢复。
  3. 任务重新分配:如果子代理任务失败且不可恢复,检查任务是否可以分配给其他健康的子代理执行。
  4. 降级决策:如果无法重新分配,评估是否可以使用部分结果进行降级处理。
  5. 错误升级:所有降级路径都无法走通时,将错误升级到用户层,请求人工介入。
核心要点: 错误传播不是简单的"出了问题就上报",而是一个完整的生命周期管理过程——从错误发生、错误封装、错误传递到错误决策和错误恢复,每个环节都应有清晰的定义和处理逻辑。

三、错误隔离

错误隔离是子代理系统中最重要的设计原则之一。其核心理念是:一个子代理的失败不应该污染或影响其他子代理的正常工作。良好的错误隔离机制可以保证系统的局部故障不会演变为全局灾难。

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 故障子代理的任务重分配

当一个子代理被判定为失败(不可恢复错误或超出重试次数)后,主代理会将其未完成的任务重新分配给一个健康的空闲子代理。重分配过程包括:

  1. 从任务队列中取出失败子代理的待办任务列表。
  2. 检查任务是否具有幂等性(即重复执行是否会产生相同结果)。
  3. 将任务重新入队并分配给下一个可用的健康子代理。
  4. 如果分配成功,通知失败子代理的管理模块清理其资源。
注意: 任务重新分配仅适用于无状态或可重入的任务。对于有副作用的操作(如已发送的邮件、已扣款的订单),不能简单重分配,而应设计对应的补偿事务(Saga模式)。

四、自动重试策略

自动重试是处理可恢复错误的首选策略。一个设计良好的重试机制能够在最大限度地提高任务成功率的同时,避免对系统和下游服务造成过大的负担。

4.1 重试条件判定

并非所有错误都适合重试。系统在触发重试前会进行以下检查:

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个数据源超时失败。系统不会简单地返回"失败",而是:

这种"尽力而为"的策略在大多数场景下比全有全无的方式更有实际价值。

5.2 透明通知机制

降级后的结果必须附带清晰的降级说明。用户(或调用方)有权知道他们拿到的结果是不完整的或经过降级处理的。系统的通知原则是:

{ "status": "degraded", "message": "任务已完成,但部分数据因外部服务超时而缺失", "completed_parts": ["用户画像分析", "行为路径分析", "留存率分析"], "failed_parts": ["实时推荐分析"], "failure_reason": "推荐引擎API超时,已重试3次后放弃", "user_action": "请稍后手动刷新推荐分析模块,或联系系统管理员检查推荐服务状态" }

5.3 降级后的功能保证

降级不是"随便返回一些东西",它需要遵循明确的契约:

核心要点: 优雅降级的精髓不在于"不出错",而在于"出错时仍然有用"。一个降级后能提供60%功能的系统,远胜于一个追求100%但在出错时完全停摆的系统。

六、最佳实践总结

综合以上五个方面的内容,以下是子代理系统错误处理的最佳实践总结:

清晰分类
建立完整的错误类型和严重程度分类体系,确保每个错误都能被快速归类并触发对应的处理策略。
完整传播
错误信息在传播过程中保持结构化和完整性,不丢失任何有用的调试上下文。
严格隔离
通过进程级、文件系统级和网络级的隔离,确保一个子代理的故障不会扩散到其他子代理。
智能重试
对可恢复错误实施指数退避自动重试,加入随机抖动避免重试风暴,严格限定重试次数上限。
优先降级
在无法完美完成任务时,优先选择提供部分结果而非直接失败,同时透明告知用户降级状态。
人工兜底
所有自动处理路径都走不通时,必须有清晰的人工介入通道和完整的现场信息供排查。