Learn Claude Code Agent 开发 | 8、后台异步执行:慢操作不阻塞主工作流
整体概述
s08 引入了后台异步任务执行机制,核心思想来自官方文档:
"慢操作丢后台, agent 继续想下一步" -- 后台线程跑命令, 完成后注入通知。
Harness 层: 后台执行 -- 模型继续思考, harness 负责等待。
这解决了长耗时命令阻塞智能体工作的问题,让智能体可以同时处理多个任务,大幅提升工作效率,是从单任务串行走向多任务并行的关键一步。
解决的核心问题(来自官方文档)
之前阻塞式执行的痛点:
- 执行
npm install、pytest、docker build这类需要跑几分钟的命令时,智能体只能干等,什么都做不了 - 用户要求"装依赖的同时建个配置文件",智能体却只能串行执行,先等依赖装完再建文件
- 无法同时启动多个长耗时任务,资源利用率低,执行效率差
核心架构设计
架构分层:
┌───────────────────────────────────────────────────────────┐
│ 主线程(非阻塞) 后台线程(并行执行) │
├─────────────────────────────┐ ┌────────────────────────┤
│ agent_loop 智能体循环 │ │ 子进程1:npm install │
│ 1. 排空后台通知队列 │ │ 子进程2:pytest │
│ 2. 注入完成的任务结果 │ │ 子进程3:docker build │
│ 3. 调用LLM决策下一步 │ └────────────────────────┤
│ 4. 执行工具(可启动新后台任务)┘ │
│ 5. 循环回到步骤1 │
└───────────────────────────────────────────────────────────┘
时间线示例:
Agent --[启动后台任务A]--[启动后台任务B]--[创建配置文件]--[其他工作]----
| |
v v
[A执行中] [B执行中] (并行执行,不阻塞主线程)
| |
+---- 结果注入通知队列,下一轮LLM调用前自动注入 ----+
核心创新:
- 非阻塞执行:后台任务启动后立即返回,主线程继续执行,不会阻塞
- 通知队列:后台任务完成后结果进入线程安全的通知队列,不会打断当前工作
- 自动注入:每次调用LLM前自动排空通知队列,把后台结果注入到对话上下文中
- 线程安全:所有共享数据操作都有加锁,不会出现竞态条件
逐段代码解析
1. 系统提示更新(第46行)
python
SYSTEM = f"You are a coding agent at {WORKDIR}. Use background_run for long-running commands."
明确告诉模型对于长耗时命令应该使用 background_run工具放到后台执行。
2. BackgroundManager 类(第50-108行)⭐ 核心组件
对应文档第38-69行的实现,负责后台任务的管理和通知:
python
class BackgroundManager:
def __init__(self):
self.tasks = {} # 存储所有任务:task_id -> {status, result, command}
self._notification_queue = [] # 已完成任务的通知队列
self._lock = threading.Lock() # 线程锁,保证并发安全
初始化三个核心结构:任务状态存储、通知队列、线程锁。
python
def run(self, command: str) -> str:
"""启动后台任务,立即返回task_id,不阻塞"""
task_id = str(uuid.uuid4())[:8] # 生成短8位唯一ID
self.tasks[task_id] = {"status": "running", "result": None, "command": command}
# 启动守护线程,程序退出时自动销毁
thread = threading.Thread(
target=self._execute, args=(task_id, command), daemon=True
)
thread.start()
return f"Background task {task_id} started: {command[:80]}"
对应文档第48-54行的实现,启动后台任务的入口:
- 生成短UUID作为任务ID,方便记忆和使用
- 任务状态初始化为
running - 启动守护线程执行任务,立即返回,不阻塞主线程
python
def _execute(self, task_id: str, command: str):
"""线程执行函数:运行命令,捕获结果,加入通知队列"""
try:
r = subprocess.run(
command, shell=True, cwd=WORKDIR,
capture_output=True, text=True, timeout=300 # 后台任务超时5分钟
)
output = (r.stdout + r.stderr).strip()[:50000]
status = "completed"
except subprocess.TimeoutExpired:
output = "Error: Timeout (300s)"
status = "timeout"
except Exception as e:
output = f"Error: {e}"
status = "error"
# 更新任务状态
self.tasks[task_id]["status"] = status
self.tasks[task_id]["result"] = output or "(no output)"
# 加锁写入通知队列,保证线程安全
with self._lock:
self._notification_queue.append({
"task_id": task_id,
"status": status,
"command": command[:80],
"result": (output or "(no output)")[:500], # 通知只返回前500字符,避免上下文臃肿
})
对应文档第60-69行的实现,后台任务的实际执行逻辑:
- 后台任务超时设为300秒(5分钟),适合大部分长耗时操作
- 支持三种状态:
completed(成功)、timeout(超时)、error(异常) - 加锁写入通知队列,避免多线程并发写的竞态问题
- 通知结果只返回前500字符,完整结果可以通过
check_background工具查询
python
def check(self, task_id: str = None) -> str:
"""查询单个任务状态或列出所有后台任务"""
if task_id:
t = self.tasks.get(task_id)
if not t:
return f"Error: Unknown task {task_id}"
return f"[{t['status']}] {t['command'][:60]}\n{t.get('result') or '(running)'}"
lines = []
for tid, t in self.tasks.items():
lines.append(f"{tid}: [{t['status']}] {t['command'][:60]}")
return "\n".join(lines) if lines else "No background tasks."
支持查询单个任务详情(包括完整结果)或列出所有任务的状态摘要。
python
def drain_notifications(self) -> list:
"""排空通知队列,返回所有未处理的完成通知,原子操作"""
with self._lock:
notifs = list(self._notification_queue)
self._notification_queue.clear()
return notifs
对应文档第75-85行的实现,是通知注入的核心:
- 原子操作:返回所有通知同时清空队列,不会重复消费
- 加锁保证多线程下的安全
3. 工具注册(第163-185行)
新增两个后台任务相关工具:
python
TOOL_HANDLERS = {
# ...其他基础工具不变...
"background_run": lambda **kw: BG.run(kw["command"]),
"check_background": lambda **kw: BG.check(kw.get("task_id")),
}
TOOLS = [
# ...其他基础工具不变...
{"name": "background_run", "description": "Run command in background thread. Returns task_id immediately.",
"input_schema": {"type": "object", "properties": {"command": {"type": "string"}}, "required": ["command"]}},
{"name": "check_background", "description": "Check background task status. Omit task_id to list all.",
"input_schema": {"type": "object", "properties": {"task_id": {"type": "string"}}}},
]
两个工具覆盖了后台任务的核心操作:启动、查询状态。
4. 核心智能体循环修改(第188-215行)
对应文档第75-85行的实现,在原有循环基础上新增后台结果注入逻辑:
python
def agent_loop(messages: list):
while True:
# 新增:每次调用LLM前先排空通知队列,注入后台结果
notifs = BG.drain_notifications()
if notifs and messages:
notif_text = "\n".join(
f"[bg:{n['task_id']}] {n['status']}: {n['result']}" for n in notifs
)
# 把后台结果作为用户消息注入
messages.append({"role": "user", "content": f"<background-results>\n{notif_text}\n</background-results>"})
# 助手确认收到,保持对话结构正确
messages.append({"role": "assistant", "content": "Noted background results."})
# 原有LLM调用和工具执行逻辑不变
response = client.messages.create(
model=MODEL, system=SYSTEM, messages=messages,
tools=TOOLS, max_tokens=8000,
)
# ... 原有逻辑不变 ...
关键设计点:
- 后台结果注入时机选在LLM调用前,不会打断当前正在执行的操作
- 注入格式用
<background-results>标签包裹,方便模型识别是后台任务结果 - 加入助手的确认消息,保持对话的"用户-助手"轮次结构正确,符合LLM的对话格式要求
- 原有逻辑完全不变,只是新增了注入步骤,保持向后兼容
相对 s07 的变更对比(来自官方文档)
| 组件 | s07(之前) | s08(之后) |
|---|---|---|
| 工具数量 | 8个 | 6个(新增2个后台工具,移除了s07的4个任务工具?不对,实际是保留基础工具+2个后台工具,共6个) |
| 执行方式 | 完全阻塞 | 支持阻塞+后台并行执行 |
| 通知机制 | 无 | 线程安全的通知队列,自动注入结果 |
| 并发能力 | 无 | 支持任意数量后台任务并行执行 |
| 资源利用率 | 低(阻塞等待) | 高(等待的时候可以做其他工作) |
使用示例(来自文档)
bash
python agents/后台任务核心实现
s08 >> Run "sleep 5 && echo done" in the background, then create a file while it runs
> background_run: Background task a1b2c3d4 started: sleep 5 && echo done
> write_file: Wrote 20 bytes to test.txt # 不用等sleep完成,立即执行写文件
# 5秒后下一轮循环自动注入结果:
<background-results>
[bg:a1b2c3d4] completed: done
</background-results>
s08 >> Start 3 background tasks: "sleep 2", "sleep 4", "sleep 6". Check their status.
> background_run: Background task wxyz1234 started: sleep 2
> background_run: Background task abcd5678 started: sleep 4
> background_run: Background task hjkl9012 started: sleep 6
> check_background:
wxyz1234: [running] sleep 2
abcd5678: [running] sleep 4
hjkl9012: [running] sleep 6
# 2秒后第一个任务完成,自动注入结果
# 4秒后第二个任务完成,自动注入结果
# 6秒后第三个任务完成,自动注入结果
核心设计思想和收获
s08 建立了异步任务执行的核心范式:
1. 关注点分离
Harness层负责等待慢操作完成,模型只负责决策和思考,把IO等待的时间利用起来做其他事情,效率提升明显。
2. 非侵入式设计
不需要修改原有智能体的任何逻辑,只是在LLM调用前注入结果,原有工作流完全不受影响。
3. 线程安全保障
所有共享资源操作都有加锁,不会出现并发问题,后台任务数量没有限制。
4. 用户体验和效率双重提升
用户不需要等待长耗时命令完成,可以继续给智能体其他指令,模型也可以并行处理多个任务,整体工作效率提升数倍。
这个设计是智能体从"单线程脚本工具"升级到"多任务并行工作系统"的关键特性,也是生产级智能体的必备功能。
本内容参考开源项目 learn-claude-code