心跳执行模型:DB 驱动的 Agent 唤醒与调度
心跳执行模型:DB 驱动的 Agent 唤醒与调度
1. 问题定义
在多 Agent 编排系统中,如何高效、可靠地唤醒 Agent 执行任务?简单的轮询(定时轮询每个 Agent)存在以下问题:
- 无效唤醒:没有任务的 Agent 也被唤醒,浪费 token
- 重复唤醒:同一 Agent 被多次触发,导致并发执行冲突
- 预算失控:唤醒前不检查预算,Agent 执行到超出预算才停止
- 状态丢失:Agent 进程崩溃后,唤醒队列中的任务无法恢复
心跳执行模型通过 DB 驱动的唤醒队列解决这些问题。
2. 核心架构
3. 唤醒队列设计
3.1 队列存储
唤醒请求持久化到数据库表(如 agent_wakeup_requests),包含:
| 字段 | 作用 |
|---|---|
agent_id | 目标 Agent |
issue_id | 关联任务 |
trigger_type | 触发类型(scheduled / event / manual) |
coalesce_key | 合并键,相同键的请求合并为一个 |
status | pending / processing / completed / skipped |
created_at | 创建时间,用于优先级排序 |
3.2 Coalescing(合并)
当同一 Agent 在短时间内收到多个唤醒请求时,合并为一个执行:
- 按 coalesce_key 合并:相同 Agent + 相同触发源的请求合并
- 保留最新上下文:合并时保留最新的 issue 关联和参数
- 避免并发冲突:同一 Agent 的 run 在执行中时,新请求排队
4. 触发器类型
4.1 定时触发(Scheduled)
基于 cron 表达式的定时唤醒,常用于:
- Agent 定期检查分配的任务
- 周期性报告生成
- 例行维护和监控
4.2 事件触发(Event-driven)
由系统事件触发的唤醒:
| 事件 | 行为 |
|---|---|
| 新任务分配给 Agent | 立即唤醒 Agent 开始执行 |
| 任务被 @-mention | 唤醒 Agent 处理提及 |
| 上级 Agent 下发子任务 | 唤醒子 Agent |
| 审批通过/拒绝 | 唤醒 Agent 调整策略 |
4.3 手动触发(Manual)
由人类操作者(董事会)手动触发,用于:
- 强制 Agent 立即执行
- 调试和测试
- 紧急干预
5. 执行前检查
5.1 预算预检
在执行 Agent 之前,检查该 Agent 的月度预算消耗:
预算检查流程:1. 查询当前月度已消耗金额2. 对比预算政策的 warn_percent 和 hard_limit3. 若已超 hard_limit → 跳过执行,记录 skipped 事件4. 若达到 warn_percent → 继续执行,但记录 warning 事件5. 若正常 → 继续执行关键设计:预算检查是执行前的预检,而非执行中监控。这避免了 Agent 已经开始执行后被强行中断的中间状态。
5.2 工作空间解析
确定 Agent 执行时需要的工作目录:
- 项目工作空间:Agent 关联的项目目录
- 执行工作空间:git worktree 或 operator branch,隔离执行环境
- 运行时服务:dev server、preview URL 等
5.3 Secret 注入
将 Agent 执行所需的 API Key、数据库连接字符串等敏感信息注入到执行环境:
- Secret 在运行时解析,不暴露给提示词
- 按 Agent scope 过滤,Agent 只能获取自己绑定的 Secret
- 解析结果缓存,避免每次心跳都解密
6. 执行模式
6.1 Local CLI/Session 模式
启动或恢复本地 CLI Agent 会话(如 Claude Code、Codex):
伪代码:process = spawn(agent_command, args)pipe stdout/stderr to run_logawait process.exitparse exit_code, usage, session_statereturn execution_result6.2 Command Execution 模式
执行任意命令或脚本:
伪代码:process = spawn(command, args, cwd=workspace_dir)inject env_vars (secrets, workspace paths)pipe stdout/stderr to run_logawait process.exit6.3 Fire-and-Forget Webhook 模式
通知外部 Agent(如 OpenClaw)执行:
伪代码:http.post(agent_webhook_url, { issue_id, task_description, context})# 不等待结果,Agent 完成后回调报告7. 执行后处理
7.1 成本事件记录
从 Adapter 执行结果中提取 token 使用量和成本:
| 维度 | 说明 |
|---|---|
| Agent | 哪个 Agent 消耗的 |
| Issue | 哪个任务产生的 |
| Provider | 哪个模型提供商 |
| Model | 哪个模型 |
| Cost USD | 美元成本 |
| Tokens | input / output / cached |
7.2 会话状态持久化
保存 Agent 的会话状态,支持下次心跳恢复:
- 会话 ID 和会话参数
- 未完成的工具调用状态
- 压缩/摘要后的上下文
7.3 审计日志
所有心跳执行写入活动日志(activity log),记录:
- 谁触发了执行
- 执行了哪个 Agent 的哪个任务
- 执行结果(成功/失败/跳过)
- 成本和时间
8. 孤儿恢复(Recovery)
当 Agent 进程异常退出或心跳执行超时,可能产生孤儿 run:
8.1 检测机制
- Liveness 检查:定期检查运行中的 heartbeat run 是否还在产出日志
- Watchdog 决策:超时后自动标记为 orphaned
- 恢复动作表:系统自动生成 recovery action 记录
8.2 恢复策略
| 场景 | 策略 |
|---|---|
| 进程意外退出,执行锁仍在 | 释放执行锁,创建 recovery action |
| 孤儿 run 超过最大重试次数 | 标记为 failed,升级人工处理 |
| Agent 会话可恢复 | 恢复会话上下文,重新入队唤醒 |
9. 陷阱与对策
| 陷阱 | 后果 | 对策 |
|---|---|---|
| 高频心跳浪费 token | 预算快速消耗 | Coalescing 合并重复唤醒 + 预算预检 |
| 并发唤醒导致双分配 | 同一任务被两个 Agent 同时执行 | 原子 checkout + 执行锁 |
| 预算竞态(多个 Agent 同时通过预检) | 总消耗超出预算 | 预算检查时加乐观锁,或使用余额扣减 |
| 孤儿 run 无法检测 | Agent 已死但系统认为还在运行 | Liveness watchdog + 超时自动标记 |
| Secret 注入泄漏 | 敏感信息出现在日志中 | 日志脱敏(log redaction),Secret 值替换为 [REDACTED] |
10. 参考来源
- Paperclip Heartbeat Service —
server/src/services/heartbeat.ts(361KB,完整实现) - Paperclip Agent Wakeup Requests Schema —
packages/db/src/schema/agent_wakeup_requests.ts - Paperclip Run Liveness —
server/src/services/run-liveness.ts - Paperclip SPEC-implementation.md — §5.1 Heartbeat invocation