Learn Claude Code:CodeAgent 的最小架构——工具+权限+Hook扩展钩子

Learn Claude Code:CodeAgent 的最小架构——工具+权限+Hook扩展钩子 一个 Coding Agent 的最小内核循环、工具、权限与 Hooks一个 Coding Agent 的核心循环可能不到几十行但一个可用系统绝不只是while True。这一篇从最底层开始整理我对 Agent Loop、工具系统、权限系统和 Hooks 的理解。一、Agent Loop持续行动的最小闭环普通大模型调用只有一次输入和一次输出用户问题 → 模型回答 → 结束Agent Loop 则会识别模型是否请求调用工具用户问题 → 模型请求 read_file → Harness 读取文件 → 文件内容返回模型 → 模型请求 edit_file → Harness 修改文件 → 修改结果返回模型 → 模型给出最终回答伪代码如下defagent_loop(messages):whileTrue:responsellm(messagesmessages,toolsTOOLS)messages.append(to_assistant_message(response))tool_callsextract_tool_calls(response)ifnottool_calls:returnextract_text(response)tool_results[]forcallintool_calls:outputexecute_tool(call)tool_results.append(make_tool_result(call.id,output))messages.append({role:user,content:tool_results,})这里最关键的不是循环语法而是协议闭环tool_use_id —— tool_result.tool_use_id每个工具请求必须有对应结果否则模型无法知道某次调用是否完成。生产环境中是否继续循环最好依据响应里是否真的出现tool_use块而不是只相信某个停止原因字段因为流式响应的状态可能尚未完全更新。二、工具系统工具本质上是带 Schema 的函数工具通常包含两部分。第一部分提供给模型{name:read_file,description:Read a UTF-8 text file from the workspace.,input_schema:{type:object,properties:{path:{type:string}},required:[path]}}第二部分提供给 Harnessdefread_file(path:str)-str:...二者通过分发表关联TOOL_HANDLERS{read_file:read_file,write_file:write_file,edit_file:edit_file,bash:run_bash,}执行时不需要写大量if-elsehandlerTOOL_HANDLERS[call.name]outputhandler(**call.input)这种设计的关键价值是新增工具不需要修改 Agent Loop。新增一个工具只需添加工具 Schema注册处理函数。循环、权限、日志和错误恢复逻辑都可以复用。三、工具描述会直接影响 Agent 稳定性工具不仅要能执行还必须让模型容易理解。不好的工具描述process_file处理文件模型不知道它是读取、修改、删除还是转换。更好的描述edit_file在指定 UTF-8 文本文件中将唯一出现的 old_text 替换为 new_text若 old_text 不存在或出现多次则返回错误。一个工具应尽量具备单一职责清晰输入明确副作用可预测输出可验证错误尽可能少的隐式状态。工具太粗模型难以控制工具太碎调用次数和上下文成本又会迅速增加。工具粒度需要围绕任务场景权衡。四、多工具调用不能简单地全部并发模型可能在一次响应中请求多个工具read_file(a.py) read_file(b.py) write_file(result.md)前两个读取可以并发但写入必须等待读取结果和模型的下一步决策。更一般地说只读工具通常可以并发修改文件、数据库和外部状态的工具应谨慎串行相互有依赖的工具必须保留顺序。一种较合理的做法是按原始顺序扫描调用 → 将连续的并发安全工具组成 batch → batch 内并发 → batch 之间串行而不是粗暴地把所有调用扔进线程池。同时每个工具在执行前还应经过验证管线Schema 验证 → 工具自己的参数校验 → PreToolUse Hooks → 权限检查 → 真正执行五、权限系统安全不能靠模型自觉文件工具可以限制在工作目录内但bash天然拥有更大的破坏能力。模型可能因为误解任务而执行rm-rf...sudo...gitreset--hard安全设计应在工具执行前完成而不是在执行后补救。可以将权限判断理解为三层闸门硬拒绝 → 规则判断 → 用户审批硬拒绝无论上下文如何都不能执行的行为例如明显的系统破坏命令。规则判断根据工具、路径和参数判断风险例如写入工作区外删除文件修改受保护配置调用带破坏性的 MCP 工具。用户审批规则判断认为需要确认时暂停当前工具调用让用户明确允许或拒绝。生产系统的权限结果通常不只有allow/deny还可能包含allow 直接允许 deny 直接拒绝 ask 请求人工确认 passthrough 当前规则不处理交给下一层这里的重要原则是用户扩展的 Hook 不应绕过系统级 deny 规则。即使某个 Hook 总是返回允许系统的硬拒绝和安全规则仍应继续生效。六、Hooks扩展行为但不污染核心循环如果每增加一个功能都直接修改循环log_tool_call()check_permission()notify_user()auto_git_add()collect_metrics()循环很快会变得无法维护。Hooks 的作用是提供稳定扩展点UserPromptSubmit PreToolUse PostToolUse Stop实现可以非常简单HOOKS{UserPromptSubmit:[],PreToolUse:[],PostToolUse:[],Stop:[],}defregister_hook(event,callback):HOOKS[event].append(callback)deftrigger_hooks(event,*args):forcallbackinHOOKS[event]:resultcallback(*args)ifresultisnotNone:returnresult常见用途如下Hook典型用途UserPromptSubmit输入审计、补充上下文PreToolUse权限检查、参数修正、日志PostToolUse结果检查、指标统计Stop清理资源、生成会话摘要Hooks 的设计价值不在于“回调函数”本身而在于让核心循环保持稳定。七、必须防止 Hook 自己制造死循环例如 Stop Hook 认为任务还没完成向模型追加一条消息要求继续。如果下一轮仍触发相同 Stop Hook就可能无限续跑。因此需要显式状态stop_hook_active true当系统已经因为 Stop Hook 重入循环时后续 Stop Hook 应识别该状态避免再次触发同一种续跑逻辑。任何可以“阻止停止”或“重新注入任务”的扩展点都应配置最大触发次数重入标志超时可审计日志。八、总结一个最小 Coding Agent 可以由一个循环和一个 Bash 工具构成但真正可用的内核至少需要Agent Loop Tool Registry Input Validation Concurrency Policy Permission Pipeline Hook System其中最重要的设计原则是循环保持简单稳定能力通过工具扩展安全通过确定性代码实现横切逻辑通过 Hooks 挂载任何自动续跑机制都必须有边界。理解这一层后后续的记忆、任务、多 Agent 和 MCP 都只是继续挂载在同一个循环周围。