第二章 最小可行 Harness:系统架构蓝图
核心命题:第一章的论证在一个节点上终止:如果约束是结构必然,那么这个”装置”的形态是什么? 本章从实践观察出发回答这个问题——任何独立构建的成熟 Harness,无论技术栈、规模、领域,都不约而同地包含相同的五个核心构件,以相同的拓扑关系组合。这种趋同不是模仿,而是结构压力收敛于同一解的证据。本章将这一拓扑关系明确化,形成最小可行 Harness 的架构蓝图:后续所有构件章节(第七至十一章)的共同参照系,全书”工程实践”线的起点。
本章与后续章节的分工
本章提供系统视图——五个核心构件的职责边界与组合拓扑(Skill 作为组织级持续改进机制,在第三部分第十三章单独展开)。这张蓝图是”地图”,不是”说明书”:它告诉你各部分在哪里、负责什么,但不解释为什么以这种方式组合(这是第三至六章的任务),也不解释怎么把每个构件做好(这是第七至十一章的任务)。在继续阅读之前,建立这张整体地图,能让后续的每一章深化都有方向感。
2.1 系统架构图
┌────────────────────────────────────────────────────────────────┐
│ Harness │
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ System Prompt(角色、边界、行为准则) │ │
│ └─────────────────────────────┬───────────────────────────┘ │
│ │ 静态约束注入 │
│ ┌─────────────────────────────▼───────────────────────────┐ │
│ │ Context Manager │ │
│ │ ┌──────────────────┐ ┌──────────────────┐ │ │
│ │ │ Static Context │ │ Dynamic Context │ │ │
│ │ │ (任务规范/文档) │ │ (工具返回/状态) │ │ │
│ │ └──────────────────┘ └──────────────────┘ │ │
│ └─────────────────────────────┬───────────────────────────┘ │
│ │ ← 数据流(Context Flow) │
│ ┌───────────▼──────────┐ │
│ │ LLM (Model) │ │
│ └───────────┬──────────┘ │
│ │ ← 控制流(Control Flow) │
│ ┌─────────────────▼─────────────────┐ │
│ Pre-Hook ──►│ Plan / Action │◄─ Post-Hook│
│ └─────────────────┬─────────────────┘ │
│ │ │
│ ┌───────────▼──────────┐ │
│ │ Tool Executor │ │
│ └───────────┬──────────┘ │
│ │ ← 反馈流(Feedback Flow) │
│ └──────────────────────────────►│
│ (回写 Context) │
│ │
│ ↑ Human on the Loop │
└────────────────────────────────────────────────────────────────┘
这张图揭示了 Harness 的三条流,它们的明确分离是可维护性的前提:
- 数据流(Context Flow):决定 LLM”看到什么”。System Prompt 提供静态约束,Context Manager 动态管理工作记忆,Tool 返回结果回写至 Context——信息在这条流上被构建、筛选、注入 LLM 的认知窗口。
- 控制流(Control Flow):决定 Agent”做什么”。Plan 将任务分解为可执行步骤,Tool Executor 完成具体操作——意图在这条流上被翻译为对外部世界的动作。
- 反馈流(Feedback Flow):决定 Agent”如何纠正”。Hook 在关键节点捕获执行结果,把质量信号、错误信息、人类判断反馈回系统——偏差在这条流上被识别并触发修正。
三流混杂的 Harness 在调试时无法追溯问题来源——“是信息不对,还是决策不对,还是执行没纠正?“这个问题本身就回答不了。三流分离是维护性的架构前提,不是优化选项。
2.2 各构件职责边界
| 构件 | 职责 | 主要影响的流 | 本书深化章节 |
|---|---|---|---|
| System Prompt | 定义角色、能力边界、行为准则;静态约束 LLM 的解空间 | 数据流(静态) | 第七章 |
| Context Manager | 管理工作记忆:信息分层、动态更新、压缩(Compaction) | 数据流(动态) | 第八章 |
| Plan | 显式建模解路径,提前识别不可行方案,是人机协作的自然接口 | 控制流 | 第九章 |
| Tool | 与外界交互,扩展 Agent 的可达状态边界;每个 Tool 是操作算子 | 控制流 | 第十章 |
| Hook | 多层反馈回路的工程实现:行动前拦截、行动后检验、任务终止验收 | 反馈流 | 第十一章 |
Human on the Loop 未单独列为构件,因为它不是一个可以被代码实例化的模块——它是贯穿全系统的设计原则:在数据流的 Plan 展示处、控制流的不可逆操作前、反馈流的异常升级点,都必须为人类留有介入接口。这一原则的完整讨论见第十五章。
Skill 同样不列为核心构件,而是在第三部分(第十三章)以「Harness 的持续改进机制」独立展开——它不是单任务 Harness 的内置模块,而是在组织级将跨任务经验系统化积累、驱动 Harness 持续演化的知识层。评估(第十二章)揭示系统当前行为的分布形状,Skill 将评估识别的模式转化为可复用知识,沉淀进 Harness 的长期演化。
2.3 一个可以直接实现的 Reference Architecture
下面是最小可行 Harness 的代码骨架。这不是生产就绪的实现,而是一个概念脚手架——展示五个构件如何协作,以及三条流在代码层面如何体现。后续每个构件章节,都是对这段骨架中某个方法的深化。
class MinimalHarness:
"""最小可行 Harness 的参照骨架。
三条流在此处的体现:
- 数据流:build_context() → LLM 推理 → context.update()
- 控制流:model.plan() → model.next_action() → tool_executor.execute()
- 反馈流:hook_engine.run_*() → 信号回写至 context/plan
"""
def run(self, task: str) -> Result:
# ── 数据流 ①:构建初始上下文 ─────────────────────────────
# System Prompt 在 context_manager 初始化时注入(静态约束)
# 任务规范、相关文档、当前状态——按信息密度分层
context = self.context_manager.build(task)
# ── 控制流 ①:规划阶段 ────────────────────────────────────
# Plan 将任务显式建模为可执行步骤序列
# 这是人类监督的最优介入点:行动发生之前
plan = self.model.plan(context)
# ── 反馈流 ①:Pre-Plan Hook ───────────────────────────────
# 在任何工具调用之前,验证计划的合理性
# 可以是自动检查(预算估算、风险评估)或请求人工确认
hook_result = self.hook_engine.run_pre_plan(plan)
if hook_result.requires_human_approval:
plan = self.await_human_approval(plan) # Human on the Loop
# ── 核心执行循环 ──────────────────────────────────────────
while not plan.is_complete():
# 控制流 ②:选择下一步行动
action = self.model.next_action(context, plan)
# 反馈流 ②:PreToolUse Hook(行动前拦截)
# 权限检查、输入验证、危险模式检测
pre_result = self.hook_engine.run_pre_tool(action)
if pre_result.blocked:
plan.record_blocked(action, pre_result.reason)
continue # 跳过,让 LLM 在下一轮选择替代行动
# 控制流 ③:执行工具
tool_result = self.tool_executor.execute(action)
# 反馈流 ③:PostToolUse Hook(行动后检验)
# 输出格式验证、错误检测、可选的语义质量评估
post_result = self.hook_engine.run_post_tool(action, tool_result)
# 数据流 ②:将结果回写上下文
# Context Manager 负责:分层存储、信息密度评估、超限时压缩
context.update(tool_result, quality_signal=post_result)
# 控制流 ④:根据反馈更新计划
# 计划不是合同——意外结果应触发动态修正,而非强行继续
plan.update(post_result)
# 反馈流 ④:成本熔断(防止发散)
if context.token_usage > self.budget.hard_limit:
return self.escalate_to_human("Token budget exceeded")
# ── 反馈流 ⑤:Stop Hook(任务终止验收) ──────────────────
# 发现问题成本最高的节点——在此之前的 Hook 是预防,此处是验收
stop_result = self.hook_engine.run_stop(context, plan)
if not stop_result.passed:
# 质量未达标:可以触发重试、降级、或升级人工
return self.handle_quality_failure(stop_result)
return Result(success=True, output=stop_result.output)
读这段骨架的方式:
- 每个
hook_engine.run_*()调用都是反馈流的一个节点——这是第十一章要深化的内容 context.update()和context_manager.build()是数据流的核心——这是第八章要深化的内容plan.update()和model.next_action()是控制流的核心——这是第九章(Plan)和第十章(Tool)要深化的内容await_human_approval()和escalate_to_human()是 Human on the Loop 的接口——这是第十五章要深化的内容
2.4 三流分离的工程含义
三流分离不只是架构上的整洁——它在调试、迭代、故障隔离三个维度上有直接的工程价值。
调试时的问题归因
当 Agent 表现异常时,三流分离把”为什么出错”这个开放问题切分为三条可独立检查的线索:
| 症状 | 首先检查 | 对应的流 |
|---|---|---|
| Agent 做了”正确的事”,但方向错了 | Context 中的信息是否准确、充分 | 数据流 |
| Agent 理解了任务,但选择了错误的行动序列 | Plan 的粒度和约束是否合理 | 控制流 |
| Agent 走错路了,但没有纠正 | Hook 的覆盖节点和信号质量 | 反馈流 |
| 三者都看不出问题 | System Prompt 的约束是否激励相容 | 数据流(静态) |
迭代时的变更隔离
三流分离使 Harness 的迭代可以精确定向,而不会引入意外的交叉影响:
- 质量问题 → 调整 Hook 传感器的覆盖范围和信号精度(反馈流)
- 成本问题 → 优化 Context 的分层策略和压缩时机(数据流)
- 任务失败率高 → 检查 Plan 的粒度设计和 Tool 的权限配置(控制流)
故障模式的独立性
单条流的故障不会必然导致其他流崩溃。例如,反馈流的 Hook 设计缺陷(漏掉了某个错误检测节点)不会拖垮数据流——Context 仍然正常构建,只是系统少了一个纠错机会。这种故障独立性是 Harness 可以渐进改进的前提:可以在生产环境中逐步加强反馈流,而不必重构整个系统。
2.5 约束分类矩阵:三个设计维度
三流分离回答的是约束的流向归属——信息在哪条流上发挥作用;但要落到具体约束的设计,还需要另外三个维度:时机(何时作用)、性质(如何建立)、强度(如何执行)。三流是横轴,这三个维度是纵轴——两者交叉构成本节的约束分类矩阵。
维度一:时机(何时介入)
| 事前约束(Pre) | 事后约束(Post) | |
|---|---|---|
| 含义 | 在行动发生之前施加限制或引导 | 在行动完成之后检验或评估 |
| 典型机制 | System Prompt、Plan、Pre-Hook | Post-Hook、Stop Hook |
| 成本结构 | 预防成本低,但预测难度高 | 纠正成本已发生,但信息更完整 |
| Harness 原则 | 越关键的约束越应前置 | 形式化验证作为事后安全网 |
维度二:性质(如何建立)
| 静态约束(Static) | 动态约束(Dynamic) | |
|---|---|---|
| 含义 | 设计时确定,运行时不变 | 运行时根据当前状态构建 |
| 典型机制 | System Prompt、静态文档注入 | Context 动态层、Hook 传感器输出、Plan |
| 灵活性 | 低(无法适应运行时状态变化) | 高(可响应工具结果、环境变化) |
| 认知负担 | 集中在设计期,维护时需版本管理 | 分散到运行期,需持续保证信息新鲜度 |
维度三:强度(如何执行)
| 硬约束(Hard) | 软约束(Soft) | |
|---|---|---|
| 含义 | 确定性执行:违反即阻断,不依赖概率判断 | 概率性评估:输出质量信号,供后续决策参考 |
| 典型机制 | 权限拦截、代码 Linter、格式校验、测试套件 | LLM-as-judge、语义评分、风格审查 |
| 成本 | 低(规则计算,毫秒级) | 高(LLM 调用,成本约为硬约束的 100-1000 倍) |
| 确定性 | 高(结果可重现) | 低(结果概率性,需多次采样校准) |
各机制在三个维度上的定位:
| 约束机制 | 时机 | 性质 | 强度 |
|---|---|---|---|
| System Prompt | 事前 | 静态 | 软 |
| 静态 Context(任务规范、文档) | 事前 | 静态 | 软 |
| 动态 Context(工具返回、状态更新) | 事前(下一步前) | 动态 | 软 |
| Plan + 人工确认节点 | 事前 | 动态 | 硬(人类介入) |
| Pre-Hook(权限拦截、格式验证) | 事前 | 动态 | 硬 |
| Post-Hook(计算型:Linter、测试) | 事后 | 动态 | 硬 |
| Post-Hook(语义型:LLM-as-judge) | 事后 | 动态 | 软 |
| Stop Hook | 事后 | 动态 | 软 + 硬(分层) |
时机 × 强度矩阵:设计决策的核心权衡
这两个维度的交叉揭示了约束设计的核心权衡:
事前(Pre) 事后(Post)
┌────────────────────────┬──────────────────────────┐
硬约束 │ Pre-Hook 权限拦截 │ 计算型 Post-Hook │
│ ✓ 成本最低 │ ✓ 成本已发生,但确定性高 │
│ ✓ 确定性高 │ → 作为质量门控(Safety Net)│
│ → 用于临界安全约束 │ │
├────────────────────────┼──────────────────────────┤
软约束 │ System Prompt / Plan │ 语义型 Post-Hook │
│ ✓ 方向引导(低成本) │ ✗ 成本最高 │
│ ✗ 概率性,不可保证 │ ✗ 确定性最低 │
│ → 用于质量风格约束 │ → 仅用于无法形式化的语义质量│
└────────────────────────┴──────────────────────────┘
设计原则:硬约束前置,软约束兜底。Pre×Hard 成本最低、确定性最高,应承载最关键的安全与格式约束;Post×Soft 成本最高、确定性最低,应只用于无法被形式化的语义质量判断,且必须与 Pre×Hard 配合,不可单独使用。一旦某个约束可以被形式化,它就应该从软约束迁移到硬约束、从事后迁移到事前——这是 Harness 迭代成熟的方向。
承上启下:Q/T/C 分析语言的预告
本书使用**三轴约束(Q/T/C)**作为贯穿全书的分析语言——质量(Quality)、时间(Time)、成本(Cost)。每一个 Harness 设计决策最终都在这三个维度上产生权衡。第三章将正式引入 Q/T/C 框架,包括可行解集的数学定义、六个理论视角,以及具体场景下的指标体系。在此之前,只需记住这个分析语言存在——后续章节在描述设计决策的代价时,都会在这个坐标系中表达。
章末案例剖析
导言中的客服 Agent 在三天内产生了三个独立的失败:回复了不该回复的邮件、删除了未读邮件(误以为是已处理)、在无限循环中消耗了 $2000 的 API 成本。本章建立的架构蓝图为这组失败提供了系统化的归因语言——三个失败分别对应三条流的三处缺位,并且各自落在 §2.5 约束分类矩阵的不同坐标上。
失败一:错误回复(数据流缺失)
症状:Agent 回复了促销邮件、系统通知和已有人工跟进的工单,触发了客户投诉。
根因:没有任何 Context 约束定义”哪些邮件需要 Agent 处理”。Agent 的决策完全依赖 System Prompt 中的模糊指令——“帮助处理客服邮件”——这在 LLM 的解空间中几乎涵盖了所有操作。任务边界没有被数据流精确编码,模型便用训练时学到的”客服应该回复邮件”模式填补了这个空白——这正是第一章所描述的分布外填充。
修复方案:在数据流上建立两层约束:
- System Prompt(事前 × 静态 × 软):明确定义排除条件——“不处理发件人域名为 @noreply、@no-reply 的邮件;不处理标题包含’自动回复’/‘Out of Office’的邮件;不处理已有工单标签’assigned’的邮件”
- Pre-Hook(事前 × 动态 × 硬):在 send_reply() 工具调用前运行规则引擎,检查收件人是否在排除列表、工单状态是否已有人工负责人;任何一项命中则硬性拦截,并将原因记入 Context
软约束(System Prompt)提供方向性引导,硬约束(Pre-Hook 规则引擎)提供确定性保障——两者缺一不可。仅靠 System Prompt,模型的自信度误校准仍会在边界案例上产生错判;仅靠 Pre-Hook,又不可能预先枚举所有排除场景。
失败二:误删邮件(控制流缺失)
症状:Agent 将未读但已手动标注”待处理”的邮件删除,造成数据丢失。
根因:Tool 设计没有区分操作的不可逆性。mark_as_read()(可逆、代价低)与 delete_email()(不可逆、代价高)在 Agent 的工具调用视角中地位相同——都不过是一个函数调用。Plan 也没有对不可逆操作做任何特殊标注。于是模型选择了最”高效”的路径完成任务:删除一步到位,优于标记加跟进——这是完全符合训练目标的行为。
修复方案:在控制流上建立不可逆性分级:
- Tool 权限分级(事前 × 静态 × 硬):将工具分为两级。一级工具(读取、标记、分类):Agent 可自主调用;二级工具(删除、批量操作、外发邮件):调用前必须触发确认流程
- Pre-Hook + Human on the Loop(事前 × 动态 × 硬):二级工具的 Pre-Hook 暂停执行,向人工界面推送一条确认请求,包含:拟执行操作、影响范围、可选的替代操作。人工在 30 秒内不响应,默认拒绝并记录原因
- Plan 中的不可逆性标注(事前 × 动态 × 软):Plan 生成时对包含二级工具的步骤显式标注”[不可逆]“,便于人类在 Plan 确认阶段预先识别风险,而非在执行时才被 Hook 打断
这三层约束形成纵深梯度:Plan 标注让人类在行动前看到风险,Pre-Hook 在执行前强制暂停,Tool 权限分级在架构层阻止越权调用。三层叠加之下,删除这类操作在任何一层都不可能”意外通过”。
失败三:成本爆炸(反馈流缺失)
症状:Agent 陷入”读取邮件列表 → 尝试处理 → API 报错 → 重试读取”的无限循环,三天消耗 $2000。
根因:反馈流完全缺失。API 报错本是一个高质量的反馈信号,却没有任何 Hook 捕获它并将其转化为”停止或升级”的决策。Agent 把重试作为默认响应(训练时的”遇到错误就重试”模式),而系统既没有累计成本的记忆机制,也没有向人类升级的通道。这个无限循环从架构上看是必然的——缺乏反馈流的系统在持续错误下,唯一能做的就是继续执行原计划。
修复方案:在反馈流上建立三道防线:
- 错误信号结构化(PostToolUse Hook,事后 × 动态 × 硬):每次工具调用后记录返回码、错误类型、重试次数。连续相同错误超过 3 次,触发”错误模式识别”——将错误类型写入 Context 的高优先级区域,迫使下一轮 LLM 推理时必须”看到”这个错误
- 成本熔断(全局 Hook,事后 × 动态 × 硬):在执行循环的每一轮结束时检查累计 token 消耗。设置两道阈值:软阈值(如 200)直接终止执行,写入终止原因,等待人工介入
- 升级通道(Stop Hook,事后 × 动态 × 硬 + 软):任务非正常终止时,Stop Hook 生成结构化的失败报告(包含:终止原因、已处理邮件数、累计成本、建议的下一步操作),通过预设的人工通知渠道发送。人工获得足够信息来决定是否、如何重启任务
三个失败在约束分类矩阵中的坐标
| 失败 | 缺位的流 | 时机 | 性质 | 强度 | 修复的核心约束 |
|---|---|---|---|---|---|
| 错误回复 | 数据流 | 事前 | 静态 + 动态 | 软 + 硬 | System Prompt 排除规则 + Pre-Hook 规则引擎 |
| 误删邮件 | 控制流 | 事前 | 静态 + 动态 | 硬 | Tool 权限分级 + Pre-Hook 人工确认 |
| 成本爆炸 | 反馈流 | 事后 | 动态 | 硬 | PostToolUse 错误捕获 + 成本熔断 + Stop Hook 升级 |
这张表揭示了一个在 §2.5 约束矩阵中已经预示的规律:三个失败都对应硬约束的缺位——前两个发生在事前(数据流上的 Pre-Hook、控制流上的权限分级),第三个发生在事后(反馈流上的熔断机制与升级通道)。能形式化的关键约束被遗留为”靠模型自觉”的软约束,正是这类系统性失败的共同结构性根因。
三个失败、三条流、三处缺位——Harness 不是一个整体”有/没有”的问题,而是三条流各自需要独立设计、独立验证的工程问题。任何一条流的缺位都无法被另外两条流的完善所补偿:客服 Agent 的数据流即便设计精良,也阻止不了反馈流缺失导致的成本爆炸。这是三流分离在失败模式上的对称推论——独立设计,独立失败,独立修复。