work hard work smart

专注于AI+Java后端开发。 不断总结,举一反三。
  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

Harness 工程:驾驭 AI Agent 的工程化艺术

Posted on 2026-08-01 12:37  work hard work smart  阅读(108)  评论(0)    收藏  举报

Harness 工程:驾驭 AI Agent 的工程化艺术

当 AI Agent 从玩具走向生产,你会发现:模型本身只占 20% 的工作量,剩下 80% 都是"如何驾驭它"。本文以「智能旅行助手」为例,聊聊什么是 Harness 工程,以及这套工程化基础设施如何让 Agent 可靠地跑起来。


一、什么是 Harness(驾驭层)?

1.1 从"调 API"到"驾驭 Agent"

早期做 AI 应用,核心就是一行 client.chat.completions.create(...)——模型是全部。

但当你试图构建一个真正可用的 Agent 系统,比如一个智能旅行助手,用户说"帮我规划 7 天云南行程,两个人,预算 1 万,帮我订机票酒店,再出一份费用清单"——你会发现问题远不止"调模型"这么简单:

  • Agent 怎么知道当前用户是谁、偏好靠窗还是靠过道?
  • 工具调了 200 次还没结果,怎么防止死循环?
  • 上下文窗口炸了怎么办?
  • 多个 Agent 之间怎么协作、怎么隔离?
  • 沙箱挂了,用户就问不了"附近有什么好吃的"了?
  • 用户喜欢海景房、素食这种偏好,怎么自动记住?
  • 用户自己上传的"私人导游联系方式"技能包,怎么热插拔?

这些模型不解决但又必须解决的问题,催生了 Harness 这一层。

Harness(驾驭层) 是包裹在 AI Agent 外围的一整套工程化基础设施。它不是模型,不是提示词,而是让模型可靠地、可控地、可扩展地运行起来的所有工程机制的总和。

一句话概括:

模型提供智能,Harness 提供可靠性。

Harness 简史:从马具到测试工具到 AI 驾驭层

Harness 这个词本身,经历了一场跨越 60 年的语义迁移。

🐴 词源:马具(Harness)

Harness 的本意是"马具"——套在烈马身上的缰绳、马鞍和围栏。马本身有力气、能奔跑,但没有马具,骑手无法控制方向、无法制动、甚至可能被甩下来。马具不改变马的能力,但决定了马能不能被安全地使用。

这个隐喻精准地映射到了今天的 AI Agent:模型是烈马,Harness 是驾驭层。

🔧 1960s–1979:测试线束(Test Harness)

"Harness" 作为工程术语,最早出现在 NASA 阿波罗计划的飞行软件测试中。Margaret Hamilton 领导的 MIT 仪器实验室团队,在为登月飞船编写飞行软件时,需要一套基础设施来隔离测试各个模块——这套基础设施被称为"测试线束"(test harness),包括桩程序(stub)、驱动程序(driver)、测试脚本和数据夹具(fixture)。

1979 年,Glenford J. Myers 在其里程碑式的著作《The Art of Software Testing》中,正式系统化了 test harness 的概念,将其定义为"支撑自动化测试执行和验证的全部基础设施集合"。从此,test harness 成为软件工程中测试领域的标准术语。

📊 2010s:评估线束(Eval Harness)

随着机器学习的兴起,test harness 的概念被引入模型评估领域——用于标准化地跑 benchmark、收集指标、对比模型表现。这个阶段的"线束"主要关注的是"怎么科学地给模型打分"。

🤖 2025–2026:Agent 线束(Agent Harness)

"Harness" 从测试领域跳到 AI Agent 领域,关键的推动者是 Anthropic

时间 事件 意义
2025 年 11 月 Anthropic 发布《Effective harnesses for long-running agents 首次将"harness"用于 AI Agent 语境,提出 initializer-agent + coding-agent 模式,解决 Agent 跨上下文窗口长时间运行的问题
2026 年 2 月 Anthropic 发布《Building a C compiler with a team of parallel Claudes 展示了多 Agent 并行的 harness 设计,多个 Claude 在统一驾驭层下协作
2026 年 3 月 LangChain 发布《The Anatomy of an Agent Harness 系统拆解 Agent Harness 的 11 个组件:编排循环、工具、记忆、上下文管理、文件系统、沙箱等
2026 年 3 月 Anthropic 发布《Harness design for long-running application development 提出三 Agent harness 架构:Planner → Generator → Evaluator,并开源配套代码库
2026 年 3 月 arXiv 论文《Natural-Language Agent Harnesses 学术界首次对 agent harness 进行形式化定义
2026 年 4 月 LangChain 通过 harness engineering 将 DeepAgents 在 Terminal Bench 2.0 上的得分从 52.8% 提升到 66.5%,排名从第 30 名跃升至 Top 5 关键转折点——证明不换模型、只改 harness 就能大幅提升 Agent 表现
2026 年 4 月 Martin Fowler 发表《Harness engineering for coding agent users 将 agent harness 比喻为"控制论调速器"(cybernetic governor),结合前馈和反馈来调节代码库
2026 年 5 月 行业全面跟进,MongoDB、OpenAI、各路学者纷纷发表 harness engineering 相关文章 Harness Engineering 正式成为 AI Agent 领域的核心工程范式

关键转折点:LangChain 的 Terminal Bench 实验

LangChain 团队的实验是 harness engineering 成为行业共识的标志性事件。他们没有换模型、没有改提示词,仅仅通过优化 harness 的六个方面——工具门控(tool gating)、自验证(self-verification)、追踪(tracing)、上下文优化、重试策略、终止规则——就把同一个 Agent 的表现提升了 13.7 个百分点。

这有力地证明了一个论断:

Agent 的可靠性,更多地由 harness 决定,而不是由模型决定。

这也解释了为什么 2026 年的 AI 工程界出现了"Harness Engineering"这个独立学科——它不是提示词工程的翻版,也不是传统的后端工程,而是专门解决"如何让 Agent 在真实世界中可靠运行"的工程实践。

1.2 Harness 的核心组成

一个完整的 Harness 工程体系通常包含以下层次:

┌──────────────────────────────────────────────────────────┐
│                    用户请求 / UI                          │
└────────────────────────┬─────────────────────────────────┘
                         ▼
┌──────────────────────────────────────────────────────────┐
│                  中间件管线(Middleware Pipeline)          │
│  ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐    │
│  │上下文注入 │→│技能同步  │→│限流保护  │→│记忆更新  │    │
│  └──────────┘ └──────────┘ └──────────┘ └──────────┘    │
└────────────────────────┬─────────────────────────────────┘
                         ▼
┌──────────────────────────────────────────────────────────┐
│              Agent 核心循环(Model ↔ Tools)              │
│  ┌──────────┐ ┌──────────┐ ┌──────────┐                 │
│  │ LLM 调用 │←→│ 工具执行 │←→│ 状态管理 │                 │
│  └──────────┘ └──────────┘ └──────────┘                 │
└────────────────────────┬─────────────────────────────────┘
                         ▼
┌──────────────────────────────────────────────────────────┐
│                  后端抽象层(Backend)                     │
│  ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐    │
│  │沙箱后端  │ │本地后端  │ │组合后端  │ │弹性后端  │    │
│  └──────────┘ └──────────┘ └──────────┘ └──────────┘    │
└────────────────────────┬─────────────────────────────────┘
                         ▼
┌──────────────────────────────────────────────────────────┐
│              持久化层(Store / Checkpoint)                │
│  ┌──────────┐ ┌──────────┐ ┌──────────┐                 │
│  │对话状态  │ │用户偏好  │ │技能存储  │                 │
│  └──────────┘ └──────────┘ └──────────┘                 │
└──────────────────────────────────────────────────────────┘

上图展示了 Harness 的五层架构。下面逐一说明每一层的职责:

① 中间件管线(Middleware Pipeline)

中间件是 Harness 中最活跃的一层。它像流水线一样,在 Agent 执行的各个阶段插入自定义逻辑:

  • 生命周期钩子before_agent(启动前注入上下文)、after_agent(回复后更新记忆)、wrap_model_call(每次 LLM 调用前后拦截修改)
  • 典型中间件:上下文注入、技能同步、记忆更新、摘要压缩、调用限流

中间件的核心价值是关注点分离——Agent 不需要"知道"怎么管理记忆,Harness 自动替它做。

② Agent 核心循环(Model ↔ Tools)

这一层是 Agent 的"大脑",由框架(如 LangGraph)驱动,负责:

  • LLM 调用:把用户消息 + 系统提示词 + 工具描述发给模型,获取决策
  • 工具执行:模型决定调用哪个工具(查航班、写文件、执行脚本),框架负责执行并把结果喂回模型
  • 状态管理:维护对话历史、中间状态、检查点,支持暂停/恢复/回溯

核心循环本身不处理横切关注点——这些全交给中间件管线。

③ 后端抽象层(Backend)

Agent 需要执行代码、读写文件,但不同场景对执行环境的要求差异很大。后端抽象层提供统一的文件/命令接口,底层路由到不同的执行环境:

  • 沙箱后端(OpenSandbox):代码在隔离容器中执行,安全但不一定可用
  • 本地后端(LocalShell):直接在本机执行,快但无隔离
  • 弹性后端(Resilient):沙箱优先,不可用时自动降级到本地
  • 组合后端(Composite):按路径路由——用户偏好存本地,代码执行走沙箱

后端抽象层让 Agent 用同一套 API 操作文件,完全不感知底层是沙箱还是本地。

④ 持久化层(Store / Checkpoint)

Agent 的记忆不能只存在内存里——重启就丢了。持久化层负责把状态存到可靠的存储中:

  • 对话状态(Checkpoint):每轮对话的完整快照,支持跨重启恢复、Human-in-the-Loop 暂停/继续
  • 用户偏好(Store):长期记忆,如"喜欢靠窗座位、素食",按用户隔离存储
  • 技能存储:用户自定义技能包(如私人导游联系方式),跨会话持久化

存储后端可以是 MongoDB、文件系统、内存——框架提供统一接口,具体实现可替换。

⑤ 声明式配置层(贯穿所有层)

除了上面四个"运行时"层,Harness 还有一个隐形的第五层:声明式配置。它贯穿所有层,用 YAML 文件定义:

  • 子 Agent 是谁、能做什么(name / description / system_prompt
  • 每个子 Agent 用哪些工具(tools,运行时动态解析)
  • 每个子 Agent 配哪些中间件(middleware,差异化策略)
  • 安全边界和人工介入点(interrupt_on

声明式配置让新增一个 Agent 变成"写一个 YAML 文件",而不是"改一堆 Python 代码"。


接下来,我们用智能旅行助手的真实实现,逐一拆解每一层是怎么落地的。


二、中间件管线:Harness 的灵魂

2.1 为什么需要中间件?

回到那个旅行场景:用户说"帮我分析一下昆明、大理、丽江的酒店价格趋势,最好有对比图表"。

在行程规划师 Agent 开始工作之前,其实有很多"前置条件"需要准备好:

  1. 系统需要知道当前用户是谁、出行人数、预算上限(上下文注入)
  2. 数据分析技能包是否已同步到沙箱(技能同步)
  3. 用户之前偏好海景房、素食这些偏好需要加载(记忆恢复)
  4. 任务需要被拆解为可跟踪的步骤(Todo 管理)

Agent 完成回复之后,还有"后置动作":

  1. 提取对话中提到的目的地和酒店偏好,自动更新偏好文件(记忆更新)
  2. 统计本次调用消耗了多少 token(计量)
  3. 压缩过长的上下文——景点信息动辄几万字(摘要)

如果把这些逻辑全塞进 Agent 的提示词里,系统会迅速膨胀到不可维护。中间件就是把这些横切关注点(cross-cutting concerns)从业务逻辑中抽离出来的机制。

2.2 智能旅行助手的中间件栈

在主 Agent 的创建流程中,我们构建了一个 8 层中间件栈:

main_middleware = [
    # 1. 自定义 Todo 管理(约束工作流程)
    create_custom_todo_middleware(),

    # 2. 用户上下文注入(身份识别)
    ContextInjectionMiddleware(),

    # 3. 预置技能同步(增量同步到沙箱)
    SkillsSyncMiddleware(sandbox_backend),

    # 4. 用户技能恢复(恢复用户创建的技能)
    UserSkillsRestoreMiddleware(sandbox_backend, USER_SKILLS_DIR),

    # 5. 上下文摘要(自动压缩 + 主动压缩工具)
    build_summarization_middleware(backend, SUMMARY_MODEL),

    # 6. 自动记忆更新(LLM 提取实体并持久化)
    MemoryUpdateMiddleware(model=SUMMARY_MODEL),

    # 7. 模型调用上限(防止无限循环)
    ModelCallLimitMiddleware(run_limit=50),

    # 8. 工具调用上限(防止资源耗尽)
    ToolCallLimitMiddleware(run_limit=200),
]

每个中间件都实现了统一的钩子接口:

钩子 触发时机 典型用途
before_agent / abefore_agent Agent 开始运行前 注入上下文、同步文件
after_agent / aafter_agent Agent 完成回复后 自动记忆更新
wrap_model_call / awrap_model_call 每次 LLM 调用前后 拦截/修改请求、动态过滤工具

执行时序如下:

用户请求
  │
  ▼
[before_agent] ContextInjectionMiddleware  → 注入 user_id、预算、出行人数
[before_agent] SkillsSyncMiddleware        → 同步预置技能到沙箱
[before_agent] UserSkillsRestoreMiddleware → 恢复用户私人导游联系方式等技能
  │
  ▼
┌─ Agent Loop ─────────────────────────────────┐
│  [wrap_model_call] CustomTodoMiddleware       │
│  [wrap_model_call] SummarizationMiddleware    │
│         ↓                                     │
│       LLM 调用                                │
│         ↓                                     │
│  [工具调用] write_todos / compact_conversation │
└───────────────────────────────────────────────┘
  │
  ▼
[after_agent] MemoryUpdateMiddleware → 提取"偏好海景房"等,更新偏好文件
[after_agent] 限流中间件统计调用次数

2.3 真实案例:上下文注入中间件

ContextInjectionMiddleware 为例,看看一个中间件长什么样:

class ContextInjectionMiddleware(AgentMiddleware):
    """将 runtime.context 中的用户信息注入到对话开头。"""

    def before_agent(self, state, runtime):
        ctx = getattr(runtime, "context", None)
        if ctx is None:
            return None
        user_id = getattr(ctx, "user_id", None)
        username = getattr(ctx, "username", None) or user_id

        notice = (
            f"【系统上下文】\n"
            f"当前用户 user_id: {user_id}\n"
            f"当前用户 username: {username}\n"
            f"用户偏好文件路径: /memories/{user_id}/preferences.md\n"
            f"\n请首先使用 read_file 读取上述偏好文件了解用户偏好。"
        )
        return {"messages": [SystemMessage(content=notice)]}

就这么简单的一段代码,解决了"Agent 不知道当前是谁在说话"的问题。当用户问"帮我订去昆明的机票"时,Agent 不需要调用任何工具就能知道用户身份,直接去读偏好文件——里面写着"偏好靠窗座位、素食、海景房"。

2.4 真实案例:自动记忆更新中间件

MemoryUpdateMiddleware 更复杂一些——它在 Agent 回复完成后自动触发,用 LLM 提取对话中的目的地和偏好信息,合并写入用户的偏好文件:

用户:"我下个月想去云南玩,喜欢安静的海边酒店,最好能提供素食"
      ↓ Agent 正常回复,规划了行程
      ↓ [after_agent 钩子触发]
      ↓ MemoryUpdateMiddleware 启动
      ↓   1. 判断是否为有意义的旅行交互(跳过"你好""在吗")
      ↓   2. 调用 SUMMARY_MODEL 提取实体
      ↓      → {"destinations": ["云南"], "preferences": ["海边酒店", "素食"]}
      ↓   3. 读取 /memories/zhangsan/preferences.md
      ↓   4. 合并新的偏好到 accommodation 和 diet 字段
      ↓   5. 写回 store
      ↓
用户下次说"帮我订酒店"时,Agent 已经"记得"他要海景房和素食

Agent 不需要主动维护记忆——Harness 替它做了。

这就是 Harness 的哲学:让 Agent 专注于智能,让框架兜底可靠性。


三、后端抽象层:让 Agent"脚踏实地"

3.1 为什么需要后端抽象?

行程规划师 Agent 需要执行 Python 脚本做路线优化——安装 scipynetworkx 跑图论算法。但在企业环境中,你不能让 Agent 直接在宿主机上跑不可信的代码——太危险。你需要一个沙箱

但沙箱又有问题:

  • 启动慢,可能不可用
  • 有些操作(如生成费用报告)不需要沙箱
  • 用户偏好等持久化数据不该放在沙箱里

这就需要后端抽象——让 Agent 用统一的接口操作文件系统,底层路由到不同的执行环境。

3.2 智能旅行助手的四层后端架构

CompositeBackend(路径路由器)
├── /AGENTS.md, /docs/      → OpenSandbox(沙箱默认路由)
├── /memories/               → FilesystemBackend(本地持久化,按 user_id 隔离)
├── /persisted-skills/       → FilesystemBackend(本地持久化,私人导游联系方式等)
└── /download/               → FilesystemBackend(费用报告下载目录)

ResilientBackend(弹性后端)
├── 沙箱可用 → 沙箱执行(路线优化、数据分析)
└── 沙箱不可用 → 自动降级到本地执行(简单行程规划)

弹性后端ResilientBackend)是一个有意思的设计:

class ResilientBackend(LocalShellBackend):
    """启动时不阻塞,沙箱在后台异步连接,不可用时自动降级。"""

    def __init__(self, root_dir, *, sandbox_factory=None, **kwargs):
        super().__init__(root_dir=root_dir, ...)
        self._sandbox_backend = None
        self._sandbox_lock = threading.Lock()

        # 后台线程连接沙箱,不阻塞启动
        if sandbox_factory is not None:
            t = threading.Thread(target=self._connect_sandbox, args=(sandbox_factory,))
            t.start()

    def execute(self, command, *, timeout=None):
        sb = self._sandbox_backend
        if sb is not None:
            try:
                return sb.execute(command, timeout=timeout)
            except Exception:
                pass  # 降级
        return super().execute(command, timeout=timeout)  # 本地执行

效果:系统启动时不卡在沙箱连接上;沙箱挂了,当地向导依然能在本地跑 API 查询推荐餐厅。用户几乎感知不到后端的切换。

3.3 组合后端:路径级路由

CompositeBackend 更进一步——它根据路径前缀把文件操作路由到不同的后端:

CompositeBackend(
    default=sandbox_backend,          # 默认走沙箱
    routes={
        "/memories/": FilesystemBackend(root_dir="/data/travel/memories"),
        "/persisted-skills/": FilesystemBackend(root_dir="/data/travel/persisted-skills"),
        "/download/": FilesystemBackend(root_dir="/data/travel/download"),
    },
)

这样设计的好处:

路径 路由目标 设计原因
/AGENTS.md 沙箱 智能体指引文件,需要与执行环境一致
/memories/ 本地文件系统 用户旅行偏好需要持久化,不受沙箱生命周期影响
/persisted-skills/ 本地文件系统 用户自定义技能需要跨会话持久化
/download/ 本地文件系统 费用报告需要用户可直接下载
其他路径 沙箱 临时文件、脚本执行等

对 Agent 来说,它看到的是一个统一的文件系统;但在底层,不同路径的数据被存储在了最合适的位置。


四、多 Agent 编排:声明式子 Agent

4.1 问题:单 Agent 的困境

让一个 Agent 同时处理行程规划、机票预订、当地推荐、应急处理、费用报告——提示词会膨胀到上万字,工具集混杂着二十几个工具,模型在"什么都要懂"的重压下反而什么都做不精。

4.2 解法:YAML 声明式定义

Harness 工程的另一个重要体现是子 Agent 的声明式管理。每个子 Agent 是一份 YAML 文件,而不是代码:

# trip_planner.yaml
name: trip-planner
description: >
  行程规划专家。负责目的地分析、路线规划、景点推荐和时间分配。
  当用户需求涉及"规划行程"、"推荐景点"、"路线安排"、"几天怎么玩"时委派。

tools:
  - scenic_spot_query
  - distance_calculator
  - weather_query
  - generate_visualization
  - web_search

skills:
  - /skills/trip-planning/

system_prompt: |
  你是旅行行程规划师,运行在隔离沙箱中。
  核心职责:收集信息 → 路线优化 → 生成行程方案 → 返回结论。

运行时,loader.py 自动扫描 YAML 目录、校验必填字段、将工具名模式匹配为实际工具对象:

raw_configs = load_subagent_configs()   # 扫描 *.yaml
subagents = resolve_subagent_tools(      # 解析工具引用
    raw_configs, available_tools, extra_middleware
)

新增一个子 Agent = 新建一个 YAML 文件。 不需要改代码、不需要重启服务。

4.3 五个专业子 Agent 的 Harness 差异化配置

不仅主 Agent 有中间件栈,每个子 Agent 也有自己专属的 Harness 配置:

子 Agent 摘要中间件 模型调用上限 工具调用上限 后端 策略原因
🗺️ 行程规划师 50 200 弹性后端 景点信息量大,需要反复查询对比、跑 Python 脚本
✈️ 机酒预订专家 20 50 弹性后端 + HITL 流程固定:查询→确认→下单,涉及支付需人工审批
🏮 当地向导 20 50 本地后端 即问即答,流程简短直接
🆘 旅途应急员 20 50 本地后端 应急处理讲究快速决策
📊 旅行报告官 50 200 本地后端 需要汇总大量消费数据生成可视化

核心规律:分析类任务配宽松限制 + 摘要中间件;操作类任务配严格限制、不配摘要。既保证灵活性,又避免资源浪费。

这种分层 Harness 设计让每个子 Agent 都有最适合自己的约束和辅助机制。


五、技能系统:可插拔的能力扩展

5.1 技能 vs 工具

在 Harness 工程中,"技能"和"工具"是两个不同层次的概念:

维度 工具(Tool) 技能(Skill)
粒度 单个函数调用 一组文件 + 操作手册 + 脚本
定义 Python 函数 / MCP 工具 目录(含 SKILL.md、脚本等)
加载 静态注册到 Agent 按需 read_file 渐进式加载
扩展 需要改代码 新增目录即可

比如"网页抓取"是一个技能——它包含一个 Python 脚本、一份使用说明(SKILL.md)、一个依赖清单。Agent 通过 read_file 读取 SKILL.md 后才知道怎么用这个技能。

5.2 技能同步中间件

SkillsSyncMiddleware 在每个 Agent 周期开始前,自动将本地 src/skills/ 下的技能文件增量同步到沙箱:

def _sync_files(self):
    for skill_dir in local_skills_dir.iterdir():
        for local_file in skill_dir.rglob("*"):
            local_hash = hashlib.md5(local_content).hexdigest()
            if self._last_hashes.get(cache_key) == local_hash:
                continue  # 未变化,跳过
            # 对比沙箱文件,有变化则上传
            self.backend.upload_files(files_to_upload)

检测到变化时,还会自动插入系统通知提醒 Agent 有新技能可用。

用户的私人技能(比如"私人导游联系方式列表")通过 UserSkillsRestoreMiddleware 在每次启动时恢复——这些技能按 user_id 隔离存储,不与其他用户共享。


六、记忆与状态管理:让 Agent"记住"

6.1 三层记忆体系

层次 存储内容 存储位置 生命周期 旅行场景示例
工作记忆 当前对话上下文 LangGraph State 单次对话 本轮讨论的景点和酒店
短期记忆 对话 Checkpoint MongoDB 跨重启 上次聊到一半的行程规划
长期记忆 用户偏好 FilesystemBackend 永久 靠窗座位、素食、海景房

6.2 自动上下文压缩

当对话超过 6 轮或上下文接近窗口上限时(行程规划场景尤其容易触发——景点信息动辄几万字),SummarizationMiddleware 自动触发压缩:

  • 完整历史保存到 /conversation_history/
  • 上下文中只保留摘要 + 最近几轮

Agent 也可以通过调用 compact_conversation 工具主动触发压缩——比如行程规划师在输出最终方案后,会主动压缩上下文,避免下一轮对话时"记着"几万字的历史景点列表。


七、安全护栏:Harness 的底线

Harness 工程的另一个关键职责是安全兜底

7.1 调用次数限制

ModelCallLimitMiddleware(run_limit=50)    # 最多 50 次 LLM 调用
ToolCallLimitMiddleware(run_limit=200)    # 最多 200 次工具调用

防止 Agent 陷入死循环或无限工具调用——比如行程规划师在"查景点→查路线→查天气→再查景点..."的循环里停不下来。

7.2 安全边界(AGENTS.md)

通过上传到沙箱的 AGENTS.md 定义不可逾越的红线:

  • 不修改 /AGENTS.md 本身
  • 不访问其他用户的 /memories/ 路径
  • 订单创建/支付操作必须经过机酒预订专家,不得绕过
  • 技能代码必须在沙箱内验证

7.3 Human-in-the-Loop

机票预订、酒店支付等涉及真金白银的操作,通过 interrupt_on 配置触发人工审批流程:

用户:"帮我订明天去昆明的机票"
  ↓
机酒预订专家查询航班 → 准备好订单详情
  ↓
[interrupt_on 触发] → 系统暂停执行
  ↓
用户确认:"确认,靠窗座位"
  ↓
真正执行下单

Agent 不能跳过这一步——这是 Harness 的硬性约束,不是提示词的"建议"。


八、Harness 工程的哲学

回顾整个智能旅行助手的实现,Harness 工程的核心哲学可以总结为三条:

8.1 关注点分离

Agent 负责"智能",Harness 负责"可靠性"。

行程规划师不需要操心"用户是谁""偏好怎么存""上下文超了怎么办"——这些都是 Harness 的事。Agent 只需要专注于分析景点、优化路线、给出方案。

8.2 声明式优于命令式

新增一个子 Agent = 写一份 YAML,不是一行代码。

子 Agent 的定义、工具绑定、中间件配置都是声明式的。这意味着:

  • 业务人员可以参与 Agent 的配置(改 YAML 比改 Python 容易)
  • 配置可以独立于代码迭代
  • 系统更容易理解和审计

8.3 优雅降级

沙箱挂了,用户依然可以问"附近有什么好吃的"。

ResilientBackend 的设计体现了 Harness 工程的一个重要原则:永远有 Plan B

  • 沙箱不可用?→ 行程规划师降级为本地简单规划
  • MCP 工具加载失败?→ 降级为空工具列表
  • MongoDB 不可用?→ 降级到内存存储

每一层都有自己的降级方案,确保系统在任何情况下都能给出响应。


九、Harness 工程的本质

说了这么多,Harness 工程到底是什么?

回到最初的那个比喻:模型是一匹烈马,聪明但不可控。Harness 就是那套缰绳、马鞍和围栏——它不改变马的智能,但让马安全地、高效地为你工作。

在 AI Agent 从 demo 走向生产的过程中,Harness 工程是最容易被忽视、却又最决定成败的部分。

它不是一个框架、不是一个库,而是一种工程思维

  • 承认模型的不确定性,用工程手段兜底
  • 承认系统的复杂性,用分层架构解耦
  • 承认需求的多变性,用声明式配置应对

如果你正在构建一个 Agent 系统,不妨问自己:

"我的 Harness 够厚吗?"

不是要你堆砌功能,而是要你思考:那些模型不解决但又必须解决的问题,你交给谁了?


本文基于 DeepAgents 框架的智能旅行助手实践总结。项目使用 LangGraph 编排引擎、OpenSandbox 沙箱后端、MongoDB 对话持久化构建。