AI Agent工程化:状态管理、可观测性与工具链治理
1. 项目概述这不是课程笔记而是一份AI Agent工程化落地的实操手记“李宏毅课程笔记Harness Engineering如何驾驭 AI Agent”——这个标题里藏着三个关键信号李宏毅代表的是扎实、系统、不玩虚的AI教学风格课程笔记暗示内容不是空泛理论而是从真实学习过程里长出来的结构化沉淀而最核心的“Harness Engineering”直译是“驾驭工程”但绝非字面意义的“控制”或“约束”它指向一个正在快速成型的新工程范式把AI Agent当作一个可设计、可测量、可调试、可迭代的工程对象来对待。我带过不少刚接触Agent开发的开发者他们常卡在同一个地方写完一个调用LLM的函数加了几个工具再套个ReAct循环就以为做出了Agent。结果一上线任务失败率高、响应时间飘忽、错误归因困难、用户反馈模糊——根本没法进生产环境。这恰恰暴露了当前最大的认知断层我们还在用写脚本的思维写Agent却忘了它本质是一个多模块协同、状态持续演进、外部环境强耦合的动态系统。Harness Engineering要解决的就是这个断层。它不教你怎么调API而是教你怎么给Agent装上仪表盘、刹车片、故障诊断仪和升级通道。比如当一个电商客服Agent连续三次把“退货政策”答成“换货流程”传统做法是改promptHarness Engineering的做法是先检查它的记忆衰减曲线是否异常、工具调用链路中是否有未捕获的超时抖动、知识库检索模块的向量相似度阈值是否被近期数据漂移拉低。这些细节不会出现在任何一门基础课里但却是你真正能把Agent用起来、用稳、用久的底层能力。这篇文章就是我把过去一年在多个实际项目中打磨出的Harness Engineering方法论拆解成可复现、可验证、可迁移的具体动作。它适合三类人刚学完LangChain/LlamaIndex想进阶的实践者、正为Agent线上稳定性头疼的工程师、以及想系统理解Agent不再只是“大模型工具”的技术决策者。全文没有一行代码是为演示而写每一行都来自某次线上告警后的回溯修复。2. 核心思路拆解为什么必须抛弃“Prompt即全部”的旧范式2.1 从“单次推理”到“状态化会话流”的范式迁移早期的AI应用开发本质上是在做“单次推理优化”。输入一个问题调整prompt让模型输出更准的答案。这种模式在问答、摘要等静态任务中有效但一旦进入Agent场景立刻失效。原因很简单Agent的核心价值在于跨步骤、跨工具、跨时间的上下文维持与决策接力。一个订机票的Agent需要先查航班再比价格再确认用户偏好最后调支付接口——这四个步骤之间状态必须可靠传递。而传统prompt工程只管“当前这一步怎么写”对“上一步的输出如何影响下一步的输入”缺乏建模能力。我参与过一个旅游规划Agent的重构原版本所有状态都靠拼接字符串塞进prompt结果当用户中途插入一句“算了改成去上海”整个上下文就乱了航班查询结果被覆盖酒店偏好丢失甚至支付接口被意外触发。问题根源不在模型而在状态管理完全依赖LLM的文本记忆能力而这是不可靠、不可测、不可调试的。Harness Engineering的第一步就是强制剥离状态。我们把所有中间状态如已查航班列表、用户明确排除的城市、预算上限存入独立的结构化状态存储初期用内存Dict后期切Redis每一步Agent执行前先从这里读取最新状态执行后再把新状态写回去。这样prompt只负责“当前决策逻辑”状态只负责“事实记录”二者解耦。实测下来任务成功率从68%提升到92%更重要的是当失败发生时我们可以直接dump出完整状态快照而不是对着几千字的prompt日志猜谜。2.2 “可观察性”不是附加功能而是Agent的呼吸系统很多团队把监控当成上线后的补救措施但对Agent而言可观测性必须前置到设计阶段。为什么因为Agent的行为具有强随机性、弱确定性、高延迟敏感性。一次API调用慢了200ms可能让整个ReAct循环卡死一个工具返回了格式错乱的JSON可能让后续所有步骤解析失败。如果你没有在第一步就埋好观测点等到用户投诉“为什么一直转圈”你连问题发生在哪一层都不知道。我们定义Agent的“呼吸系统”包含三个必埋点输入层观测记录原始用户query、时间戳、会话ID、决策层观测记录LLM生成的thought/action序列、token消耗、生成耗时、执行层观测记录每个工具调用的入参、出参、耗时、错误码。这些数据不求实时大屏但必须能按会话ID一键追溯。有一次我们发现某个金融咨询Agent在下午3点后失败率陡增排查数小时无果最后通过输入层观测发现那个时段大量用户query里混入了特殊符号“①②③”而我们的清洗模块恰好漏掉了Unicode编号字符导致后续所有解析失败。这个bug如果没埋输入观测根本无法定位。所以Harness Engineering里写第一个Agent之前先写好观测SDK这和写main函数前先初始化logger一样自然。2.3 工具链不是“插件集合”而是有拓扑关系的微服务网络新手常把工具Tool理解为一个个孤立的函数比如get_weather()、search_web()。但在真实Agent中工具之间存在严格的依赖拓扑与调用契约。例如一个房产Agent必须先调用“区域筛选工具”拿到小区列表才能把列表ID传给“房价查询工具”如果跳过第一步直接查房价工具必然报错。Harness Engineering要求我们像设计微服务架构一样设计工具链定义每个工具的输入Schema必须是JSON Schema不能是模糊描述、输出Schema明确字段名、类型、是否必填、失败语义HTTP 404是“数据不存在”500是“服务不可用”需不同重试策略。我们曾用OpenAPI规范为所有内部工具生成统一描述文件再用这个文件自动生成Agent的tool description。好处立竿见影当“天气工具”的API变更比如把temperature字段改成temp_c只要更新OpenAPI文件Agent的tool description自动同步无需手动改prompt。更关键的是它让我们能做工具链健康度分析统计每个工具的平均调用耗时、失败率、参数分布发现“地图坐标转换工具”在高并发下P99耗时飙升立刻推动后端优化而不是等Agent整体变慢才感知。工具链不再是黑盒拼图而是一张可度量、可治理的服务拓扑图。3. 关键环节实现从零搭建一个可驾驭的Agent骨架3.1 状态管理模块用“版本化快照”替代脆弱的文本拼接状态管理是Harness Engineering的基石但实现上极易陷入两个极端要么过度设计搞出一套复杂的状态机框架要么过度简陋用全局变量硬扛。我们采用的是轻量级版本化快照方案核心就三点状态结构化、快照原子化、版本可追溯。首先定义状态基类from dataclasses import dataclass, asdict from typing import Optional, Dict, Any import json import time dataclass class AgentState: session_id: str step_count: int 0 last_updated: float 0.0 # 业务状态字段按需扩展 flight_options: list None user_preferences: dict None budget_limit: float 0.0 def __post_init__(self): if self.flight_options is None: self.flight_options [] if self.user_preferences is None: self.user_preferences {} self.last_updated time.time() def to_dict(self) - Dict[str, Any]: return asdict(self) def to_json(self) - str: return json.dumps(self.to_dict(), ensure_asciiFalse)这个基类强制所有状态字段显式声明避免运行时动态赋值导致的不可控。__post_init__确保默认值安全初始化。其次快照必须原子化。我们不用数据库事务而用内存快照异步持久化class StateManager: _cache: Dict[str, AgentState] {} classmethod def get_state(cls, session_id: str) - AgentState: if session_id not in cls._cache: # 从持久层加载如Redis raw redis_client.get(fstate:{session_id}) if raw: data json.loads(raw) cls._cache[session_id] AgentState(**data) else: cls._cache[session_id] AgentState(session_idsession_id) return cls._cache[session_id] classmethod def update_state(cls, state: AgentState): # 1. 先存内存保证当前请求可见 cls._cache[state.session_id] state # 2. 异步写持久层避免阻塞主流程 asyncio.create_task(cls._async_persist(state)) classmethod async def _async_persist(cls, state: AgentState): # 使用Redis的SET命令带过期时间 redis_client.setex( fstate:{state.session_id}, 3600, # 1小时过期防内存泄漏 state.to_json() )这里的关键是update_state不等待持久化完成因为Agent的实时性要求极高。我们接受短暂的“内存-持久层不一致”但通过过期时间兜底。实测在QPS 200的压测下平均延迟增加不到3ms。最后版本可追溯。每次update_state时我们额外记录一个版本号# 在AgentState中增加 dataclass class AgentState: # ... 其他字段 version: int 0 def bump_version(self): self.version 1 self.last_updated time.time() # 更新时 state.bump_version() StateManager.update_state(state)这个version字段看似简单却支撑了关键能力当需要回滚时我们能按version拉取历史快照当做A/B测试时可以按version分组统计不同状态版本下的成功率。它让状态从“当前值”变成了“时间序列”。提示不要在状态里存大对象如原始图片base64、长文本日志。状态只存“指针”和“元数据”。比如用户上传的合同图片状态里只存S3 URL和文件哈希具体内容由专门的文档服务管理。否则状态体积膨胀会直接拖垮Redis。3.2 观测埋点模块用“结构化日志”替代杂乱print观测不是越多越好而是要精准、低成本、可关联。我们摒弃了所有print和logger.info统一使用结构化日志事件Structured Log Event每个事件必须包含三个核心字段event_type标识事件类型、session_id用于全链路追踪、timestamp毫秒级时间戳。定义日志事件基类from datetime import datetime import time dataclass class LogEvent: event_type: str # input_received, llm_thought, tool_call_start session_id: str timestamp: float 0.0 # 业务相关字段按需扩展 query: str thought: str action: str tool_name: str tool_input: dict None tool_output: dict None error: str def __post_init__(self): if self.timestamp 0.0: self.timestamp time.time() if self.tool_input is None: self.tool_input {} if self.tool_output is None: self.tool_output {} def to_dict(self) - dict: data asdict(self) # 格式化时间戳为ISO字符串便于日志系统解析 data[timestamp_iso] datetime.fromtimestamp(self.timestamp).isoformat() return data埋点位置严格遵循Agent生命周期# 在Agent入口处 def handle_user_query(session_id: str, query: str): # 1. 输入观测 log_event LogEvent( event_typeinput_received, session_idsession_id, queryquery ) logger.info(log_event.to_dict()) # 发送到日志收集服务 # 2. 获取并更新状态 state StateManager.get_state(session_id) state.step_count 1 # 3. LLM推理前观测 log_event LogEvent( event_typellm_thought_start, session_idsession_id, queryquery ) logger.info(log_event.to_dict()) # 执行LLM调用... thought, action llm.invoke(prompt_template.format(...)) # 4. LLM推理后观测 log_event LogEvent( event_typellm_thought_end, session_idsession_id, thoughtthought, actionaction, # 记录token消耗需LLM SDK支持 token_usage{prompt: 120, completion: 45} ) logger.info(log_event.to_dict()) # 5. 工具调用观测在工具执行器内 try: result tool_executor.execute(action, tool_input) log_event LogEvent( event_typetool_call_success, session_idsession_id, tool_nameaction, tool_inputtool_input, tool_outputresult ) except Exception as e: log_event LogEvent( event_typetool_call_error, session_idsession_id, tool_nameaction, tool_inputtool_input, errorstr(e) ) logger.info(log_event.to_dict())这套埋点的关键在于事件类型标准化。我们定义了12种核心事件类型覆盖从输入到输出的全路径。运维同学只需在Kibana里输入event_type: tool_call_error就能看到所有工具失败再加tool_name: weather_api就能聚焦到天气服务问题。没有复杂的日志解析规则只有清晰的字段过滤。注意logger.info()调用必须是非阻塞的。我们用的是Python的concurrent.futures.ThreadPoolExecutor包装的日志发送器确保即使日志服务暂时不可用也不会拖慢Agent主流程。实测在日志服务宕机时Agent P99延迟波动小于5ms。3.3 工具链治理模块用“契约先行”驱动开发与协作工具链治理的目标是让每个工具像乐高积木一样能被任意Agent安全、可靠地组合。这要求我们放弃“先写工具再写Agent”的顺序改为契约先行Contract-First先用OpenAPI 3.0规范定义工具接口再生成代码和文档。以一个“股票行情查询工具”为例其OpenAPI YAML片段openapi: 3.0.0 info: title: Stock Quote Tool version: 1.0.0 paths: /quote: get: summary: 获取股票实时行情 parameters: - name: symbol in: query required: true schema: type: string example: AAPL - name: exchange in: query required: false schema: type: string enum: [NASDAQ, NYSE, SHSE, SZSE] default: NASDAQ responses: 200: description: 成功返回行情数据 content: application/json: schema: type: object properties: symbol: type: string price: type: number format: float change_percent: type: number format: float last_updated: type: string format: date-time 404: description: 股票代码不存在 500: description: 内部服务错误有了这个YAML我们用openapi-generator自动生成三样东西Python工具类包含get_quote(symbol: str, exchange: str NASDAQ) - dict方法内部已封装HTTP调用、错误处理、重试逻辑Agent可用的tool description将YAML中的summary、parameters、responses自动转为LLM能理解的自然语言描述Postman测试集合供QA同学直接测试无需写额外脚本。这个流程带来的改变是质的当后端修改了API比如新增currency参数只需更新YAML重新生成所有下游Agent、测试、文档自动同步。我们曾因此将一次API变更的交付周期从3天缩短到2小时。更进一步我们用这个YAML构建了工具链健康看板。每天凌晨一个定时任务会遍历所有工具的OpenAPI文件调用其/health端点每个工具必须实现并将结果存入Prometheus指标名类型说明tool_health_status{toolstock_quote,envprod}Gauge1健康0异常tool_latency_ms{toolstock_quote,quantile0.95}HistogramP95延迟毫秒tool_error_rate{toolstock_quote}Rate每分钟错误率运维同学在Grafana里一眼就能看出哪个工具在拖慢整个Agent集群。这才是真正的“驾驭”不是靠猜而是靠数据。4. 实战问题排查那些让你半夜爬起来的Agent故障现场4.1 故障现象Agent在特定时段批量“失忆”用户重复提问现场还原某教育辅导Agent在每天上午9:00-9:15集中出现“不记得上一轮对话”的问题。用户问“刚才说的公式是什么”Agent回答“我不记得之前的对话”。日志显示input_received事件正常但llm_thought_start事件里的session_id和上一轮不一致。排查路径首先检查session_id生成逻辑前端传来的session_id是JWT token的一部分解码后发现token有效期设为15分钟且9:00整点签发。这意味着所有在9:00签发的token都在9:15过期前端自动刷新token生成新session_id。再查状态管理StateManager.get_state()在session_id不存在时会创建新AgentState导致“失忆”。根本原因会话生命周期与Token生命周期未对齐。Token是认证凭证session_id是状态标识二者不该强绑定。解决方案前端session_id改为独立生成的UUID与JWT token解耦。JWT只用于认证不携带session_id。后端StateManager.get_state()增加fallback逻辑当新session_id不存在时尝试从请求头中读取X-Previous-Session-ID前端在token刷新时主动带上并迁移旧状态。加入熔断如果1小时内同一用户触发超过5次状态迁移自动标记为“可疑会话”降级为无状态模式并告警。实操心得永远不要假设session_id是稳定不变的。在分布式系统中它可能因负载均衡切换、客户端重启、网络抖动而变化。Harness Engineering要求我们为session_id设计“柔性一致性”允许短暂不一致但必须有快速收敛机制。我们后来在所有Agent入口加了一行日志logger.debug(fSession continuity: {old_sid} - {new_sid})专门监控迁移频率。4.2 故障现象Agent响应时间从2s突增至15s但CPU/内存无异常现场还原某客服Agent在某次发布后P95响应时间从2s飙升至15s监控显示服务器CPU、内存、网络带宽均正常。日志里llm_thought_end和tool_call_start之间的时间差巨大。排查路径查llm_thought_end日志发现token_usage字段显示completion tokens高达12000远超正常值通常500。说明LLM生成了超长文本。追溯llm_thought_start日志发现query字段里混入了大量HTML标签div.../div这是前端富文本编辑器未过滤导致的。分析LLM行为当prompt里混入大量无意义HTML模型会尝试“理解”这些标签生成冗长的、试图解释HTML结构的thought导致token爆炸和生成延迟。解决方案前置清洗在handle_user_query入口增加HTML标签过滤import re def clean_html(text: str) - str: # 移除所有HTML标签保留纯文本 clean re.compile(.*?) return re.sub(clean, , text) query clean_html(query)LLM层防护在prompt template中加入硬性约束“你的thought必须在200字以内禁止生成任何HTML、XML、JSON格式内容只用纯中文回答。”增加token预警在llm_thought_end日志中如果completion_tokens 1000自动触发告警并记录完整prompt供人工审计。注意不要依赖LLM自己遵守长度限制。我们测试过在prompt里写“请用100字回答”模型仍有30%概率超限。必须在代码层做硬拦截。后来我们加了一条规则LLM调用前预估prompt长度用tiktoken如果预估completion tokens 1000则自动截断用户输入的最后200字符并在响应中追加提示“您的问题较长已做精简处理。”4.3 故障现象Agent在处理多轮复杂任务时工具调用顺序混乱出现“先支付后选商品”现场还原某电商Agent在用户说“帮我买iPhone 15预算5000顺丰发货”时有时会跳过商品搜索直接调用支付工具导致支付失败。排查路径查llm_thought_end日志发现thought里写着“用户要买iPhone 15我需要先调用支付工具完成下单。”——这明显违背了ReAct逻辑。检查ReAct循环代码发现action解析逻辑有缺陷当LLM输出Action: pay时代码只匹配开头的Action:但忽略了Action Input:后面的内容。而这次LLM输出是Thought: 用户要买iPhone 15我需要先调用支付工具完成下单。 Action: pay Action Input: {amount: 5000}代码错误地把Thought行里的“支付工具”当成了Action导致误触发。根本原因LLM输出解析过于脆弱未做格式校验。解决方案重构解析逻辑强制要求LLM输出严格遵循格式Thought: [你的思考过程] Action: [工具名] Action Input: [JSON格式输入] Observation: [工具返回结果]在代码层做三重校验正则匹配必须同时存在Thought:、Action:、Action Input:三行JSON校验Action Input后的字符串必须是合法JSON工具存在性校验Action字段的值必须在预定义工具列表中。如果任一校验失败不执行工具而是生成一条固定回复“我暂时无法理解您的请求请换一种方式描述。”并记录parsing_failed事件。实操心得永远不要相信LLM的输出格式。我们后来在所有Agent部署时都开启了一个“格式校验开关”开关打开时任何格式错误都会被拦截并告警开关关闭时才走宽松解析。这个开关成了我们灰度发布的利器新版本先开开关跑一周等格式错误率降到0.1%以下再关掉开关全量。5. 工程化进阶从单体Agent到可编排的Agent工作流5.1 为什么需要Agent工作流单体Agent的天花板在哪里单体AgentMonolithic Agent指所有逻辑规划、工具调用、记忆、输出都封装在一个实例里。它简单直接但存在三个硬性天花板第一职责过载难以维护。一个电商Agent既要懂商品搜索又要懂库存校验还要懂支付风控代码行数轻松破万。当支付接口升级你得通读整个Agent代码生怕改错一行就影响搜索功能。第二资源争抢性能瓶颈。所有工具调用都挤在同一个线程/进程里。当“图像识别工具”在做CPU密集型计算时“天气查询工具”的HTTP请求只能排队导致整体响应变慢。第三复用困难成本高昂。你在电商Agent里写的“地址解析工具”无法直接用在物流Agent里因为两者状态结构、错误处理逻辑完全不同。Harness Engineering的进阶就是打破单体走向Agent工作流Agent Workflow把一个复杂任务拆解为多个专业Agent每个Agent只专注一件事它们之间通过明确定义的输入/输出契约协作。这就像工厂流水线每个工位只做一道工序。5.2 工作流编排器用DAG图定义Agent协作关系我们采用**有向无环图DAG**作为工作流的抽象模型。每个节点是一个Agent每条边代表数据流向。以“智能旅行规划”为例其DAG如下[用户Query] ↓ [意图识别Agent] → 判断是订机票、订酒店还是行程规划 ↓ (if 行程规划) [城市筛选Agent] → 根据预算、季节、用户偏好筛选候选城市 ↓ [景点推荐Agent] → 为每个候选城市推荐TOP3景点 ↓ [交通规划Agent] → 计算城市间交通方式及时长 ↓ [最终整合Agent] → 生成图文并茂的行程单实现这个DAG我们不造轮子而是基于成熟的Apache Airflow改造。Airflow原生支持DAG定义、任务调度、失败重试、依赖管理我们只需做两件事封装Agent为Operatorfrom airflow import DAG from airflow.operators.python import PythonOperator from datetime import datetime def run_city_filter(**context): # 从XCom获取上游输出 user_query context[ti].xcom_pull(task_idsintent_recognition, keyquery) # 调用城市筛选Agent result city_filter_agent.run(user_query) # 将结果推送到XCom供下游使用 context[ti].xcom_push(keycities, valueresult) with DAG(travel_planner, start_datedatetime(2024,1,1)) as dag: intent_task PythonOperator( task_idintent_recognition, python_callablerun_intent_recognition ) city_task PythonOperator( task_idcity_filter, python_callablerun_city_filter, # 显式声明依赖 depends_on_pastFalse, trigger_ruleall_success ) # 建立DAG边 intent_task city_task增强XCom传输能力Airflow默认XCom只传小数据48KB。我们替换了XCom backend用Redis存储大对象只在XCom里存Redis Key完美支持图片、PDF等二进制结果的传递。这个方案的好处是所有工作流逻辑可视化、可版本化、可回滚。Airflow UI里你能看到实时DAG图点击任一节点就能看到它的输入、输出、日志、耗时。当“景点推荐Agent”升级你只需修改run_attraction_recommend函数DAG其他部分完全不受影响。5.3 工作流治理如何防止Agent工作流变成“黑盒迷宫”工作流带来灵活性也带来新挑战当DAG有20个节点时一个失败可能引发连锁反应而你根本不知道源头在哪。Harness Engineering要求我们为工作流建立三层治理第一层节点级SLA契约。每个Agent节点必须声明自己的SLAAgent节点P95延迟失败率输入大小限制输出大小限制意图识别800ms0.5%2KB500B城市筛选1.2s1.0%5KB2KB景点推荐2.5s2.0%10KB5KB工作流编排器在调度前会校验整个DAG的SLA叠加是否满足终端用户要求如“全程5s”。如果不满足自动拒绝调度并返回降级方案。第二层链路级可观测性。我们在每个节点的Operator里注入统一的观测SDKdef run_with_observability(func): def wrapper(**context): # 记录节点开始 log_event LogEvent( event_typeworkflow_node_start, session_idcontext[dag_run].conf.get(session_id, unknown), node_idcontext[task].task_id, input_sizelen(str(context[ti].xcom_pull())) ) logger.info(log_event.to_dict()) try: result func(**context) # 记录节点成功 log_event LogEvent( event_typeworkflow_node_success, session_idcontext[dag_run].conf.get(session_id, unknown), node_idcontext[task].task_id, output_sizelen(str(result)), duration_ms(time.time() - start_time) * 1000 ) logger.info(log_event.to_dict()) return result except Exception as e: # 记录节点失败 log_event LogEvent( event_typeworkflow_node_error, session_idcontext[dag_run].conf.get(session_id, unknown), node_idcontext[task].task_id, errorstr(e) ) logger.error(log_event.to_dict()) raise return wrapper这样一个workflow_node_error事件就能精准定位到是哪个节点、哪个会话、哪个输入出了问题。第三层工作流级熔断。当某个节点在5分钟内失败率超过10%工作流编排器自动触发熔断暂停该节点的所有调度将流量路由到备用Agent如用规则引擎Agent替代LLM Agent发送告警并附上最近10次失败的完整日志链接。这套治理机制让工作流不再是“一堆Agent的随意组合”而是一个有纪律、有保障、可信赖的工程系统。6. 总结Harness Engineering的本质是把AI Agent从“艺术品”变成“工业品”写到这里我想起去年在某次技术分享会上一位资深架构师问我“你们做了这么多工程化是不是把AI的‘灵性’给磨没了”我当时没直接回答而是反问他“汽车刚发明时司机要自己调火花塞、换齿轮油、听发动机声音判断故障。今天的司机只需要踩油门、打方向。我们失去的是‘灵性’吗不我们得到的是可预测、可复制、可规模化的出行能力。”AI Agent也一样。Harness Engineering不是要消灭LLM的创造力而是要把它关进一个坚固、透明、可控的“工程容器”里。这个容器里状态是可追溯的行为是可观测的工具是可治理的工作流是可编排的。当你能用kubectl get pods查看Agent实例用curl -X POST /workflow/dag?session_idxxx触发一次完整流程用SELECT * FROM agent_errors WHERE nodepayment AND time now() - 1h查故障你就知道AI Agent已经走出了实验室真正踏上了工业化生产的轨道。我个人在实际操作中的体会是别急着堆功能先搭好Harness。那套状态管理、观测埋点、工具契约的代码可能只占你总代码量的15%但它决定了剩下85%的功能能不能活过上线第一天。最后再分享一个小技巧每周五下午留出30分钟专门做“Harness健康检查”。打开你的日志系统搜event_type: tool_call_error挑出Top 3的错误花10分钟看能否用一行代码加固比如加个空值判断、加个超时设置。坚持三个月你会惊讶于Agent的稳定性提升。这就是Harness Engineering最朴素的胜利。