工程化扩展:后台任务、Cron、Worktree 与 MCP
当 Agent 能调用工具、管理上下文和拆分任务后,仍然有四个现实问题:
- 慢命令会阻塞主循环;
- 周期任务需要按时间触发;
- 多个 Agent 会修改同一份代码;
- 外部服务的工具接入缺少统一标准。
后台任务、Cron、Git Worktree 和 MCP 正好对应这四个问题。
一、后台任务:慢操作不应阻塞主循环
以下命令可能运行数分钟:
bash
npm install
pytest
docker build
mvn package
如果工具调用一直等待命令返回,Agent 无法处理其他工作。
因此 Bash 工具可以支持:
json
{
"command": "pytest",
"run_in_background": true
}
Harness 立即返回占位结果:
text
后台任务 bg_0003 已启动,完成后会发送通知。
命令在独立线程或进程中执行,完成后生成独立事件:
xml
<task_notification>
<task_id>bg_0003</task_id>
<status>completed</status>
<summary>128 tests passed</summary>
</task_notification>
通知在下一轮注入 messages。
这里必须注意:原始工具调用已经返回过一次 tool_result,后台完成通知不应再次伪装成同一个 tool_result,而应作为新的异步事件。
二、真正的后台任务需要完整生命周期
教学 Demo 用线程就能演示,但生产实现还要处理:
text
进程 ID
stdout/stderr 重定向
增量读取输出
超时和看门狗
主动终止
异常退出
任务恢复
最大并发数
结果持久化
后台任务的本质不是"开线程",而是:
管理一个脱离当前工具调用生命周期的执行单元,并在未来把状态变化重新送回 Agent。
在 Node.js/Bun 等单线程事件循环环境中,真正的 Shell 命令通常由独立子进程运行,主进程只负责管理状态和输出文件。
三、Cron:调度与执行必须解耦
"每天早上九点运行测试"和"现在运行测试"不是同一种触发方式。
Cron 系统可以拆成四层:
text
Scheduler
→ Queue
→ Queue Processor
→ Agent Loop
Scheduler
独立线程或定时器负责判断时间是否到达。
Queue
到期任务只进入队列,不直接执行。
Queue Processor
检查 Agent 是否空闲,决定何时交付。
Agent Loop
将调度消息当作新的用户输入处理。
这种拆分保证:
- 调度器不需要理解 Agent;
- Agent Loop 不需要每轮计算 Cron;
- 忙碌时任务可以排队;
- 一个坏任务不会拖垮整个调度线程。
四、Agent 内部 Cron 不是操作系统定时任务
Agent 自带调度器通常依赖 Agent 进程存活。
即使任务配置被持久化,进程关闭时也不会自动触发。重新启动后可以恢复配置,但无法替代系统级 Cron、Kubernetes CronJob 或云调度服务。
因此:
- 需要强准时、跨进程保证的任务,使用系统调度器;
- 需要把事件注入当前 Agent 会话的轻量任务,可以使用 Agent 内部 Cron。
为了避免多个任务同时整点触发造成惊群,还可以加入随机抖动。代价是任务不会绝对精确到秒。
五、Worktree:任务系统解决"做什么",Worktree 解决"在哪做"
多个 Agent 在同一个目录修改代码时,会出现覆盖和脏状态混合。
Git Worktree 是 Git 原生能力:
bash
git worktree add .worktrees/auth -b wt/auth HEAD
git worktree add .worktrees/ui -b wt/ui HEAD
此时:
text
Alice → .worktrees/auth
Bob → .worktrees/ui
两个工作目录拥有独立分支,但共享同一个 Git 对象数据库,成本远低于完整复制仓库。
Worktree 隔离系统需要完成三件事:
text
创建
绑定
清理
创建
为任务建立独立目录和分支。
绑定
在任务记录中保存:
json
{
"task_id": "task_001",
"worktree": "auth"
}
Agent 认领任务后,所有 Bash 和文件工具都在对应目录执行。
清理
任务完成后:
keep:保留分支等待 Review;remove:删除 Worktree;- 有未提交改动时默认拒绝删除;
- 强制删除必须显式确认。
六、Worktree 本身并不负责 Agent 绑定
Git 只提供多个工作目录,不知道哪个 Agent 应该使用哪个目录。
因此 Harness 必须维护:
text
Agent
→ Task
→ Worktree ID
→ cwd
所有工具执行都应显式使用该 cwd,而不是依赖模型自己记住先执行一次 cd。
只在 System Prompt 中告诉 Agent"请进入某目录"并不可靠,目录绑定应该是运行时状态。
七、MCP:统一外部工具的发现与调用
如果每接一个服务都手写工具,Agent 很快会出现大量重复适配代码。
MCP 将外部工具接入拆成三层:
text
MCP 定义发现和调用工具的语义
JSON-RPC 定义请求与响应消息格式
stdio / HTTP 负责消息传输
典型流程:
text
Client 连接 Server
→ tools/list
→ 获得工具 Schema
→ 将工具加入模型工具池
→ 模型调用工具
→ tools/call
→ 结果返回模型
MCP Server 可以由任何语言实现,只要遵守协议。
八、MCP 工具必须处理命名冲突
多个 Server 都可能提供 search 工具。
因此可以使用命名空间:
text
mcp__notion__search
mcp__github__search
mcp__docs__search
连接后动态组装工具池:
python
tools = BUILTIN_TOOLS + discovered_mcp_tools
handlers = BUILTIN_HANDLERS + mcp_handlers
名称还应经过规范化,避免特殊字符造成冲突或注入。
九、动态工具会影响 Prompt Cache
如果在对话中途连接 MCP Server,工具列表发生变化:
text
之前:内置工具
之后:内置工具 + MCP 工具
如果所有完整 Schema 都位于请求前部,工具池变化可能破坏缓存前缀。
一种优化思路是延迟加载:
text
固定前缀:基础工具 + ToolSearch + System Prompt + 历史
动态后缀:命中的 MCP 工具 Schema
模型先搜索工具,再只加载需要的定义。这样既控制上下文体积,也提高缓存复用。
十、MCP 不只是单向工具调用
标准 JSON-RPC 连接可以维持后台读取循环,因此 Server 还可以向 Client 发送通知:
text
资源变化
进度更新
日志消息
工具列表变化
这意味着 MCP 不只是"远程函数调用包装",还可以成为 Agent 与外部系统之间的双向事件通道。
当然,反向通知必须经过:
- 来源校验;
- 消息类型校验;
- 限流;
- 权限判断;
- 上下文注入策略。
否则外部 Server 可以持续向 Agent 注入噪声。
十一、完整 Harness 中各模块的位置
把前面的机制放回一个循环,可以得到:
text
用户输入
→ UserPromptSubmit Hooks
→ 注入 Cron 和后台完成通知
→ 上下文压缩
→ 组装 Memory、Skills、MCP 状态
→ 调用模型
→ 错误恢复
→ 检查 Tool Use
→ PreToolUse Hooks 和权限
→ 内置/MCP/后台工具分发
→ PostToolUse Hooks
→ Tool Result 返回消息
→ 下一轮
多 Agent 则在外部共享:
text
Task Store
Message Bus
Protocol State
Worktree Registry
Permission Channel
核心循环仍然没有发生本质变化,变化的是周围的运行环境越来越完整。
十二、总结
这四个模块分别解决:
| 模块 | 核心问题 |
|---|---|
| Background Task | 慢操作不能阻塞 |
| Cron | 任务如何按时间进入系统 |
| Worktree | 并行修改如何隔离 |
| MCP | 外部能力如何标准接入 |
它们共同体现了一个设计原则:
模型负责选择动作,Harness 负责执行、调度、隔离和接入。
当 Agent 从一次性 Demo 走向长期运行时,这些看似"外围"的工程能力,反而决定了系统是否真正可用。