← 返回博客
[从零开始搭一个企业级 Agent Harness] 1/10:手搓一个 Agent,会遇到哪些问题?

从零开始搭一个企业级 Agent Harness1/10:手搓一个 Agent,会遇到哪些问题?

第一次把Agent跑起来,很容易产生一种错觉:最难的部分已经完成了。

任务交给模型,工具列表一起传进去。模型要么选择一个工具,要么返回最后答案。工具结果放回对话,循环继续。几十行代码已经能查订单、读文档、更新客户关系系统(CRM),甚至运行代码。

然后客服窗口弹出一条新消息:“我昨天被重复扣款了,帮我处理一下。”

Agent查到订单,调用退款接口,很快回复:“退款已经完成。”对话窗口里的任务结束得很干净。支付后台里,退款也许仍在处理中;通知邮件也许还躺在草稿箱;那次工具调用遇到超时以后,也许已经被执行了两遍。

这就是我们开始学习Agent Harness的地方。模型会判断下一步,Harness负责让整个任务可控、可恢复、可验证。Harness是包住整项任务的系统;其中真正推动循环、调用工具和保存进度的程序,可以叫运行时(runtime)。学会区分模型、运行时和Harness以后,我们才能判断故障究竟该在哪一层修。

先看懂最小Agent少了什么

最小Agent只有四个动作:读任务,作决定,调用工具,把结果交还模型。把它写成代码,大约就是下面这样:

def run_agent(task, model, tools, max_turns=20):
    messages = [{"role": "user", "content": task}]

    for turn in range(max_turns):
        decision = model.next(messages, tools=tools.schemas())

        if decision.type == "final":
            return {"status": "MODEL_FINISHED", "answer": decision.text}

        if decision.type != "tool_call":
            return {"status": "FAILED", "error": "UNKNOWN_DECISION"}

        tool = tools.get(decision.name)
        result = tool.execute(decision.arguments)
        messages.append(decision.as_message())
        messages.append(result.as_tool_message())

    return {"status": "STOPPED", "error": "TURN_BUDGET_EXCEEDED"}

第一行把用户任务放进消息列表。循环每走一轮,模型都看到当前消息,以及每个工具的名称、用途和输入格式,然后作一个决定。它若给出最终回答,程序立刻返回 MODEL_FINISHED;它若要调用工具,程序按名字找到工具,用模型给出的参数执行,再把“模型调用了什么”和“工具返回了什么”一同放回消息列表。超过20轮还没有结束,程序就停下。

这段循环已经具备Agent最小的行动能力,也把缺口暴露得很清楚。执行 tool.execute 以前,程序没有检查“这个用户能不能做这件事”;执行以后,也没有独立记录说明真实系统是否被改变。消息列表承担了全部状态,final一出现,程序又把“模型说完了”当成“业务完成了”。最后的轮数上限可以阻止无限循环,却不会告诉我们任务做到哪里,也不会把已有证据交给人。

Harness不替模型判断退款理由。它负责围绕这段循环保存进度、检查权限、限制预算、记录调用,并核验结果。这样,MODEL_FINISHED 只表示模型不再说话;支付、CRM和邮件系统都给出匹配证据以后,任务才成为 BUSINESS_COMPLETED。这一篇先把这些空位找出来,后面再逐个补上。

Agent loop:模型判断、调用工具、读取结果并继续判断的循环。

Agent Harness:围绕模型提供循环、上下文、工具执行、状态、权限、验证和运行控制的系统。

“模型停止”离“业务完成”还有多远?

最小循环看到模型不再请求工具,便返回 MODEL_FINISHED。这只说明模型准备结束对话。退款是否到账,要去支付系统查;客户工单是否写入退款编号,要去CRM查;邮件是否投递,要去邮件系统查。三个系统里的事实,不能由模型的一句话代替。

于是退款任务出现第一块缺口:终点必须提前写清,进度要独立保存,最后再由支付、CRM和邮件系统拿出证据。下一篇会把这三样东西做成任务契约、进度账本和独立核验程序(Verifier)。

任务继续变长,第二块缺口也出现了。订单记录、退款政策和工具返回不断挤进上下文,最初那句“昨天被重复扣款”越来越难被注意。完整历史要保存,当前这一轮却只该看到最需要的材料。第三篇会把事件、工作状态、当前上下文、长期记忆和业务真值分开。

一旦Agent能够行动,错误就有了后果

Agent只有文字输出时,错误通常停留在回答里。接上取消订单、修改账单和创建退款这些工具以后,一个相似函数名就可能改变真实数据。

因此工具不能只是一组函数。每个工具需要写清用途、参数、副作用、权限、审批和结果状态;模型先提出动作,运行系统再检查,真正持有订单的服务还要做最后授权。第四篇沿着一次误建采购申请,把这条工具管线走完整。

工具调用还会遇到时间问题。退款接口转了十秒,最后只返回超时。Agent再试一次,支付系统可能已经收到第一次请求。第五篇会跟着一封邮件,看系统怎样保存同一个动作、查询真实结果,并且只在确认旧请求不会继续执行、也没有产生效果以后才重试。

没有红色报错,也可能已经失败

Agent反复搜索同一批资料,邮件改完又推翻重写。窗口里一直有新文字,任务却没有向终点移动,模型输入用量、时间和工具费用持续上升。

所以运行系统还要管理轮数、时间、模型输入用量、工具调用和成本。预算耗尽时,任务要停下来,把已经取得的证据交给人,而不是用“仍在思考”掩盖失控循环。

权限也是同一个道理。一个全权限服务账号能让演示迅速跑通,也能让处理单张订单的Agent修改其他客户数据。第六篇会把用户、Agent、运行实例和后端服务的身份串起来,让每次动作只拿到当前订单所需的短期权限。

出错以后,我们要能沿原路找回去

普通应用出错,我先看请求、日志和数据库变化。Agent还要回答更多问题:这一轮拿到了哪些材料,选择了哪个工具,参数来自哪里,哪次审批允许了动作,哪次重试改变了状态。这里要保存可检查的决定与依据。

这些信息要从第一天就进入同一条执行记录。模型、工具、政策、审批、状态、成本和最终结果都用同一个运行编号关联;敏感正文按政策脱敏,或者只保存能用来核对内容是否变化的摘要值。到了第七篇,这些运行记录会变成可重复评测和发布门槛。

Trace:按同一次运行关联模型、工具、状态、错误、成本和结果的结构化执行记录。

一个Agent做不好,再增加几个,也不会自动消除这些问题。它只会增加模型调用、交接、状态副本和信任边界。第八篇先用同一组任务证明拆分真的带来收益,再决定是否增加执行Agent。

2026年的进展:Harness正在成为独立产品层

前面这段小循环只存在于一个进程里:进程一停,消息、进度和执行环境都可能一起消失。任务一长,就要回答三个问题:记录放在哪里,下一步由谁控制,模型生成的代码又在哪里安全执行。2026年的托管产品开始把这三件事拆开处理。

Anthropic在2026年4月8日公开的Managed Agents架构里,用session保存只能继续追加的会话记录,用harness读取记录并决定下一步,用sandbox隔离代码和文件操作。换成白话,就是记录、调度和代码执行各放在自己的位置。任务跑过很多轮或控制进程重启时,新的harness可以继续读原来的session,不必把整段工作重新交给模型。

Amazon Bedrock AgentCore Harness在2026年6月17日正式可用,把Agent循环、工具连接、上下文、跨轮状态、故障恢复和每个会话的隔离环境交给托管服务。放进开头的退款里,它可以维持Agent运行和会话隔离;支付系统是否真的生成退款编号,仍由企业自己的系统核验。

OpenAI Agents API在2026年9月10日进入公开测试,把选择分成三层:直接调用模型、在自己的应用里使用Agents SDK,或让平台托管整个Harness。判断这些产品时,我始终问同一组问题:谁保存进度,谁等待和恢复,谁隔离执行,谁验证业务结果。

带着问题地图进入下一篇

现在再看开头那笔退款,已经能看见八个不同问题:完成、上下文、工具、重试、预算、权限、观测和协作。它们不再是一句模糊的“Agent不稳定”,而是八块可以分别设计、测试和验收的系统能力。

下一篇先从最靠近终点的问题开始:Agent说“退款完成”以后,程序怎样拿出足够证据,真的把任务关掉?

新文章,直接发到你的邮箱。

AI、工程与实验,每月 1–2 封。

无垃圾邮件,随时取消。