Agent Engineering 101|13|Extensions
Extensions 是 Agent Harness 的原语层。它们用 hook 改写上下文、模型请求、工具输入和工具结果,而不是把所有能力硬编码进 core。
核心结论
配套代码仓库: github.com/llm-101/mini-agent-harness
读完本文,你应该能回答
- Extension 为什么应该是 primitive,而不是产品功能列表?
- hook 可以介入上下文、模型请求、工具输入和工具结果的哪些位置?
- 什么逻辑应该写进 core,什么逻辑应该变成 extension?
- 扩展机制如何避免把 Harness 重新变成大泥球?
本篇在系列中的位置
- 上一篇:12 Prompt Templates 讨论了用户可控的工作流入口。
- 本篇:本文把可插拔能力抽象为 hook、resource、tool 等 primitive。
- 下一篇:14 Policy Extensions 会展示权限和治理如何作为扩展介入。
贯穿例子
本系列会反复使用同一个任务来连接各章:
用户说:“帮我修复这个 repo 里的 failing tests。”
在这个任务里,Extensions 可以在构造上下文前注入项目说明,在模型调用前调整参数,在工具执行前检查路径,在工具结果后做脱敏或摘要。读者要关注的是:扩展点让能力进入 runtime,但不应该让 core 变成大泥球。
- Extension 是注册工具和 hook handler 的机制。它让外部能力在 beforeContextBuild、beforeModelCall、afterModelCall、beforeToolCall、afterToolCall 这些稳定点介入。
- 如果每个高级能力都直接写进 Agent Loop,运行时核心会越来越大,难以测试,也难以解释。
- 在
mini-agent-harness中,Extensions 提供 hook 和 tool registration,让高级能力围绕 core runtime 组合,而不是塞进 core。
定义
Extension 是注册工具和 hook handler 的机制。它让外部能力在 beforeContextBuild、beforeModelCall、afterModelCall、beforeToolCall、afterToolCall 这些稳定点介入。
为什么要单独看这一层?
Agent 产品会不断增加新能力:策略、遥测、上下文注入、工具包装、结果脱敏。Extensions 的作用,是给这些能力稳定入口,而不是修改 core。

边界
这一层的职责可以拆成几个稳定部分:
- ExtensionAPI:registerTool 与 on(event)
- ExtensionRuntime:保存 handlers 与 registeredTools
- Emit Order:按注册顺序执行 hook
- Mutable Payload:handler 可返回修改后的 payload
- Tool Registration:load() 时注册 extension tools
- Core Minimalism:高级能力作为原语组合
这一层的边界可以用一个问题检验:如果它的内部实现变化,模型适配、工具执行、状态存储和产品外壳是否都不需要跟着重写?如果答案是否定的,说明这个边界还没有真正收束变化。
代码锚点
本篇主要对应这些模块:
src/extensions/extension-api.tssrc/core/agent-loop.tssrc/core/harness.ts
阅读代码时建议先看类型,再看运行路径。
类型定义告诉你这一层暴露什么 contract;运行路径告诉你这个 contract 在 Agent 执行中何时被消费、何时被写回、何时被产品层看见。
运行流程

一次典型执行可以概括为:
- Register:extension factory
- Hook:on beforeModelCall
- Emit:AgentLoop checkpoint
- Mutate:返回新 payload
- Continue:运行时继续推进
- Policy:权限
- Telemetry:记录
- Context:动态注入
这里最容易被忽略的是“中间态”。生产级 Agent 不是只关心最终答案;它还要在运行过程中展示进度、捕获错误、记录 usage、允许取消,并把可恢复状态写回 session。
读者抓手:Extension 可以介入哪些点
| Hook 点 | 可以改变什么 | 不应该改变什么 |
|---|---|---|
| beforeContext | 注入 skill、resource、项目说明 | 直接执行危险外部动作 |
| beforeModel | 调整请求参数、模型选择 | 伪造用户消息含义 |
| beforeTool | 审批、路径保护、参数改写 | 绕过 Tool Runtime 记录 |
| afterTool | 脱敏、摘要、错误分类 | 吞掉必须进入 session 的事实 |
| afterEvent | 观测、日志、指标 | 改写已经发生的状态事实 |
Extension 的目标是增加能力,而不是让 core 失去边界。
可迁移伪实现:扩展 Hook
下面的伪代码是机制抽象,不对应真实 API 或文件结构。它只用来说明这一层的控制点:
api.on("beforeToolCall", ({ toolName, input }) => {
if (toolName === "bash") return { toolName, input: sanitize(input) };
});
api.registerTool(customTool);
这个草图的价值在于说明控制点,而不是提供可复制的库代码。
真正的工程实现还要处理错误、取消、并发、token 预算、日志、权限、序列化和 provider 差异。
工程原则
将这一层从 Agent 系统中拆出来,通常带来四个直接收益。
第一,可替换。
外部系统、模型 provider、工具集合或产品外壳变化时,核心运行时不必整体重写。
第二,可观测。
边界清晰后,事件、trace、usage、错误和状态迁移都有稳定落点。
第三,可恢复。
只要状态写入 session,运行时就可以在进程重启、工具失败或长任务中断后继续推理。
第四,可治理。
权限、脱敏、审批、路径保护和执行策略可以放在稳定 hook 或 runtime boundary 上,而不是只靠 prompt 约束。
和 Agent Harness 的关系
Agent = Model + Harness 这个公式的重点,不是把模型之外的所有东西都称为“工程杂活”。
相反,它提示我们:模型之外存在一套必须被设计的运行时系统。
Extensions 就是这套系统中的一个切面。它不替代模型能力,也不替代产品体验;它让模型能力可以被组织成可执行、可观察、可恢复、可治理的任务流程。
小结
Extensions 的核心价值,是把一类容易扩散的复杂性收束到明确边界中。
在 mini-agent-harness 中,这个边界被刻意写得较小,方便阅读和教学。但它对应的问题并不小:只要一个 Agent 要长期运行、调用工具、管理上下文、支持 UI、保存状态并处理失败,这个边界就会出现。
下一步可以继续沿着系列计划,把这些边界组合成完整 Agent Harness:模型边界、工具边界、状态边界、上下文边界、扩展边界和产品外壳边界。