ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

learn-claude-code s11 背景任务机制解析:显式 run_in_background 让慢命令异步执行,跨轮次收集 <task_notification> 通知

learn-claude-code s11 背景任务机制解析:显式 run_in_background 让慢命令异步执行,跨轮次收集 <task_notification> 通知 learn-claude-code s11 背景任务机制解析显式 run_in_background 让慢命令异步执行跨轮次收集 task_notification 通知【免费下载链接】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本文围绕 learn-claude-code 课程第 11 章s11展开讲解如何让 Agent Harness 中耗时的 Bash 命令如npm install、完整测试套件转入后台线程执行使 Agent Loop 不被阻塞并在后续轮次以task_notification通知形式把完成结果收集回对话。读完本文你将掌握后台任务判定、BackgroundManager任务生命周期、进程组清理、通知注入这四个环节的完整实现并能直接运行 s11_background_tasks/code.py 验证全流程。问题同步执行让慢命令阻塞整个 Agent Loop读文件、跑git status这类操作通常很快返回同步执行带来的等待几乎不可察觉。但安装依赖、执行完整测试套件或构建项目可能持续几分钟。在命令返回之前Harness 无法处理当前响应中的下一个工具调用也无法进入下一轮模型调用——整个 Agent Loop 停在一次 Bash 调用上。关键洞察是如果后续工作并不依赖这个慢命令就没有必要为它阻塞。例如 Agent 启动完整测试后本可以利用等待时间检查文档或整理其他文件。s11 要解决的就是这个问题让耗时的 Bash 命令在后台执行Agent Loop 继续处理其他工作并在后续轮次收集完成结果。对应的英文原文见 s11_background_tasks/README.md中文教程版为 s11_background_tasks/README.zh.md。方案总览占位 tool_result 跨轮次收集s11 的核心设计是两段式返回慢命令进入后台线程后当前工具调用立即返回一个占位tool_result内含bg_idAgent Loop 得以继续运行后续轮次开始时已完成的结果被收集以通知形式加入对话消息。同步与后台两种模式的对比维度同步s04 内核后台s11慢操作当前工具调用被阻塞后台线程执行Agent Loop等待命令返回收到占位结果后继续运行结果命令结束后返回先返回bg_id后续轮次收集结果判断标准—bash 的run_in_background参数这个设计在 Harness 分层中属于后台层异步执行不阻塞主循环。它解决慢操作不阻塞而定时执行每天早上 9 点跑测试则由下一节提到的 s12 Cron Scheduler 处理。核心机制一should_run_background只用显式请求决定是否后台s11 的第一条设计原则是是否后台执行由工具调用显式决定而非 Harness 猜测。模型通过 bash 工具的run_in_background参数请求后台执行只有参数明确为true且工具是bash时才进入后台路径其余调用一律同步执行。判定函数s11_background_tasks/code.pydef should_run_background(tool_name: str, tool_input: dict) - bool: return ( tool_name bash and tool_input.get(run_in_background) is True )注意is True的写法只有布尔值true命中字符串true、1等都不会触发后台路径。bash 工具的 schema 也相应从仅有command扩展为commandrun_in_backgrounds11_background_tasks/code.py{name: bash, description: Run a shell command., input_schema: {type: object, properties: { command: {type: string}, run_in_background: {type: boolean}}, required: [command]}},系统提示词也配合了这一契约s11_background_tasks/code.pySYSTEM ( fYou are a coding agent at {WORKDIR}. Use tools to solve tasks. Set run_in_background to true only for independent Bash commands. )值得强调的是s11 刻意放弃了按install、build、test等关键词猜测执行模式的旧做法——显式参数把决策权交还给模型Harness 只负责忠实执行选择。测试用例test_background_execution_requires_an_explicit_bash_flagtests/test_background_tasks.py验证了三点不带参数的bash npm install不进后台带run_in_background: True的 bash 进后台write_file即使传了该参数也不会进后台只有 bash 可后台。核心机制二BackgroundManager后台执行与任务生命周期BackgroundManagers11_background_tasks/code.py是后台任务的中枢持有三部分状态任务表tasks、结果表results、就绪队列_ready并用一把threading.Lock保护跨线程访问。start登记任务、启动守护线程、立即返回 bg_idstart()s11_background_tasks/code.py先做两道前置校验——只允许 bash、命令不能为空——然后在锁内自增计数器生成任务 ID 并登记状态def start(self, block) - str: if block.name ! bash: raise ValueError(Only Bash commands can run in the background) command block.input.get(command) if not isinstance(command, str) or not command.strip(): raise ValueError(Bash command cannot be empty) with self._lock: self._counter 1 task_id fbg_{self._counter:04d} # bg_0001, bg_0002, ... self.tasks[task_id] { tool_use_id: block.id, command: command, status: running, } thread threading.Thread( targetself._run, args(task_id, command), daemonTrue, ) try: thread.start() except Exception: with self._lock: self.tasks.pop(task_id, None) raise print(f [background] started {task_id}: {command[:60]}) return task_id从源码结构看有几点值得注意任务 ID 采用bg_%04d格式bg_0001、bg_0002……这就是文档中bg_id的具体形态线程是daemonTrue的守护线程不会阻止进程退出若线程启动失败已登记的任务会被回滚移除并重新抛出异常由上层execute_tool捕获为Error: ...输出避免留下幽灵任务任务记录中保存了tool_use_id原始 tool_use 块 ID但如后文所述收集通知时并不复用它。_run命令执行、状态与结果入队_run()s11_background_tasks/code.py在后台线程中执行命令并落状态def _run(self, task_id: str, command: str): try: output, exit_code _run_bash_process(command) result _format_bash_result(output, exit_code) status completed if exit_code 0 else failed except Exception as error: result fError: {type(error).__name__}: {error} status failed with self._lock: task self.tasks.get(task_id) if task is None: return task[status] status self.results[task_id] result self._ready.append(task_id)失败路径有两种命令以非零退出码结束或 worker 本身抛出异常——两者都标记为failed且错误信息会作为结果存入results最终随通知一起送达模型而不是静默丢失。exit_code 0才算completed超时或启动失败时_run_bash_process返回exit_codeNone因此会被判为failed。进程生命周期独立进程组 120 秒超时 退出清理底层命令执行由_run_bash_process()s11_background_tasks/code.py完成同步与后台共用process subprocess.Popen( command, shellTrue, cwdWORKDIR, stdoutsubprocess.PIPE, stderrsubprocess.PIPE, textTrue, start_new_sessionTrue, # 独立进程组 ) ... stdout, stderr process.communicate(timeout120) output (stdout stderr).strip() return (output[:50000] if output else (no output)), process.returncode具体参数与行为边界start_new_sessionTrueshell 在自己的会话/进程组中启动使运行时可以通过os.killpg一次性停止整个组timeout120命令超过 120 秒未返回即按Error: Timeout (120s)处理对应subprocess.TimeoutExpired分支输出截断stdout 与 stderr 合并、去空白后最多保留 50000 字符空输出归一为(no output)结果格式化_format_bash_result()对非零退出码会前置Error: command exited with status {n}再附输出。进程组清理逻辑在 s11_background_tasks/code.py所有 shell 进程登记在_shell_processes集合中由RLock保护_stop_process_group()先向进程组发SIGTERM50 毫秒后仍存活再补SIGKILLatexit钩子和SIGTERM信号处理器都会调用_stop_all_shell_processes()确保命令完成、超时或 Agent 正常/收到SIGTERM退出时原进程组被停止。必须如实说明其边界与 README 原文一致这只是生命周期清理不是沙箱。另建新 session 的子进程例如命令内部再setsid可以脱离原进程组清理逻辑管不到它们。核心机制三collect / inject跨轮次的 task_notification 通知这是 s11 消息协议中最有特色的部分。collect()s11_background_tasks/code.py在锁内把就绪队列中的任务与结果一次性取出先登记再取避免并发下重复消费然后格式化为通知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 )通知是发给模型的纯文本 XML 结构包含四个字段任务 ID、状态completed/failed、原始命令、结果摘要summary截断到 500 字符。外层封装函数在 s11_background_tasks/code.pydef collect_background_results() - list[str]: return BACKGROUND.collect() def inject_background_results(messages: list) - int: notifications collect_background_results() if not notifications: return 0 blocks [{type: text, text: item} for item in notifications] if messages and messages[-1].get(role) user: content messages[-1].get(content, ) if isinstance(content, list): content.extend(blocks) else: messages[-1][content] [ {type: text, text: str(content)}, *blocks, ] else: messages.append({role: user, content: blocks}) return len(notifications)注入策略有一个细节如果消息列表末尾已是user消息例如刚追加的 tool_result 列表或用户输入通知块直接合并进该消息否则新开一条user消息承载通知。为什么通知不复用原始tool_use_id因为原始的 tool_use 已经用占位tool_result应答过了。API 协议要求一个tool_use恰好对应一个tool_result后台完成事件因此以独立的task_notification文本事件加入对话而不是再挂一个 tool_result 到原 ID 上。这一设计保证协议不变式不被破坏。核心机制四循环集成权限检查在前、占位结果返回Agent Loop 与后台任务的衔接点有两处。第一处是execute_tool()s11_background_tasks/code.py——PreToolUse钩子含权限检查仍在主线程执行然后才决定同步还是后台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这意味着一个设置了run_in_background的危险命令例如命中 deny list 的rm -rf ...会在进入后台之前被权限钩子拦截模型收到的是Permission denied而非bg_id。测试test_background_bash_passes_permission_before_dispatchtests/test_background_tasks.py正是用伪造的client.messages.create跑完整agent_loop断言被拒命令没有产生任何后台任务lesson.background_tasks为空。第二处是agent_loop()s11_background_tasks/code.py——每次 LLM 调用前先收集已完成的后台结果def agent_loop(messages: list): while True: inject_background_results(messages) response client.messages.create( modelMODEL, systemSYSTEM, messagesmessages, toolsTOOLS, max_tokens8000, ) ...两条合起来的行为语义是慢操作先返回带bg_id的占位 tool_result后台任务不会主动唤醒 Agent只有下一次 Agent Loop 运行时inject_background_results()才会收集完成的结果。测试test_completed_result_is_collected_once_before_a_later_llm_calltests/test_background_tasks.py验证了恰好收集一次启动printf ready后台任务并等其完成后跑agent_loop断言第一次 LLM 调用的messages中已包含task_notification、task_idbg_0001/task_id、statuscompleted/status且此后再调collect_background_results()返回空列表——结果只投递一次不会重复。完整走一遍三个轮次的时间线用 npm install 的例子串起全流程与 s11_background_tasks/README.md 的 Putting It Together 一致Turn 1: LLM → bash npm install (run_in_backgroundtrue) → start_background_task → bg_0001 → tool_result: [Background task bg_0001 started] The result will be collected on a later turn. → 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: → 进入循环时 inject_background_results 收集 bg_0001 为 task_notification → LLM 在同一条消息里看到: 配置文件内容 install 完成通知npm install在后台运行的间隙Agent Loop 没有空转Turn 2 正常完成了read_fileTurn 3 调用 LLM 前install 的完成通知被并入消息列表模型一次即可看到配置内容 安装结果。这就是慢操作放后台Agent Loop 继续的完整闭环。s11 相对 s04 内核新增了哪些组件s11 建立在 s04 内核工具实现 hooks 权限检查见 s11_background_tasks/code.py 与 s11_background_tasks/code.py 的注释分节之上增量如下组件s04 内核s11执行模型全部同步慢操作后台线程 通知注入bash schemacommandcommandrun_in_background新函数—should_run_background、start_background_task、collect_background_results、inject_background_results新类型—BackgroundManager通知格式—task_notification不复用 tool_use_id循环行为工具同步执行显式后台执行后续轮次收集完成结果工具数量55bash schema 增加一个参数测试test_s11_keeps_the_s04_kernel_and_adds_one_bash_optiontests/test_background_tasks.py从结构上印证了这张表工具集合仍是bash/read_file/write_file/edit_file/glob五件套bash schema 新增了run_in_backgroundhooks 事件集保持UserPromptSubmit/PreToolUse/PostToolUse/Stop不变。运行与验证环境依赖见 requirements.txtanthropic0.25.0、python-dotenv1.0.0、pyyaml6.0。s11_background_tasks/code.py 通过load_dotenv(overrideTrue)读取.env要求环境变量MODEL_ID必填作为client.messages.create的模型名ANTHROPIC_BASE_URL可选用于指向自建或代理端点设置了该变量时会移除ANTHROPIC_AUTH_TOKEN。启动方式cd learn-claude-code python s11_background_tasks/code.py交互式提示为s11 输入q/exit退出。课程给出的三条验证 prompts11_background_tasks/README.md 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后命令是否被分派到后台是否返回bg_id形如bg_0001后续轮次是否以task_notification格式收集了完成结果控制台侧也会打印[background] started bg_0001: 命令前 60 字符与[background] collected bg_0001: completed/failed两条日志便于与消息侧对照。若要脱离真实模型回归验证可运行 pytest 套件测试通过伪造anthropic模块加载 lesson 模块不发起真实 API 调用python -m pytest tests/test_background_tasks.py -v该文件覆盖四组断言内核保持5 工具 新增 bash 参数、显式标志判定、权限先于后台分派、完成结果在后续 LLM 调用前恰好收集一次另有test_s11_code_is_ascii校验源码为纯 ASCII。在 Harness 分层中的位置与下一步s11 是课程 s01 → … → s09 → s10 →s11→ s12 → s13 → … → s16 → s17 序列中后台层的章节异步执行、不阻塞主循环。它把执行模式从 Harness 的启发式猜测变成模型的显式声明把结果回传从同步等待变成跨轮次通知这两点构成了构建任何 agent harness 时处理长耗时工具调用的可复用模式。它的边界同样清晰不解决定时触发。如果需求是每天早上 9 点跑测试或每 5 分钟检查一次服务器状态那是 s12_cron_scheduler/ 章节的 Cron Scheduler 要解决的问题——给 Agent 装一个闹钟。【免费下载链接】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),仅供参考
返回列表