Harness Engineering

第21章 设计模式与架构决策:Harness Engineering 的 14 条原则

作者 杨艺韬 · 6,740 字

本章要点

  • 回顾全专栏前 20 章,提炼 Harness Engineering 反复出现的 14 条可迁移原则
  • 每条原则配具体场景、Claude Code / LangGraph / OpenClaw 实现对照、跨领域类比
  • 理解架构决策的四个顶层判断:方法论优先于框架 / 安全默认 / 可观测优先 / 为失败而设计
  • 看懂 14 条原则之间的关联图——它们不是孤立清单,是一张有机工程网
  • 拿到一个可执行的"新 Agent 项目启动清单",把原则落到实际动作

21.1 为什么要提炼原则

技术细节会过时。框架 API 会重构,模型能力排名会变化,Prompt Caching、tool calling、structured output 的协议也会随 provider 演进。

原则不会过时

  • "工具粒度按最小可撤销操作设计"——这条原则在 LangChain、AutoGPT、Claude Code、任何未来的 Agent 框架都成立
  • "不可逆操作默认审批"——不管用什么 HITL 机制,这条红线都要显式处理
  • "上下文即接口"——只要 LLM 的输入方式没变,这条就是真理

前 20 章讲的每一个工程决策,底下都站着某条原则。本章做的事——把原则从具体实现里抽出来,形成一张可迁移的工程地图

mindmap
  root((Harness 的 14 条原则))
    架构层 4
      分层架构
      工具抽象
      OTA 循环
      可插拔接口
    控制层 3
      渐进式信任
      纵深防御
      预算约束
    信息层 3
      上下文即接口
      记忆分层
      数据契约最小化
    协作层 2
      失败即信号
      人在回路
    哲学层 2
      方法论优先于框架
      fail-fast 胜于半修复

21.2 架构层:系统骨架的四条原则

原则 1:分层架构(Layered Architecture)

问题:Agent 系统涉及 LLM 调用、工具执行、权限、上下文、记忆、观测——混在一起没法维护。

原则:分成职责单一的层次,层间通过清晰接口通信。

┌─ 交互层 ─┐  HTTP / CLI / SSE 输出
├─ 编排层 ─┤  Agent Loop、多 Agent 协调、任务调度
├─ 能力层 ─┤  工具系统、Tool Registry
├─ 控制层 ─┤  权限、沙箱、审计
├─ 模型层 ─┤  LLM 调用、Prompt 组装
└─ 基础层 ─┘  配置、存储、记忆、缓存

真实实现

  • Claude Code:CLI → Query Engine → Tool System → Permission → LLM Client
  • LangGraph:Studio UI → StateGraph → Node → LangChain → Provider
  • OpenClaw:Gateway → Router → Provider → LLM API

跨领域类比

  • OSI 网络 7 层协议栈
  • Linux 内核的用户态/内核态分离
  • Unix"everything is a file"让分层统一

适用边界:复杂 Agent 系统都需要分层。层数可以 3-7 不等,但"关注点分离"不可妥协。

原则 2:工具抽象(Tool Abstraction)

问题:Agent 能力来自工具,但每种工具底层实现可能完全不同(本地函数 / HTTP / gRPC / Shell / DB)。

原则:用统一接口描述工具 —— (name, description, params_schema, execute_fn) 四元组。

interface Tool {
  name: string
  description: string
  parameters: JSONSchema
  execute(params: any, ctx: Context): Promise<ToolResult>
}

真实实现

  • Claude Code:大量内置工具遵循统一 Tool 接口,新工具不应改动主循环
  • LangGraph:@tool 装饰器 + Pydantic schema
  • MCP 协议:跨进程跨生态的工具契约

跨领域类比

  • Unix 命令行 stdin/stdout/argv 协议
  • HTTP method+url+body+headers 四元组
  • REST 的统一资源模型

适用边界:要么全面采用统一抽象,要么直接放弃——"半统一"是最糟的状态(有些工具走抽象有些走 ad-hoc,调试地狱)。

原则 3:观察-思考-行动循环(OTA Loop)

问题:Agent 不是 one-shot 生成——它必须和环境持续交互。

原则:永远是 observe → think → act → observe ... 的循环结构。

while not done:
    obs = collect_observations()
    thought = llm.think(obs)
    if thought.has_actions:
        results = execute(thought.actions)
    else:
        done = True

真实实现

  • Claude Code 的 main loop
  • LangGraph 的 Pregel engine(BSP 超步式 OTA)
  • ReAct / Reflexion / CodeAct 等 agent 范式,底下都是 OTA

跨领域类比

  • 控制论的反馈循环(negative feedback)
  • 游戏引擎的 game loop(input→update→render)
  • React 的单向数据流(action→state→view)

适用边界:大多数交互式 agent 系统。差异在"循环如何终止"、"循环中如何并发"、"是否允许人类中断"。

原则 4:可插拔接口(Pluggable Interfaces)

问题:Agent 生态演化快——新模型、新工具、新量化方案、新部署平台——每种都要支持。

原则:用稳定的抽象接口挡住变化;具体实现是后来可替换的插件。

graph TB
    Core[核心引擎不变]
    Core -->|LLMProvider 接口| Providers[OpenAI / Anthropic / Google / Local]
    Core -->|ToolBackend 接口| Tools[Local / MCP / HTTP / gRPC]
    Core -->|MemoryStore 接口| Memory[File / Postgres / Redis / Vector DB]
    Core -->|Checkpointer 接口| Checkpoint[SQLite / Postgres / Redis]
    Core -->|Observability 接口| Obs[LangSmith / LangFuse / Langtrace]
    style Core fill:#3b82f6,color:#fff,stroke:none

真实实现

  • OpenClaw 的 Provider 热切换(换 model 不重启)
  • vLLM 的 Executor / AttentionBackend / QuantizationConfig 三层插拔(见《vLLM 推理内核深度解析》第18章)
  • LangChain 的 BaseChatModel / BaseTool / BaseMemory 抽象

跨领域类比

  • JVM 的 ClassLoader + 字节码接口
  • WordPress 的 hook/filter 系统
  • Linux kernel modules

适用边界:接口要先稳定再开放——一旦你说"这是稳定 API",破坏兼容就代价极大。接口设计慢一点、冻结前多讨论几轮,值得。

21.3 控制层:让 Agent 可控的三条原则

原则 5:渐进式信任(Progressive Trust)

问题:Agent 能做危险操作;完全禁止就无用,完全放开就失控。

原则默认最严,用户主动放权。信任永远不自动升级。

Plan Mode → Default → Auto-Edit → Full Auto
(最严)                              (最宽)

真实实现(详见第 17 章《Human-in-the-Loop》):

  • Claude Code 的四档权限模式
  • Cursor 的 Auto Mode 切换
  • Copilot 的"建议 vs 应用"双模式

跨领域类比

  • Linux sudo(显式提权)
  • iOS App 权限弹窗(位置、相册等逐项授权)
  • 金融软件的"单次授权 vs 免密支付"

适用边界:不可逆操作默认审批——与信任级别无关。信任升级主要针对可逆、可审计、可回滚操作。

原则 6:纵深防御(Defense in Depth)

问题:单一安全检查容易被绕过(prompt injection、规则漏洞、正则 bypass)。

原则多层独立安全机制叠加,每层独立失效不传染。

请求 → 权限检查 → 参数校验 → 命令白名单 → 路径沙箱 → OS seccomp → 执行

真实实现

  • Claude Code 的 Bash:模式 → 正则过滤 → 路径白名单 → Seatbelt 沙箱 → 超时
  • Kubernetes 的 Pod Security:RBAC + AdmissionController + SecurityContext + seccomp
  • 浏览器的 Same-Origin Policy + CSP + CORS + Sandbox iframe

跨领域类比

  • 军事分层防御(外围 / 前线 / 预备队)
  • 银行的多道门禁
  • 气坝 / 防洪堤的多层设计

适用边界:每加一层都有代价(复杂度、性能),但对"绝不能破"的关键路径值得。消费级 agent 通常 2-3 层够;企业级 agent 4-5 层起步。

原则 7:预算约束(Budget Constraints)

问题:Agent 可能因为 bug / 死循环 / prompt injection 消耗天量资源。

原则每个资源维度都有硬上限——token、时间、cost、循环轮次、并发数。

const BUDGETS = {
  maxTokensPerTask: 200_000,
  maxTurnsPerLoop: 50,
  maxWallTimeMs: 600_000,      // 10 min
  maxCostPerTask: 5.00,          // $5
  maxConcurrentSubAgents: 5,
}

超过立即硬停——不要"尽量控制"。

真实实现

  • Claude Code --max-turns 参数
  • OpenAI SDK 的 timeoutmax_tokens
  • 多级熔断器(第 20 章《成本控制与性能优化》):per-task / per-user / per-tenant / global

跨领域类比

  • Unix ulimit
  • Docker 的 memory / cpu limit
  • Kubernetes 的 ResourceQuota

适用边界:生产系统都应有预算约束。没有预算约束,就是把成本、延迟和循环风险交给偶然性。

21.4 信息层:让 Agent "会思考"的三条原则

原则 8:上下文即接口(Context as Interface)

问题:模型无法调 API、无法读数据库——它只能"看"文本。

原则:把所有信息精心编码为模型能理解的上下文。context 设计本质上就是 UX 设计。

四类信息:

  • 稳定规则 → System Prompt
  • 环境状态 → 动态注入(session 开始时)
  • 按需信息 → 工具调用结果
  • 噪音 → 不放

真实实现

  • Claude Code 的 CLAUDE.md 自动注入机制
  • Cursor 的 .cursorrules
  • LangChain 的 ContextualCompressionRetriever(配合 BaseDocumentCompressor 实现)

跨领域类比

  • 前端 React 的 props 设计(数据向下流)
  • 数据库的 schema(给应用的"窗口")
  • 游戏的 HUD 设计(信息展示优先级)

适用边界:所有依赖上下文驱动模型行为的 Agent 系统。context 设计做得不好,再强的模型也发挥不出能力——"Garbage In, Garbage Out"。

原则 9:记忆分层(Layered Memory)

问题:Agent 既要记短期(当前对话)又要记长期(用户偏好、项目惯例)——两类记忆机制完全不同。

原则:至少三层记忆:工作记忆 + 会话记忆 + 长期记忆。

┌─ 工作记忆 ─┐  当前 context window,MB 级,TTL 秒
├─ 会话记忆 ─┤  当前 session 可压缩历史,几百 MB,TTL 天
└─ 长期记忆 ─┘  跨 session 持久,GB 级,TTL 永久

真实实现(详见第11章《短期记忆:上下文窗口管理》、第12章《长期记忆:持久化与检索》、第 13 章《多轮对话与会话状态机》):

  • Claude Code:messages[] + compact + ~/.claude/memories/
  • LangGraph:state channels + checkpointer + long-term store
  • Letta(前 MemGPT):core memory + archival memory + recall memory

跨领域类比

  • CPU 的 L1/L2/L3 缓存 + RAM + 磁盘
  • 人类的短时记忆 + 工作记忆 + 长时记忆
  • 数据库的 buffer pool + 索引 + 磁盘文件

适用边界:任何多轮 agent。单轮工具用不着分层。

原则 10:数据契约最小化(Minimal Data Contracts)

问题:系统各层之间传递的数据结构臃肿,导致层间耦合、难演化。

原则每个跨边界 DTO 只含下游真正需要的字段,不多一个。

# ❌ 把 internal state 全抛出去
class BadRequest:
    ...20 个字段,其中 15 个下游用不到

# ✅ 最小化
class EngineCoreRequest:
    prompt_token_ids: list[int]
    sampling_params: SamplingParams
    request_id: str
    # ... 仅此而已

真实实现

  • vLLM 的 EngineCoreRequest(跨进程最小化)vs Request(进程内有状态)
  • LangGraph 的 Channel(每个 Node 只读自己关心的字段)
  • Kubernetes CRD 的 spec vs status 分离

跨领域类比

  • REST API 的 "Return the smallest useful response"
  • Protobuf 的 versioning(未知字段忽略)
  • Unix pipe 的纯文本流

适用边界:任何分布式/多进程系统。单进程应用也值得——未来拆分会轻松。

21.5 协作层:让 Agent 可靠的两条原则

原则 11:失败即信号(Failure as Signal)

问题:工具会失败、模型会幻觉、网络会抖——怎么处理?

原则把失败作为模型能消费的信号,让模型自己决定怎么调整。

// ❌ 把错误当异常抛
throw new ToolError('File not found')

// ✅ 把错误当结果返回
return {
  is_error: true,
  error: 'File not found',
  suggestion: 'Check path or use Glob to search',
  recovery_hints: ['Did you mean src/auth.ts?'],
}
// → 模型看到后会自动调整策略

真实实现

  • Claude Code 的所有工具错误都 formatted 成给模型看的文本
  • LangGraph 的 add_edge 可以从 error 节点 fork
  • ReAct 范式天然以"observation contains errors"为第一公民

跨领域类比

  • Erlang "Let it crash" + 监督树
  • HTTP 4xx 错误码(详细描述怎么错的)
  • Rust 的 Result<T, E> 强制处理错误

适用边界:错误是输入给模型的结果,不是 bug。真正的 bug(配置错、网络断)仍然要 crash + log。

原则 12:人在回路(Human in the Loop)

问题:Agent 不可能保证每一步都正确。什么时候让人介入?

原则风险驱动介入——不可逆 + 影响共享 + 跨信任边界 = 必须审批。

低风险 → 自动
中风险 → 默认问,可加白名单
高风险 → 默认问
不可逆 → 必须打字确认

真实实现(详见第 17 章《Human-in-the-Loop》):

  • LangGraph interrupt() + Command(resume=...)
  • Claude Code 的 Plan / Default / Auto-Edit / Full Auto 四档

跨领域类比

  • 核电站的人工钥匙(双人同时转动)
  • 数据库 DDL 的 "dry run" + 人工 apply
  • 军方核武发射协议

适用边界:production agent 都要定义 HITL 边界。消费级 agent 可以宽松一点,企业级必须更严格。

21.6 哲学层:两条跨全专栏原则

原则 13:方法论优先于框架(Methodology Over Framework)

问题:framework 的版本、概念和最佳实践都会变——LangChain、Semantic Kernel、AutoGen、OpenAI Agents SDK 都会继续演进。框架之争没完没了。

原则理解原则,而不是记住 API

❌ 把 LangChain 0.2 的 API 背熟
✅ 理解"Chain 是一种表达数据流的抽象"
   → 任何框架的类似概念你都秒懂

真实实现(本专栏的每一章):

  • 讲 Claude Code 时不讲它的具体 TypeScript 接口——讲它为什么这样做
  • 讲 LangGraph 时不教 add_edge 语法——讲 BSP 超步的设计动因
  • 讲 MCP 时不背 JSON-RPC 字段——理解"跨进程工具契约"为什么有价值

跨领域类比

  • 学数学不是记公式——是理解公式怎么推导出来
  • 学设计模式不是抄 Gang of Four——是看懂模式背后的意图
  • 学 Linux 不是背命令——是理解"一切皆文件"

适用边界:长期学习和架构设计场景。API 记忆会过期,方法论迁移得更久。

原则 14:fail-fast 胜于半修复(Fail-fast over Partial Recovery)

问题:Agent 运行中某个组件坏了——尝试修复还是整个重启?

原则大多数时候应该让整个进程退出,让外部编排重建。"半修复"状态复杂、难测试、容易埋 bug。

// ❌ 复杂的半修复
async function handleWorkerCrash(worker) {
  try {
    await restartWorker(worker)
    await reloadState(worker)
    await reconnectToLLM(worker)
    // ... 10 种可能的边界情况
  } catch {
    // 这里还是崩了
  }
}

// ✅ fail-fast
async function handleWorkerCrash(worker) {
  logger.error('Worker crashed, exiting engine')
  // 让外部编排(如 K8s)重新创建实例
  process.exit(1)
}

真实实现

  • vLLM 的 Worker 崩溃处理(见《vLLM 推理内核深度解析》第06章)
  • Erlang 的 "Let it crash"
  • Kubernetes pod 的 restartPolicy: Always

跨领域类比

  • 分布式系统的"幂等 + 重试"设计
  • 数据库事务的"all or nothing"
  • 飞行控制系统的"冗余 + fail-safe"

适用边界:有外部编排(K8s、systemd、PM2)的环境。没有编排的纯 CLI 工具要保守一些(用户看到 crash 体验较差)。

21.7 原则之间的关联图

14 条原则不是孤立清单,而是一张有机工程网

graph TB
    L[分层架构] --> T[工具抽象]
    L --> O[OTA 循环]
    L --> P[可插拔接口]
    T --> O
    O --> PT[渐进信任]
    PT --> DD[纵深防御]
    DD --> BC[预算约束]
    O --> CI[上下文即接口]
    CI --> LM[记忆分层]
    CI --> DCM[数据契约最小化]
    O --> FS[失败即信号]
    FS --> HIL[人在回路]
    HIL --> PT
    MO[方法论优先] -.贯穿.- L & T & O & P & PT & DD & BC & CI & LM & DCM & FS & HIL
    FF[fail-fast] -.贯穿.- DD & BC & HIL
    style L fill:#3b82f6,color:#fff,stroke:none
    style O fill:#8b5cf6,color:#fff,stroke:none
    style DD fill:#ef4444,color:#fff,stroke:none
    style HIL fill:#10b981,color:#fff,stroke:none
    style MO fill:#f59e0b,color:#fff,stroke:none
    style FF fill:#f59e0b,color:#fff,stroke:none

三条核心链路

  1. 系统骨架链:分层 → OTA 循环 → 工具抽象 → 可插拔接口
  2. 控制链:渐进信任 → 纵深防御 → 预算约束("安全"的三大支柱)
  3. 信息链:上下文即接口 → 记忆分层 → 数据契约最小化("能思考"的三大支柱)

两条贯穿原则(方法论优先、fail-fast)像底色一样贯穿所有具体决策。

21.8 架构决策的四个顶层判断

14 条原则之上,还有 4 个更高层的架构判断。它们决定你整个产品的气质:

判断 1:方法论优先于框架

体现:选型时问"它背后的设计思想是什么",而不是"它有哪些 API"。

反例:项目深度绑定某个框架的内部 API,等框架升级或概念重构时,业务逻辑、工具协议和状态模型被迫一起迁移。

判断 2:安全默认,能力可选

体现:默认拒绝一切,再选择性开放。不是默认允许一切,再试图堵漏洞。

反例:早期 AutoGPT 的 continuous mode——开启后 Agent 不经人工审批连续自主执行动作、跑偏了也没人踩刹车、被用户吐槽"一转眼它就做了一堆你没让它做的事"。(值得澄清:它的本地 Shell 执行开关 EXECUTE_LOCAL_COMMANDS 默认其实是关闭的——真正的教训不是"默认权限太大"、而是"缺少逐步审批的运行模式"。)

判断 3:可观测优先于可控制

体现:你无法控制模型的内部推理,但可以观测所有外部行为。先做到看得见,再谈控得住

没有 trace 的 agent 系统调试起来就是猜谜游戏。可观测性不是 nice-to-have,是 must-have

判断 4:为失败而设计

体现:Agent 一定会犯错——工具失败、模型幻觉、用户矛盾。好的 Harness 不是消除失败,而是让失败可恢复、可诊断、损害可控

三条具体落实:

  • 失败作为信号(原则 11)
  • fail-fast(原则 14)
  • 《可观测性》(第 19 章)

21.8.5 14 条原则 vs 常见失效模式

把全专栏前 20 章重新组织,可以看到一个规律:Agent 事故很少只违反一条原则。真正的生产故障通常是两三条原则同时失效,比如预算没有硬上限、错误没有变成可恢复信号、trace 又不够完整,最后才表现成"模型乱跑"或"账单异常"。下面这张表不是事故复盘,而是失效模式索引:看到症状时,先回到对应原则检查。

失效模式 常见症状 相关原则 修复抓手
循环失控 Agent 反复调用同一工具、重复修改同一文件 预算约束 / fail-fast max turns、单工具频率限制、连续失败熔断
权限膨胀 一次授权后后续危险操作也被放行 渐进式信任 / 人在回路 权限按操作类型拆分,不可逆操作重新确认
沙箱穿透 工具参数绕过路径边界或命令过滤 纵深防御 canonical path、denylist+allowlist、OS 级隔离
上下文污染 旧工具结果、旧记忆或错误摘要误导模型 上下文即接口 / 记忆分层 freshness 标记、引用校验、压缩后抽样审计
缓存串台 语义缓存或 prompt cache 被不同用户复用 数据契约最小化 / 纵深防御 cache key 加 tenant/user/scope,敏感操作不进语义缓存
评估脱钩 eval 分数高,真实用户仍然失败 失败即信号 / 可观测优先 从生产 trace 反哺 eval,按失败类别扩充用例
成本漂移 单次任务可控,总体账单不可控 预算约束 / 可观测优先 per-task、per-user、per-tenant、global 四级预算
恢复过度 系统试图半修复,状态越修越乱 fail-fast / 数据契约 幂等边界、外部编排重启、恢复流程最小化
多 Agent 互相踩踏 子 Agent 重复做同一件事或写同一文件 分层架构 / 数据契约 明确 ownership、任务租约、共享状态只读化
工具抽象泄漏 新工具要求主循环加特例 工具抽象 / 可插拔接口 把差异放进 adapter,不把特例推回核心循环

这张表的价值在于定位顺序。不要一看到事故就先调 prompt;先问四个问题:

  1. 有没有硬边界:预算、权限、路径、时间有没有真正的上限?
  2. 有没有可见证据:trace 能不能还原模型看到了什么、调用了什么、工具返回了什么?
  3. 有没有最小契约:跨层传递的数据是不是多带了不该带的权限、身份或缓存上下文?
  4. 有没有恢复策略:失败是作为 observation 返回、让模型调整,还是应该直接 fail-fast 交给外部编排?

如果这四个问题答不上来,继续堆 prompt 只是在掩盖架构债。Harness Engineering 的核心纪律是:先让系统边界清楚,再让模型在边界内变聪明

21.8.6 原则冲突时怎么取舍

14 条原则并不总是同向。工程难点恰恰在于冲突:

冲突 典型场景 取舍原则
安全默认 vs 用户效率 每次文件写入都询问会打断开发流 可逆操作可记忆授权,不可逆操作仍审批
上下文完整 vs 上下文噪音 全量塞入 trace 会挤掉当前任务信息 稳定规则常驻,长尾信息按需工具化
fail-fast vs 可用性 服务端 worker 崩溃是否尝试热恢复 有外部编排时 fail-fast;本地 CLI 要给用户可理解错误
插件开放 vs 接口稳定 第三方工具想扩展主循环事件 先提供窄 hook,不承诺内部状态结构
多 Agent 并行 vs 状态一致 多个子任务同时编辑同一模块 并行只分配不重叠 ownership,共享资源需要锁或审阅

一个好 Harness 的标志不是"每条原则都做到极致",而是知道哪条原则在当前场景拥有更高优先级。企业内网自动化和个人本地编程助手的取舍不同;离线批处理 agent 和实时对话 agent 的取舍也不同。原则给的是判断框架,不是机械规则。

21.8.7 把原则落成 ADR

原则如果只停留在口号层,过几周就会被需求压力冲掉。真正可执行的做法,是把关键取舍写成 ADR(Architecture Decision Record)。每一条 ADR 不需要长,但必须回答五个问题:

字段 要回答的问题 例子
Context 为什么现在要做这个决策 工具开始支持文件写入,风险等级上升
Decision 最终采用什么规则 写入 workspace 内文件可自动,删除和跨目录写入需审批
Alternatives 放弃了哪些方案 全自动、全询问、只按工具名授权
Consequences 代价是什么 用户会多看到审批弹窗,但误删风险下降
Revisit 什么时候重新评估 当 undo 覆盖率达到目标后,放宽部分写入

把 14 条原则翻译成 ADR 时,可以按下面的顺序走:

  1. 先写控制层 ADR:权限模式、不可逆操作、预算熔断、沙箱边界。这些决定系统能不能安全上线。
  2. 再写信息层 ADR:上下文注入规则、记忆 freshness、cache key、DTO 字段。这些决定模型看到什么。
  3. 最后写扩展层 ADR:provider 插件、tool adapter、memory store、eval backend。这些决定未来换实现时要不要动核心。

一个常见错误是反过来:先讨论用哪个框架、哪个向量库、哪个 tracing vendor,然后才补安全和数据契约。这样项目很容易形成"库驱动架构":框架给了什么 hook,系统就长成什么样。Harness Engineering 的顺序应该相反:先定义安全和上下文边界,再让框架实现这些边界。

ADR 还有一个隐藏价值:它让团队能区分"原则变了"和"参数变了"。比如 max_turns 从 20 调到 30,只是参数变化;"不可逆操作从审批改成自动执行",这是原则变化,必须重新审查风险、eval、审计和回滚策略。没有 ADR,这两类变化很容易混在同一个 PR 里,reviewer 只能凭直觉判断。

21.8.8 反模式清单

最后列一组反模式,作为 code review 和架构评审时的快速嗅探器:

反模式 表面表现 背后违反的原则
Prompt 补丁越写越长 每次事故都往 system prompt 加一句禁令 纵深防御、上下文即接口
Tool 直接读全局状态 工具执行依赖隐式 singleton 数据契约最小化、分层架构
权限按工具名授权 bash 一放行就什么都能做 渐进式信任、人在回路
Memory 永不失效 旧项目事实长期影响新任务 记忆分层、失败即信号
Eval 只测 happy path 上线后失败全来自边界输入 可观测优先、为失败而设计
插件可以改核心对象 第三方扩展能破坏主循环不变式 可插拔接口、fail-fast

看到这些反模式,不要急着写修复代码。先回到本章 14 条原则,判断这是架构边界问题、信息编码问题,还是控制策略问题。分类准确,修复才不会越修越散。

21.9 新 Agent 项目启动清单

把 14 条原则落到具体动作,一个可执行的启动清单:

第 1 周:架构与骨架

  • 画清楚分层架构图(3-7 层按需、见原则 1)
  • 定义所有工具的统一接口(原则 2)
  • 搭建最简 OTA 循环 + 一个 demo 工具
  • 确定可插拔接口:LLM Provider / Tool Backend / Memory Store

第 2 周:可观测性基础

  • 接 OpenTelemetry 或 LangFuse
  • 加 Trace 展示 UI
  • 搭 Prometheus metrics

第 3 周:控制层

  • 定义权限模式(Plan / Default / Auto-Edit / Full Auto 或等价物)
  • 为每个风险级别的操作定规则
  • 加预算熔断:per-task / per-user / per-day

第 4 周:信息层

  • 设计 System Prompt 分层(稳定 / 动态 / 注入)
  • 搭建短期 + 长期记忆(至少文件级别)
  • 定义所有跨层 DTO 的最小字段集

第 5 周:协作层

  • 实现所有工具的 error-as-signal 处理
  • 搭 HITL 审批机制(LangGraph interrupt 或等价)
  • 给所有操作加 undo 或 audit log

第 6 周:评估与迭代

  • 构建初版评估集(50-100 任务,覆盖 typical + edge + adversarial)
  • 跑 LLM-as-Judge pipeline
  • CI/CD 质量门禁

持续迭代

  • 每周 review 用户反馈 → 转成评估任务
  • 每月跑 Pareto 分析(quality / cost / latency)
  • 每季度 review 14 条原则落实情况

21.10 全专栏回顾

graph LR
    subgraph "第一篇 · 开篇"
        P0[ch00 前言]
        P1[ch01 为什么是 Harness]
    end
    subgraph "第二篇 · 架构"
        P2[ch02-04]
    end
    subgraph "第三篇 · 工具"
        P3[ch05-07]
    end
    subgraph "第四篇 · 提示词"
        P4[ch08-10]
    end
    subgraph "第五篇 · 记忆"
        P5[ch11-13]
    end
    subgraph "第六篇 · 安全"
        P6[ch14-15]
    end
    subgraph "第七篇 · 协作"
        P7[ch16-17]
    end
    subgraph "第八篇 · 生产化"
        P8[ch18-21]
    end
    P1 --> P2 --> P3 --> P4 --> P5 --> P6 --> P7 --> P8
    style P0 fill:#3b82f6,color:#fff,stroke:none
    style P8 fill:#10b981,color:#fff,stroke:none

从"为什么是 Harness"到"怎么评估 / 观测 / 控制成本",再到本章的"设计原则"——这个专栏画的地图完整了。

21.11 核心信念

全专栏浓缩成一句话:

模型是引擎,Harness 是缰绳。好的缰绳不限制马的力量,而是让力量指向正确的方向。

每一条原则、每一个设计决策、每一段代码,都是在服务这个信念。

Agent 工程是年轻的学科。本专栏写的是当前工程实践中相对稳定的一组判断——几年后回头看,某些章节可能会显得幼稚。那没关系——好的方法论是阶梯,你踩上去看到下一层风景后,把它拆掉就好。

感谢你读完这个专栏。现在,去构建你自己的 Harness 吧——用这 14 条原则作为起点,但不要被它们限制。


全专栏完。

愿你造的每一匹马,都有配得上它的缰绳。


延伸阅读

  • Christopher Alexander《A Pattern Language》—— 设计模式思想的源头
  • Gang of Four《Design Patterns》—— 软件设计模式经典
  • Erlang 的 "Let it crash" 哲学:Joe Armstrong 的博士论文
  • Unix Philosophy —— man 7 unix 或 Eric Raymond 《The Art of Unix Programming》
  • 杨艺韬讲堂全系列源码专栏:https://www.yangyitao.com/