
先说个背景。年初我给团队搭了一套 Agent 运行框架按社区的习惯这类东西叫 harness——它把模型调用、工具执行、上下文传递串成一条循环让 Agent 能真正“跑起来”。跑通之后问题马上来了Agent 确实会执行任务但执行质量忽高忽低同一个需求换个说法结果就完全不一样。工具调用失败后还会固执地反复重试同一个参数前一个任务的错误结论甚至会莫名其妙污染后一个任务。我意识到问题不在模型而在 harness 本身——它只解决了“怎么跑”没解决“怎么想”。这篇文章就是我把 harness 工程升级成认知工程的完整复盘包括概念拆解、升级步骤、架构演进和几段真实踩坑记录适合正在搭 Agent 框架或者准备把手头 Agent 项目往深处做的工程师参考。1. 先把概念边界划清楚harness 和 Agent 到底谁是谁1.1 我的理解harness 是运行时的“安全绳”harness 这个词字面意思是背带、安全绳。放到 AI 工程里我把它理解为包裹在模型外面的一整套运行时支撑怎么加载模型、怎么维护上下文、怎么调用工具、怎么控制循环、怎么做日志和异常恢复。它不决定 Agent 有多聪明但它决定 Agent 能不能稳定地在真实业务里跑起来。我在最早设计的时候把 harness 和 Agent 的区分想得很简单Agent 是决策主体负责理解任务、选择下一步动作、判断结果对不对harness 是承载决策的工程底座负责让每一步决策都被安全地执行和记录。这个类比可以再往生活里推一步harness 像是工地上的脚手架和安全绳Agent 是站在脚手架上干活的工人。脚手架搭不好工人技术再高也容易出事故反过来脚手架搭得再漂亮工人没经验也干不出活。我说“将 harness 工程升级到认知工程”核心就是这个转变不再只关注脚手架够不够稳而是开始关注工人有没有图纸、会不会复盘、能不能记住上次的教训。脚手架是工程图纸和复盘也是工程后者就是认知工程。1.2 harness 与 Agent 的分工逻辑很多人第一次接触时会搞混这两个概念尤其是看到“Agent 框架”“harness 工程”这类词堆在一起。我习惯用三层来划分模型层大模型本身提供推理能力是“大脑”的原料。Agent 层任务理解、目标拆解、行动决策是“大脑”。harness 层模型调用、工具注册、上下文管理、循环控制、日志监控是“神经系统和肌肉”。框架比如 LangGraph、LangChain提供的是构建 harness 的积木但不等于 harness 本身。harness 更像你在框架之上、或者干脆不用框架直接写出来的那套“业务运行时”。社区里最近讨论很多的 deepseek harness 这类项目核心思路也都是把模型调用、工具执行、上下文管理固化成一个可复用的壳和我说的 harness 是同一类东西区别只在于它们是产品化的形态而我当时是团队内部从零手写的。还有一组经常被一起问到的概念Skill 和 Agent 的区别。我的理解是Skill 是“一组封装好的能力”比如“生成图表”“查询订单库”它是可复用的原子能力Agent 是“拥有目标并驱动能力去实现目标的决策主体”一个 Agent 内部通常会挂载多个 Skill。升级到认知工程之后Skill 的边界会被定义得更清楚因为它要能被规划器识别、调度和评估这些都属于认知工程要管的事。1.3 从零手写一个最小可用的 harness我第一版 harness 没有用任何框架核心就是一个循环组装上下文调用模型判断模型是否请求工具执行工具把结果回填继续下一轮。def run_agent(task, tools, llm, max_iterations8): context { task: task, history: [], result: None, } for step in range(max_iterations): messages build_messages(context, tools) response llm.chat(messages, toolstools) tool_calls parse_tool_calls(response) # 没有工具调用说明模型认为任务完成 if not tool_calls: context[result] response.content return context for call in tool_calls: output run_tool(call) context[history].append({ step: step, call: call, output: output, }) return context这段代码只有二十几行但它已经是一个完整 harness 的雏形有循环控制、有上下文追加、有工具调度、有最大迭代限制。第一版跑起来很容易真正麻烦的是当任务变得复杂、工具超过十个、同一个 Agent 要在不同场景下复用之后这个简陋循环的缺陷会全部暴露出来。而这些缺陷恰好都指向同一个方向——认知能力缺失。2. 为什么“能跑”的 harness 不够用四个真实缺口2.1 只有上下文窗口没有工作台模型再大每次能获取的信息上限也受上下文窗口约束。但窗口大不等于信息组织得好。一开始我把任务描述、工具说明、历史消息全部拼在一段 Prompt 里丢给模型结果很典型任务一长模型容易丢失关键约束工具说明写得太细模型会“忘掉”最核心的判断标准历史记录堆太多早期信息被稀释得几乎无效。我当时打的比方是上下文窗口只是“看的面积”不是“工作台”。认知工程的第一个动作就是把模型能“看到”的信息重新组织成一个结构化的工作台——哪些是任务目标哪些是约束哪些是当前进度哪些是最近一次工具输出的原始结果被明确分区。模型还是那个模型但接收信息的形态变了可用程度完全不同。这不需要换更强的模型成本很低收益却很直接。2.2 工具越多自由发挥的 Agent 越不靠谱当工具只有两三个时让模型自由决定调用哪个通常问题不大。工具列表超过十几个以后情况会迅速变糟模型会选中“名字看起来相关但实际不匹配”的工具会在第一步就选一条绕远的路径会在一个工具返回错误后反复用同样的参数重试同一个工具完全没有“换个思路”的意识。根因在于模型每次只做“当前这一步”的决策它没有一个全局的执行蓝图。人在做复杂工作时不会走一步看一步而是先有个计划再按计划执行遇到偏离再调整。认知工程要补的正是这一层——把“临场决策”变成“规划先行”先让模型花一次调用把任务拆成步骤再按步骤执行。这样能大幅减少中途跳错路径的概率也让整个执行过程变得可预期、可复盘。2.3 模型天然无记忆而业务天然需要记忆大模型本身是无状态的放进 harness 里的历史消息只能在单次会话内起作用。一旦任务中断、服务重启、或者下一个任务到来之前沉淀的经验就全部丢失。更麻烦的是业务里很多信息是需要跨任务复用的某个业务的命名规范、某个工具返回结果里常见字段的含义、上一次执行时踩过的一个坑。这些信息放不进 Prompt因为每次 Prompt 都是临时拼的也不适合全塞进上下文因为塞进去会污染当前任务的注意力。它需要一个真正的记忆模块——区分“本次会话的短期记忆”和“跨会话的长期记忆”。长期记忆一定要支持检索按需提取而不是全量堆叠。这个听起来很容易实际上很多人一开始都会做错我后面会专门写踩坑经历。2.4 没有评估体系所有“升级”都是玄学这是我在早期吃亏最深的一点。没有 evals 之前我改一个 Prompt 或者换一个调度策略只能靠几个手工任务试感觉“好像变好了”但完全不知道是稳定变好还是碰运气。更危险的是某一个任务变好后其他任务可能已经在悄悄退化而我没有任何回归手段能提前发现。认知工程最后一块拼图就是建立一套评估体系每个任务类型固定一批测试用例跑完以后自动算分。这样每一次改动都有量化结果而不是靠感觉。我后面会专门讲怎么搭一套轻量级 evals不需要复杂平台几十行脚本就能起步但它能挡住大量的隐性退化。3. 升级实操把认知能力逐层装进 harness3.1 第一层从 Prompt 工程升级为上下文工程我最早的方法是精心打磨 System Prompt把角色、步骤、注意事项全写进去。后来发现Prompt 写再好也扛不住执行过程中的状态变化——任务执行到第 5 步时的信息需求和第 1 步完全不一样一个静态的 Prompt 无法表达这种动态结构。所以我改成“上下文组装器”每次循环都按当前状态动态生成 messages而不是把一个固定 Prompt 反复发送。结构大致是这样def build_messages(context, tools, step, max_steps): system_content f 你是一个自动化任务执行助手。 能力边界只能调用提供的工具不能编造工具返回值。 执行原则如果工具返回错误先分析错误原因再决定是否需要换个参数或换一个工具。 state_content f 当前任务{context[task]} 执行进度第 {step}/{max_steps} 步 已完成的步骤 {format_history(context[history])} 最近一次工具输出 {context[history][-1][output] if context[history] else 无} 可调用工具 {format_tools(tools)} return [ {role: system, content: system_content}, {role: user, content: state_content}, ]这段设计的要点是把“稳定信息”和“动态状态”分开。稳定信息进 system动态状态进 user。每个步骤模型的注意力都集中在“当前进度”和“最近一次工具输出”上而不是在一个上千字的固定 Prompt 里找重点。改完之后同样一个模型跑同样一批任务路径选择明显更稳。3.2 第二层显式记忆模块与读写策略上下文工程解决的是“单次循环里的信息组织”记忆模块解决的是“跨循环、跨任务的信息沉淀”。我把它拆成两块短期记忆当前任务内产生的关键信息比如已经试过哪些方案、哪些方案被否定了长期记忆跨任务复用的结论性知识比如某个工具的输出格式说明、某个业务的特殊规则。长期记忆不能什么都往里面写否则会出现严重的记忆污染。我当时的写入策略是只有满足“被验证过”的信息才允许进入长期记忆。什么叫被验证过要么是人类确认的要么是工具返回成功并且后续步骤依赖它且最终任务成功的。一次失败的尝试、一次没有依据的猜测都不允许写入。读取侧我用了“检索优先”的方案不把所有长期记忆一股脑塞进上下文。简单实现可以先用关键词粗筛再用向量检索精排class MemoryStore: def __init__(self, embed_fn, llm): self.episodic [] self.semantic [] self.embed_fn embed_fn self.llm llm def add_episode(self, observation): self.episodic.append(observation) def commit_to_semantic(self, summary, confidence): # 只有高置信度的结论才进入长期记忆 if confidence 0.8: self.semantic.append({ text: summary, embedding: self.embed_fn(summary), }) def retrieve(self, query, top_k3): if not self.semantic: return [] q_vec self.embed_fn(query) scored sorted( self.semantic, keylambda x: cosine_similarity(q_vec, x[embedding]), reverseTrue, ) return [item[text] for item in scored[:top_k]]这套设计跑了一段时间后我最大的感受是记忆模块真正的难点不是“存进去”而是“决定什么值得存”。如果这个关口不把住后面所有任务都会为前一个任务的错误买单。3.3 第三层规划器与执行器分离以前工具少的时候让模型边想边做没问题。工具一多我就把“规划”抽出来单独作为一个环节。流程变成先调用一次模型只做任务拆解产出步骤列表然后进入执行循环每一步严格按计划执行执行中如果遇到计划外的错误才允许重新规划。规划器的实现不需要很复杂核心是让模型产出结构化步骤def plan(task, tools, llm): plan_prompt f 把下面的任务拆解为 3 到 6 个步骤。 要求 1. 每一步只能依赖一个工具完成明确写出工具名和输入参数来源。 2. 步骤之间必须有依赖顺序后一步不能假设前一步产生不存在的数据。 3. 如果任务本身无法拆解直接回复无法拆解并说明原因。 任务{task} 可用工具{format_tools(tools)} 输出格式 STEP 1: ... STEP 2: ... ... raw_plan llm.chat([{role: user, content: plan_prompt}]) return parse_steps(raw_plan)规划器与执行器分离之后有个额外的好处可观测性大幅提升。以前执行过程是一团乱麻出了问题很难定位是哪一步决策错了。现在有一张明确的计划表每步执行完都能对照计划看偏差排查问题的成本低了一个量级。这也为后面的反思环节提供了依据。3.4 第四层反思循环与自我修正规划和执行解决的是“怎么把任务做完”反思解决的是“怎么把做错的任务纠正回来”。我在 harness 里加了一个可选的后置环节执行完成后调用一次专门的反思 Prompt让模型回顾整个过程产出三个结论——结果是否真的满足任务要求、哪一步决策导致了偏差、下一次应该改变什么。这个反思过程并不每次都产生行动但它有两个作用。第一在任务最终结果可能有瑕疵时它能触发“二次修复循环”把反思结论和当前结果一起送回执行器再跑一轮修正。第二它负责给长期记忆提供高质量素材——反思产出的“下次应该怎么做”这类结论正是长期记忆最值得保存的内容。需要注意的是反思循环必须设置上限。我当时的做法是任务级最多允许两轮反思修复超过就直接标记“需要人工介入”。不要指望 Agent 能无限自我修正成本会失控而且错误容易在反复修复中被放大。3.5 第五层搭建 evals让认知可度量这一层最容易被小团队忽略但我认为是认知工程的“验收标准”。没有 evals前面四层都是凭感觉。我搭的 evals 分成三类评估类型覆盖内容例子单步工具调用每次调用是否选对工具、参数是否正确给定“查用户 10086 的订单”必须调用query_orders(user_id)任务完成度最终结果是否满足任务要求结果是否包含指定字段、是否完成指定动作回归测试改动后旧任务是否退化历史 50 个任务全部重跑对比通过率轻量级实现其实很简单。每个任务配一个 checkerchecker 可以是规则判断也可以是一次模型调用让它判定结果是否符合要求。然后写一个脚本统一读取测试用例跑一遍输出通过率def run_eval(task_set, agent, checker_map): results [] for task in task_set: result agent.run(task.input) ok checker_map[task.name](task, result) results.append((task.name, ok)) total len(results) passed sum(1 for _, ok in results if ok) print(f通过率: {passed}/{total}) return results这个体系建立以后我再改 Prompt、调记忆策略、改规划器都有了明确依据跑完回归看通过率是升是降。认知工程里所有“我觉得变好了”的感觉到这里都变成“数据证明变好了”的结论这才是工程和玄学的分界线。4. 升级路上的踩坑记录四个有代表性的翻车现场4.1 死循环工具失败后 Agent 不肯认错第一个让我头疼的问题是 Agent 在工具调用失败后陷入死循环。现象是某个 API 返回了一个 400 错误模型没有去分析错误原因反而用几乎一模一样的参数反复调用同一个工具把 max_iterations 耗完之后直接报错退出。排查后我发现问题出在两处。第一我的错误回填太简陋只是把异常文本塞进 history模型没有获得“为什么失败”的结构化信号。第二模型没有重试策略的概念它不知道“连续失败两次后应该换方案”。修复方案是在工具输出里增加结构化错误码和失败建议def safe_run_tool(call): try: output run_tool(call) return {status: success, output: output} except Exception as e: return { status: error, error_type: type(e).__name__, message: str(e), suggestion: generate_fix_hint(call, e), }同时我在上下文工程里加了一条硬规则同一个工具的同一个参数组合最多重试一次。连续失败后模型必须从“分析错误原因、换参数、换工具、终止任务”里选一个动作。加上这个约束后死循环基本绝迹任务失败也能快速进入可诊断状态。4.2 记忆污染错误结论写入长期记忆的代价记忆模块上线初期我的写入策略很宽松结果栽了大跟头。当时有个任务是让 Agent 整理一批报表的字段含义中间有一次模型误判了一个字段把“这个字段代表成本价”这种结论写进了长期记忆。后面所有涉及这张报表的任务都被这个错误结论影响连续三天任务质量下降而我一直以为是模型不稳定。排查过程很痛苦最后是逐个检查长期记忆的内容才发现那条错误记录。这次事故之后我给记忆写入加了三道门槛第一结论必须经过工具成功返回的事实支撑第二涉及业务规则的结论必须至少被两个独立任务重复验证第三长期记忆每条记录必须带上“来源任务 ID”和“写入时间”方便事后追溯和撤回。这也让我意识到记忆模块本质上和业务数据一样需要治理。它不能只做加法还得有校验、审计和删除机制。否则长期记忆积累越多系统出错的风险反而越大。4.3 过早引入多 Agent 编排的教训我的 harness 升级到第三版时有一段时间我特别想上多 Agent 架构觉得一个“总指挥”加几个“专项执行者”更高级。结果建了两套方案并行对比发现单 Agent 方案在大部分任务上的完成率和速度反而更好多 Agent 方案引入了一大堆新问题任务交接时要传递哪些信息、子 Agent 之间如何避免重复工作、总指挥误判该把任务交给谁。这个教训很直接多 Agent 的价值在于隔离关注点、并行处理独立子任务而不是听起来更智能。如果你的任务执行链是高度线性的单 Agent 加上规划器、记忆、反思这层认知增强往往比强行拆成多个 Agent 更稳。我最终把多 Agent 的引入条件定成了两条一是存在天然独立且可并行的子任务二是单 Agent 因为上下文混合导致严重互相干扰时才值得拆。4.4 可观测性缺失问题无法回溯认知工程上线后有一类新的故障特别难查模型“觉得自己做得很好”但实际上跑偏了。这种问题不像工具报错那样有显式异常它藏在一次看似正常的调用链里。如果日志没有记录关键信息事后根本无法复盘。早期我的日志只记了“任务开始”“任务结束”“最终结果”中间过程几乎空白。出问题后想定位是哪一步决策错了完全没有线索。后来我补上了链路追踪每个任务分配一个 trace_id记录每一次模型调用的输入和输出、工具入参和出参、每一步的耗时和 tokens 消耗、规划到执行的偏差。这部分的成本并不高但价值极大。我现在的要求是任何一次产线异常都能通过 trace_id 把整条执行链路原样还原出来。如果还原不了说明可观测性是失败的。对 Agent 系统尤其如此因为模型的不确定性决定了它的每一步都有可能是问题来源不记录就等于放弃诊断能力。5. 架构演进实录从同步脚本到认知工作台5.1 第一版单线程同步循环最原始的版本就是 1.3 节那段代码单线程、同步、串行。优点是简单直观缺点也很明显一个任务从开始到结束独占整个进程所有工具调用必须排队任务一旦卡住整个服务跟着卡住也无法同时处理多个任务。这个版本支撑了最早期的实验场景我不建议一上来就追求复杂架构。如果你的任务量不大同步循环完全够用重点应该放在把上下文工程、记忆策略这些认知能力先沉淀下来。5.2 第二版事件驱动的异步总线任务量上来之后我把同步循环改成了事件驱动的异步架构。每个任务不再是一个 for 循环而是一系列事件的流转任务创建、规划完成、工具执行中、工具执行完成、反思触发、任务结束。每个事件进到一个消息队列worker 处理完一个事件后发布下一个事件。这样做的直接收益是异步解耦工具调用不再阻塞整个任务多个任务可以同时处于不同阶段。同时也让每个节点变得可观测——事件本身就是天然的日志节点。这个阶段我用到了消息队列和事件存储harness 不再是一个循环函数而是事件流上的处理器集合。5.3 第三版可插拔的认知组件层第三版我把几个认知能力模块化形成了稳定的组件层。每个组件只负责一件认知职责通过配置决定是否启用上下文组装器负责信息分区规划器负责步骤拆解记忆读写器负责短期和长期记忆反思器负责结果审查evaluator 负责最终评分。执行层和工具层在下层保持稳定与认知层解耦。这个架构的收益是想对比“有规划器”和“没有规划器”的效果只需要改一行配置想给某个特定任务关闭反思也只涉及对应任务的策略。认知工程从“一套写死的流程”变成了“一堆可组装的能力”这是它作为工程体系真正成型的关键一步。5.4 同一个任务升级前后的表现对比为了验证升级的实际效果我拿一个高频业务任务做了对比测试任务是根据原始销售数据生成一份周报要求包含汇总、TOP 产品和异常波动说明。维度第一版纯循环 harness升级后的认知工程 harness任务平均完成时间3 分 20 秒2 分 05 秒首次执行成功率61%86%需要人工介入的比例27%9%工具调用平均次数14 次9 次结果格式完全达标率52%88%数据差异的主要原因并不在模型模型始终是同一个。提升主要来自规划器减少了绕路、上下文工程减少了关键信息丢失、反思环节抓回了一部分错漏、记忆模块避免了一再踩同一个坑。我拿这份数据跟团队汇报时只有一个结论换模型可以提升上限但把 harness 升级成认知工程提升的是稳定性的下限而生产系统最缺的恰恰是下限。6. 认知工程的边界、成本与下一步6.1 哪些项目值得升级到认知工程不是所有 Agent 项目都需要完整升级到认知工程。我的判断标准很简单如果你的 Agent 只是围绕工具做单轮问答用户问一句、Agent 调一次工具、给一个答案那么做好上下文工程就够了不需要规划器和长期记忆。但如果你的 Agent 要执行多步任务、要在一个任务里反复决策、要跨会话复用经验那么规划、记忆、反思、评估这四件事迟早都要做越早做越好。另外要提一个容易被忽略的维度Agent 安全边界。认知工程赋予了 Agent 更强的自主性——它会自己规划、自己调用工具、自己根据记忆做判断这同时意味着它的行动空间变大了。我在升级过程中始终把安全约束放在上下文工程的最前面高危险动作必须二次确认不能被任何工具读取或改动的数据要在 system 层明确禁止。能力越强边界越重要这不是一句空话。6.2 成本与收益的权衡认知工程不是免费的。规划环节多一次模型调用反思环节又多一次记忆检索增加少量延迟evals 每次发布前要跑一遍测试集。这些成本在单任务上看起来不大但任务量大之后tokens 消耗和延迟都会上升。我的建议是分场景确定认知配置核心生产任务开启全量认知组件高频低风险任务只保留上下文工程和记忆检索实验性任务全部关掉只跑基础 harness。收益侧的衡量也不应该只看成功率还要看人工介入率下降了多少、排查问题的时间减少了多少这两项往往比一次模型调用贵得多。6.3 我的下一步计划与给同行的建议目前这套认知工程框架在团队内部已经稳定跑了一段时间下一步我主要想在三件事上继续挖一是把反思产出的结论做成更细粒度的 Skill让 Agent 能按需加载更聚焦的能力而不是所有场景都靠通用模型硬扛二是把 evals 从离线回归推进到准在线灰度——新策略先在流量子集上验证再全量放量三是尝试把长期记忆的写入做成人机协同模型给候选结论、人来确认减少记忆治理的成本。最后想对准备做同样升级的同学说一句不要一上来就设计一个大而全的平台你的第一版一定是从最小循环里长出来的。先把上下文分区做好再补记忆再上规划器每一步都用数据验证过以后才往下走。认知工程不是一个可以一步到位的架构它是你不断发现模型在实际业务里的短板再用工程手段一块块补上的过程。这个过程没有捷径但每一步都走得很踏实。