【免费下载链接】mini-coding-agent
Minimal and readable coding agent harness implementation in Python to explain the core components of coding agents.
在mini-coding-agent的六大核心组件中,第六个组件是"委派与有界子Agent"(Delegation And Bounded Subagents)。它通过一个名为delegate的工具,让主 Agent 把耗时的调查类子任务交给一个只读、有步数上限的子 Agent 去执行,既扩展了主 Agent 的探索能力,又严格限制了子 Agent 的权限边界。本文带你读懂 mini_coding_agent.py 中tool_delegate的完整设计思路。
为什么 Coding Agent 需要"委派"能力?
想象你让 Agent 排查一个跨文件的问题:"为什么这个函数在某个输入下返回异常?"这类任务可能需要:
- 读 5~10 个文件
- 多轮搜索关键字
- 逐步缩小排查范围
如果全部由主 Agent 完成,这些工具调用和输出会全部挤进主会话的历史,很快把上下文撑爆。委派(delegation)的思路是:把这些"跑腿式"的调查工作外包给一个临时子 Agent,子 Agent 独立跑完只返回一份浓缩的结论,主 Agent 的上下文只多出一条delegate_result。
这就是"有界"(bounded)的含义:子 Agent 不是全能的,它被锁在深度、权限、步数三道栅栏之内。
delegate 工具如何被"条件注册"
delegate工具并非无条件存在。在 build_tools 的末尾有这样一段判断:
if self.depth < self.max_depth: tools["delegate"] = { "schema": {"task": "str", "max_steps": "int=3"}, "risky": False, "description": "Ask a bounded read-only child agent to investigate.", "run": self.tool_delegate, }mini_coding_agent.py 的这段逻辑很巧妙:只有当当前 Agent 的depth小于max_depth时,才把 delegate 注入工具表。也就是说:
- 顶层 Agent 构造时默认 depth=0, max_depth=1,因此它拥有 delegate 工具;
- 子 Agent 构造时
depth=self.depth + 1恰好等于max_depth,它根本看不到 delegate 这个工具,从源头上杜绝了"子 Agent 再套娃"的无限递归委派。
工具本身只有两个参数:task(子任务描述,必填)和max_steps(子 Agent 最多可用的步数,默认 3)。risky标记为False,因为委派本身不直接修改任何东西——真正的安全约束写在下一节的栅栏里。
tool_delegate 的三道安全栅栏
核心实现位于 tool_delegate:
def tool_delegate(self, args): if self.depth >= self.max_depth: raise ValueError("delegate depth exceeded") task = str(args.get("task", "")).strip() if not task: raise ValueError("task must not be empty") child = MiniAgent( model_client=self.model_client, workspace=self.workspace, session_store=self.session_store, approval_policy="never", max_steps=int(args.get("max_steps", 3)), max_new_tokens=self.max_new_tokens, depth=self.depth + 1, max_depth=self.max_depth, read_only=True, ) child.session["memory"]["task"] = task child.session["memory"]["notes"] = [clip(self.history_text(), 300)] return "delegate_result:\n" + child.ask(task)虽然代码不长,但信息密度极高。逐条拆解:
栅栏一:深度检查(depth / max_depth)
入口处的if self.depth >= self.max_depth是最后一道保险。虽然条件注册已经保证只有depth < max_depth的 Agent 才拥有该工具,但 validate_tool 中对delegate的校验也重复了同一检查,并在越界时抛出"delegate depth exceeded"。注册层 + 校验层双重检查,任何路径都无法突破深度上限。
栅栏二:read_only=True(只读锁)
子 Agent 被显式标记为read_only=True。它的效果体现在 approve 方法的第一行:
def approve(self, name, args): if self.read_only: return Falseread_only会让所有 risky 工具的审批直接返回 False。由于 run_tool 在高风险工具执行前强制走approve关卡,子 Agent 即便"想"调用write_file、patch_file或run_shell,也会在审批环节被拒绝。于是子 Agent 在物理上只能使用list_files、read_file、search这类安全的只读工具——这正是标题中"只读受限"四个字的由来。
栅栏三:approval_policy="never"(审批策略兜底)
read_only已经拒绝了 risky 工具,approval_policy="never"是双保险:即使未来某次修改遗漏了read_only分支,never策略也会让 approve 对所有审批返回False。更值得注意的是,子 Agent 的approval_policy被硬编码为"never",不会继承父 Agent 的策略——即使你在命令行用--approval auto启动了主 Agent(自动批准所有危险操作),委派出去的子 Agent 依然是只读的。父级信任模型,不代表信任模型派生出的子代理,这一细节体现了权限设计上"最小授权"的原则。
步数预算:max_steps 控制子 Agent 的"体力"
子 Agent 的max_steps直接取自调用参数(默认 3),并沿depth=self.depth + 1向下传递。回顾 ask 的主循环,while tool_steps < self.max_steps意味着子 Agent 最多只能发起 3 次工具调用就必须给出最终答案,超时会返回Stopped after reaching the step limit without a final answer.。
这一步数预算的作用:
- 限制 token 消耗:子 Agent 每次模型调用都独立计费,3 步是"够用但不浪费"的默认值;
- 防止跑偏:调查任务如果 3 步还没结果,说明子任务定义可能有问题,早停比死磕更划算;
- 父级可调:主 Agent 委派时可以根据任务复杂度主动传
max_steps: 2或更大的值,把预算分配权交给最需要判断的主体。
上下文交接:子 Agent 带着什么信息出发?
子 Agent 是全新构造的MiniAgent实例,历史是空的。如果什么都不给,它就像蒙着眼睛进仓库。tool_delegate用两行代码完成"交接":
child.session["memory"]["task"] = task child.session["memory"]["notes"] = [clip(self.history_text(), 300)]memory["task"] = task:把委派任务写入子 Agent 的工作记忆,后续 memory_text 会将其渲染进提示词,让子 Agent 始终知道"我为什么在这里";memory["notes"] = [clip(self.history_text(), 300)]:把父 Agent **当前历史的压缩摘要(裁剪到 300 字符)**塞进子 Agent 的 notes。这样子的子 Agent 虽然看不到父级的完整对话,但能大致了解排查背景,避免从零开始乱撞。
注意这里刻意只传 300 字符:委派的本意就是隔离上下文,如果父级的完整历史灌进去,委派就失去了节省上下文的意义。这是"足够信息"与"上下文隔离"之间的精妙折中。
另外子 Agent 复用了父级的model_client、workspace和session_store,意味着它操作的是同一个工作区快照、调用同一个 Ollama 模型,并且它的会话 JSON 也会照常落盘到 .mini-coding-agent/sessions/,事后可以完整追溯子 Agent 干过什么。
结果回传:一条 delegate_result 就够了
子 Agent 跑完后,tool_delegate只返回一行结构化结果:
delegate_result: <子 Agent 的最终答案>这个返回值作为普通工具输出记录进父 Agent 的历史,父 Agent 在下一轮模型调用中读取它并整合进最终答案。由于子 Agent 的输出本身被 ask 约束为简洁结论、工具输出又被clip截断(默认 MAX_TOOL_OUTPUT = 4000),父级的上下文增量被控制得很小。
官方测试 test_delegate_uses_child_agent 完整验证了这条链路:父 Agent 先发出 delegate 工具调用,子 Agent(由FakeModelClient喂给的答案)返回结果,父 Agent 将结果写进delegate_result工具记录,最终输出整合后的答案。
一张表看懂 delegate 的约束全景
| 约束维度 | 机制 | 代码位置 |
|---|---|---|
| 递归深度 | depth < max_depth才注册工具;构造时depth+1 | L321-L327、L860 |
| 越界校验 | 双重检查,越界抛delegate depth exceeded | L594-L600、L848-L849 |
| 只读权限 | read_only=True使approve恒返回 False | L862、L602-L604 |
| 审批兜底 | approval_policy="never"硬编码,不继承父级 | L857、L607-L608 |
| 步数预算 | max_steps默认 3,超步即停 | L858、L455 |
| 上下文隔离 | 仅传 300 字符历史摘要 + 任务文本 | L864-L865 |
小结:有界委派的三个设计要点
读完tool_delegate,可以提炼出 mini-coding-agent 委派设计的三条通用原则,对你自己设计多 Agent 系统同样有参考价值:
- 能力从结构上剥夺,而不是从提示词上请求。子 Agent 不是"被要求不要写文件",而是通过
read_only+never策略在机制上无法写文件。依赖模型自觉的安全设计都是脆弱的。 - 递归必须有界。
depth/max_depth的组合让"能否再委派"成为构造期就确定的事实,子 Agent 的工具表里压根没有 delegate,比运行时拒绝更干净。 - 上下文按需交接。只传任务文本 + 300 字符摘要,既给了子 Agent 足够的背景,又保住了委派最核心的价值——把冗长的调查过程关在子上下文里,只让结论上浮。
至此,mini-coding-agent 六大核心组件已全部解析完毕:实时仓库上下文、提示词结构与缓存复用、结构化工具与权限、上下文缩减与输出管理、转录记忆与断点续传、有界子 Agent 委派。整个项目仅一个 mini_coding_agent.py(约千行),想动手复现一个最小 coding agent 的同学,可以从 EXAMPLE.md 的完整交互示例开始上手。
【免费下载链接】mini-coding-agent
Minimal and readable coding agent harness implementation in Python to explain the core components of coding agents.
相关推荐
Caveman 探索与委派机制详解:Explorer、Cavecrew 子代理与 Delegate 工具的上下文隔离设计
Caveman 探索与委派机制详解:Explorer、Cavecrew 子代理与 Delegate 工具的上下文隔离设计 在 Caveman(一个通过压缩 to
人工智能AI 应用AI 技能AI 插件LLMOps开发工具AI-Coding-Guide-Zh Subagent教程:子代理协作、任务委派与Agent团队
AI Coding Guide Zh Subagent教程:子代理协作、任务委派与Agent团队 在 AI Coding Guide Zh 这套中文教程中, S
文档教程Claude Security 插件源码解读:explore 只读代码探查 Agent 的设计与实战
Claude Security 插件源码解读:explore 只读代码探查 Agent 的设计与实战 claude plugins official 仓库中的
AI 插件开发工具插件系统
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考