Skip to content

第七章 OmO 后台任务与调度系统

← 返回目录 | 上一章:OmO Agent 体系 | 下一章:OmO 扩展与权限 →


7.1 为什么需要后台任务?

回顾 Opencode Agent 系统,Opencode 原生的 Task 工具是纯同步的——父 Agent 发起子任务后必须等待完成。这意味着如果 Sisyphus 需要同时搜索 5 个方向的信息,它必须串行执行,每个方向等 10-30 秒,总共需要 50-150 秒。

OmO 的 BackgroundManager 实现了真正的异步并行调度,使得上述场景可以在 10-30 秒内完成(所有搜索并行执行)。

7.2 BackgroundManager 架构

BackgroundManager(约 1500 行代码)是 OmO 后台任务系统的核心,管理任务的完整生命周期:

任务状态机:

  pending ──→ running ──→ completed

                ├──→ error

                └──→ cancelled

核心组件

typescript
class BackgroundManager {
  // 任务存储
  private tasks: Map<string, BackgroundTask>;

  // 并发控制:按 model+provider 分组的 FIFO 队列
  private concurrencyQueues: Map<string, Queue>;

  // 通知队列:等待注入到父会话的完成通知
  private pendingNotifications: Map<sessionId, TaskNotification[]>;

  // 轮询定时器
  private pollingInterval: NodeJS.Timer;
}

7.3 并发控制:FIFO 队列

后台任务并不是无限并行的。OmO 对每个 model+provider 组合维护一个 FIFO 队列,默认并发上限为 5:

并发队列示例:

[anthropic/claude-opus-4-6]     → 最多 5 个并发任务
[openai/gpt-5.4]                → 最多 5 个并发任务
[xai/grok-code-fast-1]          → 最多 5 个并发任务

为什么按 model+provider 分组? 因为 AI API 的速率限制(rate limit)通常是按模型粒度的。如果所有任务共享一个全局队列,一个慢模型的积压会阻塞快模型的执行。

当一个模型的队列满时,新任务会被放入 pending 状态,等待队列空位。

7.4 任务创建与执行流程

当 Sisyphus 调用 task() 工具并设置 run_in_background=true 时:

1. 解析任务参数
    ├─ subagent_type 指定?→ 直接使用对应 Agent
    ├─ category 指定?→ 通过 Category 系统解析 Agent + 模型
    └─ 两者都没有?→ 报错
    
2. 创建 BackgroundTask 对象
    ├─ 生成唯一 task_id
    ├─ 记录父 session_id(用于结果通知)
    ├─ 设置状态为 pending
    └─ 加入对应模型的并发队列

3. 队列调度
    ├─ 如果有空位 → 立即开始执行(状态 → running)
    └─ 如果队列满 → 等待空位

4. 执行(在后台)
    ├─ 创建子 Session
    ├─ 加载 Skills(如果指定了 load_skills)
    ├─ 组装 System Prompt
    ├─ 调用 Opencode 的 session.chat() API
    └─ 等待 Agent 完成

5. 完成处理
    ├─ 标记状态为 completed(或 error)
    ├─ 提取结果文本
    ├─ 将完成通知加入 pendingNotifications 队列
    └─ 等待注入到父会话

7.5 通知机制:Queue Messages

后台任务完成后,如何让父 Agent(Sisyphus)知道?这是通过 Queue Messages(队列消息)机制实现的:

后台任务完成

result-handler: 提取结果,标记为待通知

enqueueNotificationForParent: 将通知加入父 session 的队列

等待父 session 空闲(通过 event Hook 监听 idle 事件)

inject system-reminder: 将通知作为"系统提醒"注入父会话

父 Agent 收到类似以下内容:

╔══════════════════════════════════════════════════╗
║ [BACKGROUND TASK COMPLETED]                       ║
║ Task ID: task_abc123                              ║
║ Description: "Find auth implementations"          ║
║ Duration: 12.3s                                   ║
║ Status: completed                                 ║
║                                                    ║
║ Use background_output(task_id="task_abc123")      ║
║ to retrieve the full results.                     ║
╚══════════════════════════════════════════════════╝

关键设计:通知只告诉父 Agent"任务完成了",不直接包含结果。父 Agent 需要主动调用 background_output 工具来获取完整结果。这样设计的好处是:

  1. 节省上下文:完成通知很短(~100 tokens),而完整结果可能很长(~5000 tokens)。如果父 Agent 同时发射了 5 个后台任务,自动注入所有结果会浪费 25K tokens 的上下文空间。
  2. 选择性获取:父 Agent 可以根据已有信息决定是否需要某个任务的详细结果。
  3. 延迟加载:结果在需要时才加载,而不是一完成就注入。

7.6 轮询与空闲检测

BackgroundManager 使用 3 秒间隔的轮询来检查任务状态,并通过空闲稳定性检测来判断何时注入通知:

轮询循环(每 3 秒):
    1. 检查所有 running 状态的任务
    2. 更新已完成任务的状态
    3. 从 pending 队列调度新任务到空位
    4. 检查是否有待注入的通知

空闲检测(10 秒稳定窗口):
    - 监听 Opencode 的 session idle 事件
    - 只有当父 session 持续空闲 10 秒后,才注入通知
    - 这避免了在 Agent 正在思考时打断它

← 返回目录 | 上一章:OmO Agent 体系 | 下一章:OmO 扩展与权限 →