
如何把慢速 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 .envrequirements.txt 里有三个依赖anthropic0.25.0、python-dotenv1.0.0、pyyaml6.0。然后按 .env.example 修改.env# API Key (required) ANTHROPIC_API_KEYsk-ant-xxx # Model ID (required) MODEL_IDclaude-sonnet-4-6 # Base URL (optional, for Anthropic-compatible providers) # ANTHROPIC_BASE_URLhttps://api.anthropic.comcode.py 中有两个细节它调用load_dotenv(overrideTrue)加载环境变量然后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” 给出的三条提示词Run pip list in the background and find all Python files in this directoryRun npm install (use run_in_background) and while waiting, read package.jsonRun a short sleep in the background, then list all Markdown files这三条提示词都是“一条慢命令 一条快命令”的结构可以观察同一个会话里主循环能否在慢命令运行期间继续干活。run_in_background 如何把命令派进后台线程s11 构建在前一章的同步内核之上README 对比表中称 s04 Kernelcode.py 里工具实现、hooks 与权限检查也都标注“From s04”工具池不变仍是bash、read_file、write_file、edit_file、glob五个工具只在 bash schema 上加了一个参数并新增后台执行部分。按四步看。1. schema 声明与显式判定bash 工具的 schema 增加了run_in_backgroundcode.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 不再根据install、build、test这类关键词猜测——由工具调用显式选择执行模式。系统提示词同时约束模型“Set run_in_background to true only for independent Bash commands.”2. BackgroundManager立即返回 bg_id守护线程执行命令BackgroundManager.start()先登记任务并立即返回任务 IDbg_0001、bg_0002……四位自增再启动一个守护线程执行命令code.py 第 314-342 行节选with self._lock: self._counter 1 task_id fbg_{self._counter:04d} self.tasks[task_id] { tool_use_id: block.id, command: command, status: running, } thread threading.Thread( targetself._run, args(task_id, command), daemonTrue, )start()对非 bash 块抛ValueError(Only Bash commands can run in the background)空命令也会抛ValueError线程启动失败时会回滚刚登记的任务。后台线程里的_run()执行完命令后退出码为 0 标记completed非零退出码或执行异常标记failed然后把任务 ID 放入就绪队列。命令由_run_bash_process()运行subprocess.Popen(..., shellTrue, start_new_sessionTrue)等待超时 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 fError: {error} else: output call_tool(block) trigger_hooks(PostToolUse, block, output) return output所以后台命令返回给模型的tool_result是这句占位话而不是命令输出。模型拿到它就能立刻发起下一个工具调用主循环不会被慢命令阻塞。4. 结果收集下一轮开始时注入 task_notificationAgent loop 在每次 LLM 调用之前收集已完成的结果code.py 第 448-457 行def agent_loop(messages: list): while True: inject_background_results(messages) response client.messages.create( modelMODEL, systemSYSTEM, messagesmessages, toolsTOOLS, max_tokens8000, )collect_background_results()即BackgroundManager.collect()从就绪队列取出已完成任务组装成固定格式的通知第 371-380 行notifications.append( ftask_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_backgroundtrue) → start_background_task → bg_0001 → tool_result: [Background task bg_0001 started]... → LLM: OK, Ill 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 messagenpm install在后台跑的同时循环被用来读了package.json第 3 轮时安装结果以通知形式出现。汇总一下 s11 相对同步内核的全部改动README “What s11 Adds” 表项变化bash schemacommandrun_in_background新增函数should_run_background、start_background_task、collect_background_results、inject_background_results新增类型BackgroundManager通知格式task_notification不沿用tool_use_id循环行为显式后台执行已完成结果在后续轮次收集工具数量仍是 5 个不变验证后台派发与结果收集README 的 “What to observe” 给出了判断标准。用上面的提示词运行时依次确认三点派发模型显式设置run_in_background后命令是否被派进后台终端会打印[background] started bg_0001: 命令前 60 字符工具结果里是否有bg_id主循环继续派发后模型是否在同一会话里发起了另一个工具调用例如派完pip list后立刻去globPython 文件收集后续某轮 LLM 调用开始时终端打印[background] collected bg_0001: completed非零退出则是failed模型收到的消息中是否出现对应的task_notification不想消耗 API 额度时可以用仓库自带的测试在代码层面核对。tests/test_background_tasks.py mock 掉了anthropic模块并注入伪造响应不需要 API Keypytest tests/test_background_tasks.py注意pytest不在 requirements.txt 里需要先自行安装。测试的断言正好对应机制的几个关键点test_background_execution_requires_an_explicit_bash_flagbash 不带run_in_background参数不走后台带参数的非 bash 工具如write_file同样不走后台test_background_bash_passes_permission_before_dispatch命令命中 deny list如含rm -rf时权限 hook 在派发前拦截不产生任何后台任务tool_result为Permission deniedtest_completed_result_is_collected_once_before_a_later_llm_call已完成的后台命令在下一次 LLM 调用的第一条消息中注入消息包含task_notification、task_idbg_XXXX/task_idXXXX为四位自增编号、statuscompleted/status和命令输出收集后再调collect_background_results()返回空列表——结果只注入一次。使用这套机制要知道的边界只有 bash 能后台start()拒绝非 bash 块和空命令抛ValueError收集时机结果只在下一轮循环开始LLM 调用前被收集循环不继续时结果就一直留在队列里失败处理非零退出码与后台线程异常都标记failed但结果仍会被收集并以通知注入120 秒等待超时返回Error: Timeout (120s)同样标记failed长度限制命令输出截断到 50000 字符通知里的summary只取结果前 500 字符进程清理不是沙箱runtime 只停止命令的原始进程组新建了会话的进程可以留在组外是否后台由模型决定harness 不做关键词判断系统提示词要求模型只把run_in_background用于相互独立的命令。下一步从后台执行到定时触发README 的 “Whats 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),仅供参考