news 2026/9/9 22:16:14

如何把慢速 bash 命令丢进后台线程而不阻塞主循环:learn-claude-code s11 的 run_in_background 与结果收集

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
如何把慢速 bash 命令丢进后台线程而不阻塞主循环:learn-claude-code s11 的 run_in_background 与结果收集

如何把慢速 bash 命令丢进后台线程而不阻塞主循环:learn-claude-code s11 的 run_in_background 与结果收集

【免费下载链接】learn-claude-codeBash is all you need - A nano claude code–like 「agent harness」, built from 0 to 1项目地址: https://gitcode.com/GitHub_Trending/an/learn-claude-code

在 Agent Loop 里,一条慢速 bash 命令会卡住整个 harness:安装依赖、跑完整测试套件、构建项目动辄几分钟,在命令返回之前,harness 既无法处理当前响应里的下一个工具调用,也无法开始下一轮模型调用。learn-claude-code 的 s11 章节(s11_background_tasks)解决这个问题:模型在 bash 调用上显式声明run_in_background: true,命令交给后台线程执行,当前工具调用立刻返回一个带bg_id的占位结果,主循环继续处理其他工具调用;在后续某一轮开始时,已完成的结果被收集为<task_notification>消息注入对话。本文带你跑通 s11,讲清这条机制的完整路径,并给出验证派发与收集是否生效的方法。

运行 s11 并发出触发后台的提示词

准备环境

s11 的入口是 s11_background_tasks/code.py,它是可独立运行的章节实现,依赖一个 Anthropic 兼容的模型 API。按根 README.md 的 Quick Start 准备:

pip install -r requirements.txt cp .env.example .env

requirements.txt 里有三个依赖:anthropic>=0.25.0python-dotenv>=1.0.0pyyaml>=6.0。然后按 .env.example 修改.env

# API Key (required) ANTHROPIC_API_KEY=sk-ant-xxx # Model ID (required) MODEL_ID=claude-sonnet-4-6 # Base URL (optional, for Anthropic-compatible providers) # ANTHROPIC_BASE_URL=https://api.anthropic.com

code.py 中有两个细节:它调用load_dotenv(override=True)加载环境变量,然后MODEL = os.environ["MODEL_ID"]——MODEL_ID缺失会直接抛KeyError无法启动;如果设置了ANTHROPIC_BASE_URL,代码会清掉ANTHROPIC_AUTH_TOKEN避免干扰。.env.example还列出了 MiniMax、GLM、Kimi、DeepSeek 等兼容供应商的 Base URL 与模型名,可按需替换。

启动 REPL 并发送测试提示词

python s11_background_tasks/code.py

s11 >>提示符下输入问题回车发送,输入q退出。发送 章节 README “Try It” 给出的三条提示词:

  1. Run pip list in the background and find all Python files in this directory
  2. Run npm install (use run_in_background) and while waiting, read package.json
  3. Run a short sleep in the background, then list all Markdown files

这三条提示词都是“一条慢命令 + 一条快命令”的结构,可以观察同一个会话里主循环能否在慢命令运行期间继续干活。

run_in_background 如何把命令派进后台线程

s11 构建在前一章的同步内核之上(README 对比表中称 s04 Kernel,code.py 里工具实现、hooks 与权限检查也都标注“From s04”),工具池不变,仍是bashread_filewrite_fileedit_fileglob五个工具,只在 bash schema 上加了一个参数,并新增后台执行部分。按四步看。

1. schema 声明与显式判定

bash 工具的 schema 增加了run_in_background(code.py 第 170-175 行):

{"name": "bash", "description": "Run a shell command.", "input_schema": {"type": "object", "properties": { "command": {"type": "string"}, "run_in_background": {"type": "boolean"}}, "required": ["command"]}}

是否走后台完全由这个参数决定:

def should_run_background(tool_name: str, tool_input: dict) -> bool: return ( tool_name == "bash" and tool_input.get("run_in_background") is True )

只有 bash 调用且参数显式为True才进入后台路径,其余调用照旧同步执行。README 强调 harness 不再根据installbuildtest这类关键词猜测——由工具调用显式选择执行模式。系统提示词同时约束模型:“Set run_in_background to true only for independent Bash commands.”

2. BackgroundManager:立即返回 bg_id,守护线程执行命令

BackgroundManager.start()先登记任务并立即返回任务 ID(bg_0001bg_0002……四位自增),再启动一个守护线程执行命令(code.py 第 314-342 行节选):

with self._lock: self._counter += 1 task_id = f"bg_{self._counter:04d}" self.tasks[task_id] = { "tool_use_id": block.id, "command": command, "status": "running", } thread = threading.Thread( target=self._run, args=(task_id, command), daemon=True, )

start()对非 bash 块抛ValueError("Only Bash commands can run in the background"),空命令也会抛ValueError;线程启动失败时会回滚刚登记的任务。

后台线程里的_run()执行完命令后:退出码为 0 标记completed,非零退出码或执行异常标记failed,然后把任务 ID 放入就绪队列。命令由_run_bash_process()运行:subprocess.Popen(..., shell=True, start_new_session=True),等待超时 120 秒,超时返回Error: Timeout (120s);输出本身截断到 50000 字符。README 说明 shell 在自己的进程组里启动,命令结束、超时或 Agent 通过正常退出 /SIGTERM路径退出时,runtime 会停止那个原始进程组——这是生命周期清理而不是沙箱:创建了另一个会话的进程可以留在组外。

3. 占位结果让主循环继续

s11 版的execute_tool()先在主线程跑PreToolUsehooks(含权限检查),检查通过后才决定同步还是后台(code.py 第 425-443 行):

def execute_tool(block) -> str: blocked = trigger_hooks("PreToolUse", block) if blocked is not None: return str(blocked) if should_run_background(block.name, block.input): try: task_id = start_background_task(block) output = ( f"[Background task {task_id} started] " "The result will be collected on a later turn." ) except Exception as error: output = f"Error: {error}" else: output = call_tool(block) trigger_hooks("PostToolUse", block, output) return output

所以后台命令返回给模型的tool_result是这句占位话,而不是命令输出。模型拿到它就能立刻发起下一个工具调用,主循环不会被慢命令阻塞。

4. 结果收集:下一轮开始时注入 task_notification

Agent loop 在每次 LLM 调用之前收集已完成的结果(code.py 第 448-457 行):

def agent_loop(messages: list): while True: inject_background_results(messages) response = client.messages.create( model=MODEL, system=SYSTEM, messages=messages, tools=TOOLS, max_tokens=8000, )

collect_background_results()(即BackgroundManager.collect())从就绪队列取出已完成任务,组装成固定格式的通知(第 371-380 行):

notifications.append( f"<task_notification>\n" f" <task_id>{task_id}</task_id>\n" f" <status>{task['status']}</status>\n" f" <command>{task['command']}</command>\n" f" <summary>{result[:500]}</summary>\n" f"</task_notification>" )

注意通知不沿用原调用的tool_use_id:原工具调用已经被占位tool_result应答过了,完成结果作为独立事件注入,从而保持“一个tool_use恰好对应一个tool_result”。inject_background_results()把通知追加到最后一条 user 消息,若最后一条不是 user 消息则新建一条 user 消息。

已完成的任务不会自行唤醒 Agent,它只是待在队列里等下一轮循环来收。README 给过一个三轮示例(文档示例):

Turn 1: LLM → bash "npm install" (run_in_background=true) → start_background_task → bg_0001 → tool_result: "[Background task bg_0001 started]..." → LLM: "OK, I'll check later. Let me also read the config." Turn 2: LLM → read_file "package.json" (fast, sync) → tool_result: file content Turn 3: → collect bg_0001 as <task_notification> → LLM sees: config file + install notification in one message

npm install在后台跑的同时,循环被用来读了package.json,第 3 轮时安装结果以通知形式出现。

汇总一下 s11 相对同步内核的全部改动(README “What s11 Adds” 表):

变化
bash schemacommand+run_in_background
新增函数should_run_backgroundstart_background_taskcollect_background_resultsinject_background_results
新增类型BackgroundManager
通知格式<task_notification>(不沿用tool_use_id
循环行为显式后台执行,已完成结果在后续轮次收集
工具数量仍是 5 个,不变

验证后台派发与结果收集

README 的 “What to observe” 给出了判断标准。用上面的提示词运行时,依次确认三点:

  1. 派发:模型显式设置run_in_background后,命令是否被派进后台?终端会打印[background] started bg_0001: <命令前 60 字符>,工具结果里是否有bg_id
  2. 主循环继续:派发后模型是否在同一会话里发起了另一个工具调用(例如派完pip list后立刻去globPython 文件)?
  3. 收集:后续某轮 LLM 调用开始时,终端打印[background] collected bg_0001: completed(非零退出则是failed),模型收到的消息中是否出现对应的<task_notification>

不想消耗 API 额度时,可以用仓库自带的测试在代码层面核对。tests/test_background_tasks.py mock 掉了anthropic模块并注入伪造响应,不需要 API Key:

pytest tests/test_background_tasks.py

注意pytest不在 requirements.txt 里,需要先自行安装。测试的断言正好对应机制的几个关键点:

  • test_background_execution_requires_an_explicit_bash_flag:bash 不带run_in_background参数不走后台;带参数的非 bash 工具(如write_file)同样不走后台;
  • test_background_bash_passes_permission_before_dispatch:命令命中 deny list(如含rm -rf)时,权限 hook 在派发前拦截,不产生任何后台任务,tool_resultPermission denied
  • test_completed_result_is_collected_once_before_a_later_llm_call:已完成的后台命令在下一次 LLM 调用的第一条消息中注入,消息包含<task_notification><task_id>bg_XXXX</task_id>XXXX为四位自增编号)、<status>completed</status>和命令输出;收集后再调collect_background_results()返回空列表——结果只注入一次。

使用这套机制要知道的边界

  • 只有 bash 能后台start()拒绝非 bash 块和空命令,抛ValueError
  • 收集时机:结果只在下一轮循环开始(LLM 调用前)被收集,循环不继续时结果就一直留在队列里;
  • 失败处理:非零退出码与后台线程异常都标记failed,但结果仍会被收集并以通知注入;120 秒等待超时返回Error: Timeout (120s),同样标记failed
  • 长度限制:命令输出截断到 50000 字符,通知里的<summary>只取结果前 500 字符;
  • 进程清理不是沙箱:runtime 只停止命令的原始进程组,新建了会话的进程可以留在组外;
  • 是否后台由模型决定:harness 不做关键词判断,系统提示词要求模型只把run_in_background用于相互独立的命令。

下一步:从后台执行到定时触发

README 的 “What's Next” 指出了后续章节:后台任务解决了“慢操作不阻塞”的问题,但如果想要“每天早上 9 点跑测试”“每 5 分钟检查一次服务器状态”这类按时间表触发的任务,由 s12 Cron Scheduler 一章处理。

【免费下载链接】learn-claude-codeBash is all you need - A nano claude code–like 「agent harness」, built from 0 to 1项目地址: https://gitcode.com/GitHub_Trending/an/learn-claude-code

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/9 22:15:55

STM32L4内部FLASH读写实战:从擦除对齐到掉电安全

简介&#xff1a;面向STM32L4系列嵌入式开发者&#xff0c;这份代码包提供基于LL库的寄存器级内部FLASH读写实现&#xff0c;已在STM32L452RET6芯片上调通。作者将FLASH解锁、擦除、写入、读取等操作封装在独立的C源文件与头文件中&#xff0c;底层均为寄存器配置&#xff0c;因…

作者头像 李华
网站建设 2026/9/9 22:15:36

STM32 GPIO扩展实战:74HC595级联驱动原理与代码详解

简介&#xff1a;STM32驱动74HC595的可级联函数包&#xff0c;面向嵌入式系统开发人员&#xff0c;特别适合数码管动态扫描、LED点阵显示和多通道并行输出等应用&#xff0c;能够有效节省微控制器引脚资源。代码基于Keil MDK工程编写&#xff0c;使用普通GPIO模拟SPI时序&#…

作者头像 李华
网站建设 2026/9/9 22:15:11

libmodbus 3.1.6在QT上位机中的集成与Modbus通信实战

简介&#xff1a;libmodbus-3.1.6-master 是一套面向嵌入式开发者的 Modbus 通信协议库源码包&#xff0c;重点解决 libmodbus 在 ARM A7 架构 imx6ull 平台上的交叉编译与定制集成问题。资源共 158 个文件&#xff0c;以 C 源文件、头文件、configure 配置脚本和 Makefile 工程…

作者头像 李华
网站建设 2026/9/9 22:13:08

localStorage 与 sessionStorage 怎么存 JSON 数据?存储 API 完整用法

localStorage 与 sessionStorage 怎么存 JSON 数据&#xff1f;存储 API 完整用法 【免费下载链接】33-js-concepts &#x1f4dc; 33 JavaScript concepts every developer should know. 项目地址: https://gitcode.com/GitHub_Trending/33/33-js-concepts 在浏览器应用…

作者头像 李华
网站建设 2026/9/9 22:13:05

2025年TDD面试实战:Spring Boot单元测试与Mockito最佳实践

“TDD 都写了三四年了&#xff0c;面试官一问还能把我问住&#xff1f;”这是我去年帮一个朋友做模拟面试时&#xff0c;他亲口说的话。他并不是不会写测试&#xff0c;而是在面试高压下&#xff0c;把“写测试”和“TDD”混为一谈&#xff0c;一被追问“先写测试还是先写实现”…

作者头像 李华
网站建设 2026/9/9 22:12:44

seaborn进阶:如何用Python轻松绘制统计图形?

上礼拜有个刚转行做数据分析的朋友问我&#xff1a;matplotlib我都还没学明白&#xff0c;怎么到处都在说seaborn&#xff1f;我给他打了个比方&#xff1a;matplotlib是原木&#xff0c;什么都能做&#xff0c;但所有事都得自己动手&#xff1b;seaborn是给你切好拼好的板材&a…

作者头像 李华