收尾篇 · 构建你自己的 Agent Harness
这个专题从”范式转移”讲起,一路拆过配置、权限、钩子、Skills、子智能体、Plan 模式、乃至和 Codex 的哲学对比。拆解的意义不在于记住某个常量或某张流程图,而在于——当你要自己搭一套 Agent 时,知道每个决策点有哪些选项、为什么这么选。
这篇是收尾。上半部分把散落各篇的子系统重新装回一个整体,给一条能落地的六步路线图;下半部分回到最实际的问题
Windows 上,怎么从零把环境搭到开箱即用。一、先问自己 Harness 吗
不是每个用到 LLM 的场景都需要 Agent Harness。搭一整套对话循环、权限管线、上下文压缩,是有成本的。判断的关键就一句话:
如果你的系统只需要 LLM 做”输入 → 输出”的转换(翻译、摘要、分类),用简单的 API 调用就够了。如果你的系统需要 LLM 做”观察 → 思考 → 行动 → 再观察”的循环,才需要 Agent Harness。
展开成三个维度:
| 维度 | 简单 API 调用 | Agent Harness |
|---|---|---|
| 交互轮次 | 单次请求-响应 | 多轮自主循环 |
| 工具需求 | 无,或仅单轮 Function Calling | 多种工具 + 权限控制 |
| 上下文管理 | 手动拼接 Prompt | 自动压缩、记忆提取 |
| 错误恢复 | 重试 | 多层恢复(断路器、降级、压缩) |
| 成本敏感度 | 低(单次调用) | 高(长会话累积) |
| 安全要求 | 低(无副作用) | 高(文件操作、命令执行) |
满足下面任意一条,就该考虑 Harness
、涉及文件/命令/网络等副作用、对话可能跨几十上百轮、要在多个环境(CLI/IDE/SDK)保持一致行为、需要精细的 token 预算控制、需要可观测性追踪每一步决策。二、把五大原则装回去
第一篇讲过 Claude Code 的五大设计原则。当时它们是”读懂别人架构”的钥匙,现在它们是”设计自己架构”的准绳。逐条对应到工程决策:
1. 循环优于递归。 核心对话用 while(true) 循环 + 状态对象,而不是递归调用。三个理由
2. Schema 驱动而非硬编码。 每个工具用 Zod Schema 定义输入,验证逻辑、权限检查、API 描述都从同一个 Schema 派生。这就是”单一真相源”——参数改一处,全局生效,消除了三处手动同步的不一致隐患。
3. 渐进式权限。 权限分阶段短路检查(见配置篇的四阶段管线)。好处是”早期拒绝”
,权限明确拒绝就不必弹用户确认。每一阶段都是一层筛子。4. 流式优先。 从模型响应到工具结果,所有数据通过 AsyncGenerator 的 yield 传递,消费端逐条处理而非等整回合结束。这是实时 UI 和 SDK 流式消费的基础。
5. 可插拔扩展。 在生命周期节点提供钩子扩展点,每个钩子是独立的 Shell 命令或 HTTP 端点,通过标准化 JSON 协议交互。核心引擎不需要知道扩展逻辑的存在。
这五条不是孤立技巧,而是相互支撑的决策网络。当你在自己的项目里遇到本专题没覆盖的场景时,回到这五条,它们能帮你做出和整体一致的选择。
三、六步路线图
搭 Harness 是渐进过程。下面六步,每步建立在前一步之上,最终产出一个最小可运行的 Harness:
flowchart TD
S1["Step 1: 对话循环<br/>AsyncGenerator + while(true)"] --> S2["Step 2: 工具系统<br/>buildTool 工厂函数"]
S2 --> S3["Step 3: 权限管线<br/>四阶段短路检查"]
S3 --> S4["Step 4: 上下文管理<br/>渐进式压缩策略"]
S4 --> S5["Step 5: 记忆系统<br/>提取/存储/注入"]
S5 --> S6["Step 6: 钩子系统<br/>生命周期扩展点"]
S6 --> DONE["最小可运行 Harness"]
classDef stepNode fill:#e8f4f8,stroke:#2980b9,stroke-width:1px
classDef doneNode fill:#d4edda,stroke:#27ae60,stroke-width:2px
class S1,S2,S3,S4,S5,S6 stepNode
class DONE doneNode
别被”六步”迷惑,这不是瀑布流程。实践中你会在步骤之间反复迭代
,实现上下文管理时可能调整对话循环的状态结构。六步的意义是提供有序的学习路径,不是僵化的开发顺序。Step 1——心脏
对话循环是 Harness 的心脏。用 AsyncGenerator 实现一个”解构状态 → 调 API → 检测工具调用 → 执行 → 回填消息 → 决定继续或终止”的循环。核心骨架:
async function* query(initialState: State): AsyncGenerator<StreamEvent> {
let state = initialState
while (true) {
// 0. 中止检查(循环顶部,最自然的退出点)
if (state.signal.aborted) return
// 1. 预处理:上下文过大则压缩(Step 4)
state = await maybeCompact(state)
// 2. 调用 LLM,流式接收
const response = streamLLM(state.messages, state.tools)
let toolCalls = []
for await (const chunk of response) {
yield { type: 'text', delta: chunk.text } // 增量输出给 UI
if (chunk.toolCall) toolCalls.push(chunk.toolCall)
}
// 3. 没有工具调用 → 纯文本回复,终止
if (toolCalls.length === 0) return
// 4. 执行工具(经过权限管线 Step 3),结果回填
const results = await executeTools(toolCalls, state) // yield 进度
state = { ...state, messages: [...state.messages, ...results] }
// 5. continue —— 带着新消息进入下一轮
}
}
关键在于状态是不可变的
{ ...state, ... } 整体替换,而非逐字段修改。这让每次状态变化都可追溯,也让 Step 4 的压缩变成”替换 messages + continue”这么简单的一件事。
Step 2——双手
每个工具用一个 buildTool 工厂从 Schema 派生完整行为。工具契约至少包含
checkPermissions(工具特定的权限逻辑)、execute(实际执行,支持进度回调)、以及 readOnly/destructive/concurrencySafe 三个元数据(决定调度和权限)。Schema 驱动确保验证、权限、文档同源。
Step 3——护栏
四阶段短路检查:validateInput(Zod 校验) → 规则匹配(deny > ask > allow 铁律) → checkPermissions(上下文评估) → 交互式确认。核心是 deny 永远压过 allow,以及默认值必须 fail-closed(校验失败、回调抛错都降级为”更保守”而非”更激进”)。细节见配置篇。
Step 4——工作记忆
有效上下文窗口是有限的。渐进式压缩
(裁剪工具结果、折叠旧消息),不够再上 LLM 摘要。务必加熔断——连续压缩失败达到上限(比如 3 次)就停,别陷入”压缩失败→重试→再失败”的死循环。Step 5——长期记忆
跨会话的持久记忆。核心原则是”只保存无法推导的信息”
、git 历史、能重新读出来的东西都不该进记忆;用户偏好、项目约定、踩过的坑才值得存。用一个索引文件(如MEMORY.md)做目录,按需加载具体条目,避免每次把全部记忆塞进上下文。
Step 6——神经末梢
在生命周期节点(SessionStart、PreToolUse、PostToolUse、Stop 等)提供扩展点。每个钩子是独立 Shell 命令,通过 JSON 输入输出与 Harness 交互。这是”确定性控制”的关键——有些规则不能靠模型自觉,必须用钩子强制(比如提交前必须测试通过)。
走完这六步,你就有了一个能对话、能调工具、有安全护栏、会管上下文、有记忆、可扩展的最小 Harness。剩下的都是在这个骨架上做加法。
四、运行时选型
Claude Code 选 Bun,主要图三点
(约 Node 的三分之一,对 CLI 很关键)、原生 TypeScript(无需预编译)、编译时特性开关(构建阶段消除不用的代码路径)。但这不是唯一解:| 维度 | Bun | Node.js | Python |
|---|---|---|---|
| 启动速度 | 极快 | 中等 | 慢 |
| TypeScript 原生 | 是 | 需编译 | 否 |
| 包生态成熟度 | 中等 | 极高 | 极高 |
| 特性开关支持 | 内置 | 需工具 | 需工具 |
| 部署普遍性 | 新兴 | 广泛 | 广泛 |
| 类型安全 | 强 | 强 | 弱 |
如果你已有 Node.js 基础设施,Node 完全可行;数据科学/ML 场景 Python 更顺,只是类型安全和工具生态弱一些。选型没有标准答案,取决于你的部署形态——这正是对比篇的核心结论
的差异都是同一批原则在不同部署形态下的折中。五、落地清单 从零到开箱即用
理论讲完,回到最实际的问题。很多人卡在”环境都搭不起来”这一步。下面是 Windows 上的完整落地路径,按依赖顺序排列。
基础环境(必装,四步):
- PowerShell 7.x —— Win10 自带的是 5.x,补全和体验差一截。
winget install --id Microsoft.PowerShell,装完用pwsh启动(和 5.x 是独立程序)。 - Node.js —— 后续几乎所有安装都依赖它。用官网 msi 安装包(自动配环境变量),装完
node -v/npm -v/npx -v都能用。 - Git —— 版本管理是安全底线。AI 有预期外改动时,
git diff和分支能救命。CLAUDE.md 里务必写清”不主动提交/推送、不 —force 到 main”。 - Claude Code ——
irm https://claude.ai/install.ps1 | iex,然后claude进入。首次要选登录方式(官方账号 / API Key / 第三方中转)。
网络提示
API 需要代理。会话级临时设置:$env:https_proxy="http://127.0.0.1:7890"。别嫌麻烦,这比”永久生效”更可控。
进阶扩展(按需选装):
- cc-switch —— 图形化管理多个供应商配置(官方 / 中转),一键切换,还能多端同步 MCP、Skill、API 配置。新手强烈推荐,省去手改
settings.json的痛苦。 - MCP Server —— 不用一上来装一堆。真正常用的就几个(库文档检索,解决知识陈旧)、DeepWiki(GitHub 仓库 AI 文档)、Playwright(网页自动化测试)。用一段时间知道自己缺什么了再补。
- 状态栏增强(如 CCometixLine) —— 实时看上下文窗口用量,配合
/context命令,对管理 token 很有帮助。 - Workflow 脚手架 —— 如果要做大型复杂项目,可以引入规范驱动的开发工具(OpenSpec 这类),或社区的 workflow 方案,给流程加骨架。
如果你连这些都不想手动配,社区里有像 CCQ(Claude Code Quickstart)这样的一键安装脚本,把上面基础环境四步 + 进阶扩展做成了交互式选装,凭据还能持久化到本地 vault,重装不用重填。适合”想体验但不想折腾环境”的人。
但记住第二篇说过的话: 你只要会安装、启动、对话就足够开始了。上面的扩展全是非必要的。用顺手了、知道自己需求了,再回头看这些功能,你很快就明白它们的场景。一开始就想学全,只会消磨掉学习的动力。
结语 是产品,不是脚手架
整个专题拆了这么多,最后想留下的就一句话——也是对比篇里 Claude Code 和 Codex 殊途同归的那句:
Harness 承担不变量,模型承担决策。
差异化的产品力,从来不来自”你调用哪个 LLM”——这部分谁都能调到最好的模型。差异化来自 harness 怎么把那个 LLM 卡进自己的工程不变量层
,哪些留给模型;哪些默认值保守兜底,哪些边界用类型或内核钉死。读懂 Claude Code 的设计,不是为了复刻它,而是为了在你自己的场景里,知道每一个”为什么这样设计”背后的权衡。当你能对自己的每个设计决策说清”如果不这样会怎样”,你就真正拥有了这套可迁移的心智模型。
这个专题到此结束。如果它帮你把”会用 Claude Code”往前推了一步,变成”看得懂、也能自己搭”,那就够了。
本篇主要参考: