自己动手构建 AI 编码 Agent Harness
使用 AI SDK、Vercel Sandbox 和 just-bash 从零构建 AI 编码 Agent Harness。涵盖工具循环、工具设计、系统提示词、沙箱抽象、上下文修剪、子 Agent 委派、生命周期管理和可扩展性。
只有一个工具循环和三个工具,还只能算演示。真正拿它干活时,问题才会出现:读取一个 5000 行的文件后,内容会永远留在上下文里;给它 bash,它可能直接运行 rm -rf;让它重构模块,它却只解释应该怎样重构;一个长任务就能填满上下文窗口,让 Agent 忘掉自己的指令;云沙箱按分钟计费,超时后代码还可能消失。
Harness 指的就是围绕 Agent 搭建、专门处理这些问题的整套系统。本课程将从零开始构建 TeensyCode。
你将构建什么
TeensyCode 是一套可实际运行的 AI 编码 Agent Harness。它拥有紧凑的 TypeScript 核心、完整工具集和多种沙箱后端。因为每个部分都由你亲手实现,所以你能够彻底理解它:
- 工具循环: 基于
ToolLoopAgent,提供read、grep、write、edit、bash、task和askUser工具 - 安全门: 从安全命令允许列表开始,在执行层拦截危险操作,并逐步演进为支持交互、后台和委派场景的可配置审批
- 行为提示词: 使用 Agency、Guardrails 和 Handling Ambiguity 等结构化章节,并注入
AGENTS.md实现项目级配置 - 沙箱抽象: 使用统一的
Sandbox接口,提供本地实现(Node fs 与 child_process)和内存实现(just-bash 与写时复制虚拟文件系统);切换后端时无需修改工具 - 上下文管理: 结合
pruneMessages、有界工具输出和缓存控制,让长会话保持可用并控制成本 - 子 Agent 委派: 为探索器和执行器提供隔离上下文、受限工具,并按任务角色选择模型
- 人机协作: 使用带多选项的
askUser,并遵循“先搜索、再提问、后行动”的歧义处理协议 - 沙箱生命周期: 使用状态机理解生命周期,并实现快照、恢复和持久化工作流
- 可扩展性: 通过事件总线、渐进式披露的技能系统和自定义工具注册扩展 Harness
前置要求
- 熟悉 TypeScript、async/await 和基本终端操作
- 已设置
AI_GATEWAY_API_KEY环境变量 - Node.js 20+ 或 Bun 运行时
- 建议先学习 构建文件系统 Agent 课程
课程如何进行
按因果顺序推进。 每一步都源于上一步暴露的问题。第一步加入 read,因为聊天机器人看不到文件;第二步加入 grep,因为 Agent 无法搜索;第三步加入 bash,因为它不能执行命令,但这也带来了运行 rm -rf 的风险。每一步只聚焦一个概念,同时让其余部分始终可以运行。
模块 1 到 6 以边学边做为主:编写代码、运行并验证。模块 7 侧重概念和分析,因为沙箱生命周期涉及不适合在本地随意演示的持久化工作流与状态机。模块 8 到 11 则结合实现与分析。
课程模块
模块 1:Agent 循环
从一个不带工具的 ToolLoopAgent(聊天机器人)开始,加入 read 和 grep 使其成为 Agent,再为 bash 加上安全门。
- 从聊天机器人到 Agent:一个工具如何让聊天机器人变成 Agent
- 你的第一批工具:为什么工具描述就是模型的选择接口
- 补齐工具箱:如何在执行层拦截危险工具
模块 2:工具设计
把描述扩展为五段式契约,提取工具工厂模式,并实现可配置审批。
- 有效的工具描述:WHEN TO USE、WHEN NOT TO USE、DO NOT USE FOR 与 EXAMPLES
- 安全执行 Shell:用工厂和操作接口分离契约与执行
- 审批门:从布尔值演进到函数和可辨识联合类型
模块 3:系统提示词
通过结构化指令、动态组合、验证门和 AGENTS.md 塑造 Agent 行为。
- 组织 Agent 指令:Agency 加 Guardrails,让 Agent 行动而不是只做解释
- 动态构建系统提示词:让
buildSystemPrompt()根据运行时上下文调整提示词 - 验证门:建立 typecheck、lint、test 和 build 契约
- 项目上下文:放入一个
AGENTS.md即可改变 Agent 行为
模块 4:沙箱抽象
一个接口,多种实现。工具调用 sandbox.exec(),而不是直接调用 child_process.exec()。
- 设计沙箱接口:定义包含
readFile、exec和stop的Sandbox类型 - 本地实现:封装 Node fs 与 child_process
- 内存实现:使用 just-bash 和写时复制覆盖层
- 云端实现:理解远程 VM 的概念与取舍
- 生命周期钩子:实现
afterStart、beforeStop和onTimeout
模块 5:上下文管理
每次工具调用都会一直留在上下文中。通过修剪、有界输出和缓存控制解决这个问题。
- 问题所在:通过 token 日志观察线性增长
- 修剪旧结果:使用
pruneMessages避免旧工具输出不断堆积 - 工具输出设计:预防优于清理,为每个工具设置明确上限
- 缓存控制:使用服务商请求头降低重复上下文成本
模块 6:子 Agent 委派
父 Agent 负责规划,子 Agent 负责执行;各自使用隔离上下文、受限工具和按角色选择的模型。
- 为什么要委派:理解单 Agent 的失效模式
- 探索型子 Agent:只读、低成本、受约束的探索
- 执行型子 Agent:完整工具、更强模型和委派式信任
- 任务工具:处理路由、权限和角色模型选择
模块 7:沙箱生命周期
云沙箱既有成本又会超时。本模块侧重概念与分析。
模块 8:人机协作
猜错方向的 Agent,往往比主动提问的 Agent 浪费更多时间。
模块 9:规划与验证
行动前规划,行动后验证。
模块 10:交互界面
Agent 本身没有界面。CLI、TUI 和 Web 只是不同的渲染策略。
模块 11:可扩展性
依靠事件而不是继承扩展系统;技能采用渐进式披露,工具采用注册机制。
综合项目
让你的 Harness 在真实项目中完成任务。不要只做“添加 Hello World 接口”,而应尝试“为认证路由添加速率限制”。观察上下文在哪里溢出、Agent 在哪里选错工具、子 Agent 在哪里收到含糊指令,然后逐一修复实际暴露的问题。
技术栈
| 组件 | 作用 |
|---|---|
| AI SDK | ToolLoopAgent、tool()、stepCountIs、pruneMessages 与流式输出 |
| AI Gateway | 模型路由;可直接使用 "anthropic/claude-haiku-4-5" 字符串,无需额外包装器 |
| Vercel Sandbox | 提供带隔离文件系统、git 和 npm 的远程 VM |
| just-bash | 提供内存虚拟文件系统和模拟 bash |
| Vercel Workflow | 为沙箱生命周期提供持久化工作流 |
| Zod v3 | 定义工具输入 schema;v4 会破坏 AI SDK v6 的类型兼容性 |