从0到1手写 AI Agent Harness:为什么护城河不在模型,而在工程外壳

从0到1手写 AI Agent Harness:为什么护城河不在模型,而在工程外壳

📖 摘要:本文从 2026 GitHub 趋势(deepseek-harness 周增 1.4 万星)与"护城河在 Harness 不在模型"讨论切入,讲清 Agent Harness 的定义、四大职责与手写最小可运行实现------ReAct 循环 + 权限门禁 + 可回放会话。并给出把记忆/协议/技能/可观测/评测/安全拼进同一控制平面的思路与三条生产避坑。读完你将拥有可跑的 Harness 骨架。

🏷️ 关键词:Agent Harness,AI Agent,工程外壳,工具调度,权限沙箱

目录

  • 一、背景与痛点:模型不再是瓶颈
  • [二、什么是 Agent Harness](#二、什么是 Agent Harness)
    • [2.1 定义:Harness 是模型的"驾驶舱"](#2.1 定义:Harness 是模型的"驾驶舱")
    • [2.2 Harness 的四大核心职责](#2.2 Harness 的四大核心职责)
    • [2.3 一个信号:GitHub 趋势从模型转向系统](#2.3 一个信号:GitHub 趋势从模型转向系统)
  • [三、手写最小可运行 Harness](#三、手写最小可运行 Harness)
    • [3.1 整体架构设计](#3.1 整体架构设计)
    • [3.2 ReAct 主循环 + 工具调度](#3.2 ReAct 主循环 + 工具调度)
    • [3.3 权限门禁:把工具调用关进笼子](#3.3 权限门禁:把工具调用关进笼子)
    • [3.4 可回放会话:让调试可追溯](#3.4 可回放会话:让调试可追溯)
  • [四、把"六件套"装进 Harness](#四、把"六件套"装进 Harness)
    • [4.1 记忆 / Skills / 协议如何接入](#4.1 记忆 / Skills / 协议如何接入)
    • [4.2 可观测 / 评测 / 安全如何接入](#4.2 可观测 / 评测 / 安全如何接入)
  • 五、生产避坑
    • [5.1 星标暴涨 ≠ 可生产](#5.1 星标暴涨 ≠ 可生产)
    • [5.2 上下文污染与回放陷阱](#5.2 上下文污染与回放陷阱)
    • [5.3 最小权限与默认拒绝](#5.3 最小权限与默认拒绝)
  • 六、总结

一、背景与痛点:模型不再是瓶颈

过去两年,大家的注意力都在"哪个模型更强"------榜单、参数、上下文长度。但 2026 年 8 月的一组信号很说明问题:GitHub Trending 周榜第一是 deepseek-harness(一周暴涨约 1.4 万星),紧随其后的 diagram-designprime-agentTencentDB-Agent-Memoryagent-skillscloudflare/computer 几乎全在解决怎么把模型"框"起来、管起来、跑起来,而不是训练新模型。

社区里的共识正在收敛:模型质量趋同之后,真正的护城河是控制模型行为的系统。一个原始 LLM 只会"聊天",要让它"干活"------读写文件、调接口、跑命令、跨会话记住上下文、多人协作------你需要一层包裹模型的东西。这层东西,就是 Agent Harness。

💡 一句话:模型是发动机,Harness 是车架、方向盘和刹车。发动机再强,没有车架它上不了路。

二、什么是 Agent Harness

2.1 定义:Harness 是模型的"驾驶舱"

Agent Harness(工程外壳) 是介于"裸 LLM"和"可用 Agent"之间的一层工程代码。它不直接产生智能,而是负责:循环驱动模型、调度工具、管理上下文、控制权限、记录过程。你可以把它理解为模型的"驾驶舱"------模型负责思考,Harness 负责让它安全地"踩油门、打方向、踩刹车"。

它和普通的"调一次 API 拿到回答"有本质区别:

维度 裸 LLM 调用 Agent Harness
交互方式 一问一答 多轮循环 + 工具反馈
上下文 单次窗口 可压缩、可持久、可回放
能力边界 只有文字 能读文件、跑命令、调外部服务
安全 权限门禁、沙箱、审计
可调试 重跑难复现 事件日志可追溯、可回放

2.2 Harness 的四大核心职责

一个合格的 Harness 至少承担四件事:

  1. 循环驱动(Loop):把"模型输出 → 解析动作 → 执行 → 结果回填 → 再问模型"串成一个闭环,直到任务完成或步数耗尽。这是 ReAct 模式落地的骨架。
  2. 工具调度(Dispatcher):把模型想用的"工具名 + 参数"路由到真实函数,处理序列化、异常、超时。模型只描述意图,Harness 负责落地。
  3. 权限控制(Permission)默认拒绝。只有当工具在白名单内、参数通过校验,才放行;危险动作(删库、公网请求)必须显式授权或 human-in-the-loop。
  4. 过程记录(Replay):把每一轮的目标、思考、工具调用、结果、报错都落盘成事件流。出事后能像看日志一样还原"当时为什么这么做",而不是靠记忆复现一个不稳定的交互。

这四件事单独看都不神秘,但把它们拼成一个稳定、可审计、可恢复的循环,正是 Harness 真正的工程价值。

2.3 一个信号:GitHub 趋势从模型转向系统

2026 年 8 月 GitHub Trending 最明显的特征是:最快增长的项目几乎都在解决 Agent 的六个瓶颈之一------上下文、记忆、技能、编排、机器访问、边缘执行。模型选择已经"够用",开发者不再等一个稍好的基座模型,而是围绕模型组装可替换的组件。

这带来一个重要判断:看 Trending 时,星标衡量的是"需求",架构、验证、发布节奏、许可证、集成成本才决定"能不能落地"。本文要做的,就是抛开星标,亲手把 Harness 的核心拼起来。

三、手写最小可运行 Harness

下面用一个纯标准库、零外部依赖 的最小 Harness 把上面四件事跑通。生产里你只需把 mock_llm 换成真实模型 API,把工具函数换成你的业务函数即可。

3.1 整体架构设计

复制代码
            ┌─────────────────────────────┐
 用户目标 → │  Harness (控制平面)          │
            │  ┌────────┐  ┌───────────┐  │
            │  │  Loop  │→│ Dispatcher │→ 工具(读文件/跑命令/调API)
            │  └────────┘  └───────────┘  │
            │  ┌────────┐  ┌───────────┐  │
            │  │ Gate   │  │ SessionLog │  │  ← 权限门禁 + 事件回放
            │  └────────┘  └───────────┘  │
            └─────────────────────────────┘
                        ↓
                 SessionLog (JSONL 事件流)

3.2 ReAct 主循环 + 工具调度

核心是一个 Harness 类:run() 里跑 ReAct 闭环,mock_llm 仅作演示(生产替换为真实 API)。为了可运行,我把"模型决策"用步数驱动,方便你直接 python harness.py 看到完整流程。

python 复制代码
import json, uuid, datetime, os

# ---------- 1. 会话事件日志(可回放) ----------
class SessionLog:
    def __init__(self, path):
        self.path = path
    def record(self, event):
        event["ts"] = datetime.datetime.utcnow().isoformat()
        event["id"] = uuid.uuid4().hex[:8]
        with open(self.path, "a", encoding="utf-8") as f:
            f.write(json.dumps(event, ensure_ascii=False) + "\n")
    def replay(self):
        if not os.path.exists(self.path):
            return []
        with open(self.path, encoding="utf-8") as f:
            return [json.loads(l) for l in f if l.strip()]

# ---------- 2. 权限门禁(默认拒绝) ----------
class PermissionGate:
    def __init__(self, allow=None):
        self.allow = set(allow or [])
    def check(self, tool_name, args):
        if tool_name not in self.allow:
            return False, f"tool '{tool_name}' not in allowlist"
        return True, "ok"

# ---------- 3. 真实工具函数 ----------
def tool_read_file(path):
    with open(path, encoding="utf-8") as f:
        return f.read()[:2000]   # 只读前 2000 字符,避免把大文件塞爆上下文

TOOLS = {"read_file": tool_read_file}   # run_shell 故意不注册,演示"默认拒绝"

# ---------- 4. 模拟 LLM(生产替换为真实 API 调用) ----------
def mock_llm(step):
    if step == 0:
        return {"action": "call", "tool": "read_file", "args": {"path": "config.example.json"}}
    if step == 1:
        return {"action": "call", "tool": "run_shell", "args": {"cmd": "rm -rf /"}}  # 危险动作
    return {"action": "answer", "text": "已读取配置;危险命令被权限门禁拦截,任务安全结束。"}

# ---------- 5. Harness 主循环 ----------
class Harness:
    def __init__(self, session_path, gate, max_steps=10):
        self.log = SessionLog(session_path)
        self.gate = gate
        self.max_steps = max_steps
    def run(self, user_goal):
        self.log.record({"type": "goal", "text": user_goal})
        for step in range(self.max_steps):
            decision = mock_llm(step)                       # ← 生产: 真实模型决策
            if decision["action"] == "answer":
                self.log.record({"type": "answer", "text": decision["text"]})
                return decision["text"]
            tool, args = decision["tool"], decision["args"]
            ok, reason = self.gate.check(tool, args)        # 权限门禁
            self.log.record({"type": "tool_call", "tool": tool,
                             "args": args, "allowed": ok, "reason": reason})
            if not ok:
                continue                                    # 被拦截,进入下一轮,不执行
            if tool not in TOOLS:
                self.log.record({"type": "error", "text": f"unknown tool {tool}"})
                continue
            try:
                result = TOOLS[tool](**args)
                self.log.record({"type": "tool_result", "result": str(result)[:500]})
            except Exception as e:
                self.log.record({"type": "error", "text": str(e)})
        return "max steps reached"

3.3 权限门禁:把工具调用关进笼子

注意第 3 节代码里 run_shell 没有注册进 TOOLS,而且 PermissionGate 默认拒绝白名单外的工具。当 mock_llm 在 step 1 返回危险的 rm -rf / 时:

  • 门禁 check("run_shell", ...) 返回 False
  • 循环 continue真实命令永远不会执行
  • 事件流里留下一条 allowed: false 的记录,事后可审计"谁、什么时候、想干什么、被拦了"。

这就是"安全从感知层移到执行层"的落地:与其在提示词里求模型"别干坏事",不如在 Harness 这一层用代码物理阻断

3.4 可回放会话:让调试可追溯

SessionLog 把所有事件写进 JSONL。出问题时,不需要重跑一遍不稳定的交互,直接 replay() 就能看到完整时间线:

python 复制代码
if __name__ == "__main__":
    gate = PermissionGate(allow=["read_file"])      # 仅放行只读工具
    h = Harness("session.example.jsonl", gate)
    print("结果:", h.run("读取配置文件并检查风险项"))

    print("\n--- 会话回放(事件流)---")
    for ev in h.log.replay():
        print(f"[{ev['ts']}] {ev['type']}: {json.dumps(ev, ensure_ascii=False)}")

运行后你会看到:goaltool_call(read_file, allowed=true)tool_resulttool_call(run_shell, allowed=false)answer一条被拦截的危险调用清晰可查,这就是 Harness 相对"裸调 API"的核心优势。

四、把"六件套"装进 Harness

社区这几年把 Agent 的各个能力点都磨得很成熟了:记忆、工具协议(MCP/A2A)、Skills、可观测、评测、安全------单看都好用,问题是怎么拼起来。Harness 恰好是那个"控制平面",把这些模块接成一张网。

4.1 记忆 / Skills / 协议如何接入

  • 记忆 :在 run() 每轮开始前,从记忆中心拉取与当前目标相关的上下文(对话/文档/代码),注入系统提示;工具产出再写回记忆。跨会话复用靠它。
  • Skills(声明式技能) :把 TOOLS 从硬编码函数升级为"元数据驱动的技能清单"------每个 Skill 有名称、描述、输入 schema。模型按描述匹配,Harness 懒加载,避免把所有指令塞进上下文。
  • 协议(MCP/A2A)Dispatcher 不只调本地函数,还能通过 MCP 连外部工具服务、通过 A2A 把子任务转发给另一个 Agent。协议是"工具/Skill"的 transport 层,Harness 只管调度,不关心工具在哪。

4.2 可观测 / 评测 / 安全如何接入

  • 可观测 :把 SessionLog 的每一条事件加上 trace_id / span,导出到 OpenTelemetry 后端(Langfuse、Phoenix),就能看到每个工具调用的耗时、成本、成功与否。
  • 评测 :在 run() 外层包一个 Eval 循环------跑一批固定任务,用"确定性 + 启发式 + LLM-as-Judge"三层打分,作为 CI 质量门禁,防止改了 Harness 把准确率带崩。
  • 安全PermissionGate 只是第一道。再叠加输入注入检测(识别提示词攻击)、输出脱敏(拦截泄露的密钥)、出口白名单(只允许访问审批过的域名),就构成执行层防护。

一句话:记忆给上下文、Skills 给能力、协议给扩展、可观测给眼睛、评测给标尺、安全给刹车------而 Harness 是把它们拧在一起的轴。

五、生产避坑

5.1 星标暴涨 ≠ 可生产

deepseek-harness 一周 1.4 万星很炸裂,但星标衡量的是注意力 ,不是可靠性。落产前请查四项:贡献者分布是否健康、有无未解决的安全 issue、是否有打 tag 的正式 release、依赖风险与维护响应速度。任何能碰仓库或 shell 的 Agent,都必须先过这几关再进生产。

5.2 上下文污染与回放陷阱

回放能还原"发生了什么",但还原不了"当时的完整上下文" ------如果上下文里混入了错误的中间结论,回放看到的是"被污染后的决策链",容易误判根因。对策:回放时同时存快照版本号,调试优先看 tool_result 原始值而非模型复述;并定期做可恢复性演练,确认日志真能重建现场。

5.3 最小权限与默认拒绝

永远从"默认拒绝"出发:TOOLS 白名单 + PermissionGate 双保险。不要因为"模型一般不会乱来"就放开危险工具。把危险动作(写库、公网请求、删文件)设为必须显式审批或 human-in-the-loop。可参考上一条原则:安全要在执行层用代码物理阻断,而不是在提示词里靠模型自觉。

六、总结

2026 年的 Agent 竞争,已经从前两年的"模型军备赛"切换到"系统工程赛"。Harness 就是这场比赛里最关键的控制平面:它用 ReAct 循环驱动模型、用 Dispatcher 落地工具、用 PermissionGate 物理阻断危险动作、用 SessionLog 让一切可追溯。

本文给的最小实现只有几十行标准库代码,却把四件核心职责跑通了。把它和记忆、Skills、协议、可观测、评测、安全这"六件套"接起来,你就拥有了一个能生产落地、可审计、可恢复的 Agent 框架骨架------而不是又一个只能在 Demo 里惊艳的玩具。

🚀 动手建议:先把本文的 harness.py 跑起来,再逐步把 mock_llm 换成真实模型、把 TOOLS 换成你的业务函数,最后接上一条可观测链路。欢迎在评论区聊聊你踩过的 Harness 坑。


示例数据声明:文中 config.example.jsonexample.comrun_shell 等均为通用化演示素材,不对应任何真实系统或业务。

相关推荐
独隅1 小时前
从 Copilot 到 Agent:AI 驱动下的开发工作流重构实战
人工智能·重构·copilot
Summer-Bright1 小时前
独立 AI 浏览器之死与「内嵌化」之生:Atlas 关停、Comet 免费化、豆包虚拟桌面,入口之争的终局
人工智能·ai·ai 浏览器
FYKJ_20101 小时前
django学习成绩预警系统10905
java·javascript·spring boot·python·spark·django·php
淳悦qiqi1 小时前
2026依托技术优势高准确率语音识别软件多场景让信息整理更省事更清晰
人工智能·语音识别
云浪1 小时前
给大模型装上"手":三分钟看懂 AI Agent 的 Tool 工具调用
javascript·人工智能·node.js
YHHLAI1 小时前
实战 Vibe Coding:用 AI 从零搭一个 React 待办清单
前端·人工智能·react.js
电化学仪器白超1 小时前
梅特勒-托利多自动滴定管产品线全览
网络·python·单片机
狂师1 小时前
AI 测试 | 把 UI 自动化测试执行固化成五步流程,这套AI Skill 思路可以直接抄
人工智能·agent·测试
调试到凌晨1 小时前
2026年配音工具避坑实测:免费额度、长文本稳定性与API集成能力全记录
人工智能·经验分享·实时音视频