第二章 最小可行 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-HookPost-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 消耗。设置两道阈值:软阈值(如 50)触发告警并将成本信息注入Context;硬阈值(如50)触发告警并将成本信息注入 Context;硬阈值(如 200)直接终止执行,写入终止原因,等待人工介入
  • 升级通道(Stop Hook,事后 × 动态 × 硬 + 软):任务非正常终止时,Stop Hook 生成结构化的失败报告(包含:终止原因、已处理邮件数、累计成本、建议的下一步操作),通过预设的人工通知渠道发送。人工获得足够信息来决定是否、如何重启任务

三个失败在约束分类矩阵中的坐标

失败缺位的流时机性质强度修复的核心约束
错误回复数据流事前静态 + 动态软 + 硬System Prompt 排除规则 + Pre-Hook 规则引擎
误删邮件控制流事前静态 + 动态Tool 权限分级 + Pre-Hook 人工确认
成本爆炸反馈流事后动态PostToolUse 错误捕获 + 成本熔断 + Stop Hook 升级

这张表揭示了一个在 §2.5 约束矩阵中已经预示的规律:三个失败都对应硬约束的缺位——前两个发生在事前(数据流上的 Pre-Hook、控制流上的权限分级),第三个发生在事后(反馈流上的熔断机制与升级通道)。能形式化的关键约束被遗留为”靠模型自觉”的软约束,正是这类系统性失败的共同结构性根因。

三个失败、三条流、三处缺位——Harness 不是一个整体”有/没有”的问题,而是三条流各自需要独立设计、独立验证的工程问题。任何一条流的缺位都无法被另外两条流的完善所补偿:客服 Agent 的数据流即便设计精良,也阻止不了反馈流缺失导致的成本爆炸。这是三流分离在失败模式上的对称推论——独立设计,独立失败,独立修复。