第8章:调度、委托与执行后端 —— 从一次对话到无人值守工作流
一、Cron 不是“定时 print 一句文本”
Hermes 的 cron 任务要完成完整的 Agent 运行:读取配置、构建 prompt、选择模型和工具、执行脚本或 Agent、持久化输出,再把结果投递到指定平台。cron/jobs.py:2753 的 run_job 和 :4099 的 tick 是核心入口。
一个 job 的生命周期可以画成:
二、为什么需要 claim 和 heartbeat
多个 Gateway 或多个 scheduler worker 可能同时看到同一个 due job。claim_dispatch、heartbeat_run_claim 和 execution records 用来防止重复执行;长任务还需要 heartbeat 证明 worker 仍然活着。
这和 session turn lease 是同一种思想:把“我正在负责这个副作用”写成可过期、可恢复的所有权记录。
三、Delivery target 不是一个字符串
cron/jobs.py 的 delivery 逻辑会解析 platform、chat、thread、home target、routing token、mirror 和 adapter capability。一个 job 的 deliver 值可能指向:
- 当前来源平台;
- 某个已配置的 home chat;
- 多个平台目标;
- profile 或 thread;
- 媒体/文本不同的发送路径。
先解析为结构化 target,再交给平台 adapter,可以避免 cron 模块直接理解每种平台 API。
四、Delegate:把一个大任务拆成隔离子任务
tools/delegate_tool.py:2778 的 delegate_task 与 agent/subagent_lifecycle.py 共同实现子 Agent。一个子任务至少要有:
- parent task/session identity;
- spawn depth 和并发上限;
- 独立或受限的 context;
- 工具/权限/credential 边界;
- 取消、超时、结果回收和 live log;
- 返回 parent 的结构化结果。
子 Agent 不是“再调用一次 AIAgent”这么简单。它必须防止:无限递归、凭证跨 profile 泄露、父任务取消后子任务孤儿化、并发 worker 把输出写到错误 session。
五、Delegation 的上下文传递
agent/delegation_context.py 使用 ContextVar/环境变量把 parent identity、session source 和运行上下文传递给子线程或子进程;subagent_lifecycle.py 的 registry 负责注册、状态、完成和清理。
可把它理解成“结构化的调用栈”:
parent session
└─ child task
└─ child tool call
└─ execution environment
每一级都可以看到必要的父信息,但不能随意反向修改父级所有状态。
六、Execution Environment:同一个工具,七种落点
tools/environments/base.py 的 BaseEnvironment 之下,源码提供 local、Docker、SSH、Singularity、Modal、Daytona、Vercel Sandbox 等实现。它们的共同接口通常围绕:
- 创建/复用环境;
- cwd 和文件同步;
- 执行命令、读 stdout/stderr;
- 超时、终止和进程回收;
- 网络/凭证/环境变量隔离;
- 清理和持久化。
这里体现“能力与落点分离”:模型只知道有 terminal,不需要知道底层是 Docker 还是远程 SSH;环境层决定真实的信任边界。
七、Relay:把调用生命周期集中到一个边界
agent/relay_runtime.py 的 RelayHostRegistry 和 session coordinator 相关代码,把 provider codec、逻辑调用、生命周期、共享指标和进程内 managed call 连接起来。Relay 的价值在于:
- 对模型调用进行统一观测;
- 管理异步/同步和跨线程调用;
- 记录一次 logical call 的开始、结束、失败、取消;
- 让共享指标只接收有界 projection,而不把原始敏感数据到处复制。
它是可选边界,不应改变 Agent Loop 的语义;当 relay 不可用时,核心运行时仍需要有明确的降级路径。
八、Batch 与 trajectory:研究工作负载的另一侧
batch_runner.py 和 trajectory_compressor.py 说明 Hermes 不只服务交互用户,也服务数据生成和训练前处理。研究负载需要:
- 固定模型/工具/环境配置;
- 多任务并行与重试;
- 保存完整 trajectory;
- 对工具调用轨迹做压缩、采样和清洗;
- 不把交互式 UI/Gateway 状态带入离线任务。
这再次证明 AIAgent 作为可复用运行时容器的价值:CLI、Gateway、Cron、Batch 都能共享核心 Loop,但拥有不同的 session source 和生命周期。
九、运维层的四个边界
- 调度边界:何时执行、谁 claim、下一次何时运行。
- 委托边界:谁创建子任务、谁拥有并发和深度配额。
- 执行边界:命令在哪运行、文件和网络如何隔离。
- 投递边界:结果发送给谁、重复发送如何避免。
拆开这四个边界,才能让“无人值守”不等于“无人负责”。