最近好几个做 Agent 的朋友跑来问我同一个问题:为什么我的 Agent 在 demo 里跑得好好的,一上生产就崩?我翻了他们的代码,发现套路几乎一模一样——调大模型 API、拼一个 tools 列表、让模型自己选工具调用。单看一轮交互逻辑,确实没什么毛病。但并发一上来,上下文一拉长,流程一复杂,整个人就麻了。这背后的根子,在于大家把 Agent 工程想得太“薄”了。
真正能在生产环境里扛住的 Agent,必须拆成三层来看:Harness 管边界,Loop 管思维,Graph 管流程。这三层构成一套完整的 Agent 工程三层架构。这篇就把我落地这套架构的完整经验写出来,包括每一层到底要做什么、为什么这样做、以及我在生产环境里踩过哪些坑。适合正在把 Agent 从原型推向生产的工程师、架构师,也适合想系统搭 Agent 框架的技术团队参考。
1. 为什么 Agent 工程需要三层架构
1.1 一次真实的生产事故
先讲一个我经历过的真实事故。某个客服 Agent 上线第一天,就出现了三件让团队崩溃的事:Agent 在工具调用时没有白名单,直接调用了内部积分系统接口,给一批测试用户加了积分;由于工具返回报错,Agent 没有中止机制,反复重试同一个失败请求,把后端打出了告警;会话上下文没有做任何压缩,五轮对话之后 token 数冲到 3 万,单次请求耗时超过 30 秒,用户端直接超时。
这三件事其实都不是模型的问题。模型推理能力再强,也弥补不了工程侧的漏洞。问题出在三个地方:没有给 Agent 划定执行边界,没有给思维循环设定终止条件,没有把多步骤任务用编排层串起来。对应到架构上,就是缺 Harness、Loop、Graph 这三层。
1.2 三层不是概念,而是边界
很多团队把 Agent 工程理解成“模型 + 工具调用”,这个理解太粗糙。我把它的骨架拆成三层。
Harness 是 Agent 运行的外壳,负责会话生命周期、技能注册、权限校验、沙箱隔离、状态存储。它的核心职责是回答一个问题:Agent 能做什么、不能做什么、能活多久。没有 Harness 的 Agent 就像在办公室里随意使用公章,没人管它有没有权限盖。
Loop 是 Agent 的“想-做-查”循环。它负责的是持续推理和行动,包含每一步的模型调用、工具选择、结果观察、记忆更新、终止条件判断。Loop 决定的是 Agent 单线程自主完成一件事的能力。
Graph 是面向多步骤、多分支、多 Agent 场景的编排层。它把一个复杂任务拆成子节点,定义节点之间的依赖关系、并行关系、失败路径。Graph 解决的是“一个 Loop 搞不定的事怎么拆给多个 Loop 干”。
打个比方:Harness 是公司的办公环境和规章制度,Loop 是员工独立思考和处理问题的工作方式,Graph 是项目负责人拆任务、排期、协调分工的流程表。三层缺一层,生产环境都会出事。
1.3 如何判断当前项目需要几层
不是所有项目都需要完整的 Graph,但几乎所有生产级 Agent 都需要 Harness 和 Loop。我的选型经验大致如下表所示:
| 场景 | 需要哪几层 | 理由 |
|---|---|---|
| 单轮问答、单次工具调用 | Loop + Harness | 一次调用就结束,不需要多步编排 |
| 多轮自主任务、按步骤调用多个工具 | Loop + Harness + 轻量 Graph | 需要控制循环节奏,也需要分支判断 |
| 跨数据源、跨部门协作、多 Agent 协同 | 完整三层 | 必须用 Graph 模型化各个子流程 |
| 面向企业内外用户,有权限要求 | Harness 优先 | 权限边界和安全没设计好,一切免谈 |
一个很常见的误区是,业务方拿着简单的 demo 问“为什么不能直接上生产”。我通常的回答是:先看它是否需要多步推理,需要就补 Loop;再看它是否会在不确定的环境里执行动作,需要就补 Harness;最后看它是否涉及多条并行任务链,需要就补 Graph。
2. Harness 层:Agent 工程里的“运行牢笼”
2.1 Harness 不是 API 封装
很多人以为 Harness 就是把模型 API 包一层,传个 API Key,把对话记录存到数据库里,就完事了。这只能叫 SDK 封装,离 Harness 差得很远。
生产级 Harness 要管的是一整套运行生命周期。一个 Agent 会话从创建到销毁,会有下面这些状态:created、initializing、ready、running、paused、shutdown、failed。每一个状态切换都要有钩子函数,比如进入 running 之前必须检查技能是否已挂载,进入 failed 之后要触发告警和状态归档。
我习惯用一个很小的 Python 骨架来表达 Harness 的最小职责:
class Harness: def __init__(self, session_id, skills, policies): self.session_id = session_id self.skills = skills self.policies = policies self.state = "created" self.memory = [] async def start(self): self.state = "initializing" # 校验技能白名单、加载用户策略、初始化沙箱 await self._init_sandbox() await self._load_skills() self.state = "ready" async def run_loop(self, graph_engine, loop_engine): self.state = "running" # 由 loop_engine 执行推理循环,由 graph_engine 完成节点编排 result = await graph_engine.execute(self, loop_engine) self.state = "shutdown" return result async def kill(self): # 硬停止:无论当前在哪一步,都要终止所有运行中的子任务 self.state = "shutdown" await self._terminate_children()这段代码的重点是呈现生命周期管理的必要性:所有资源都要能清理,所有子任务都要能终止。特别是 kill 方法,这是生产环境的安全底线。
2.2 技能注册与权限控制
Harness 里最容易被忽略的部分是技能注册。所谓技能,其实就是工具。但在生产环境中,工具不能只是把 function 列表塞给模型,而要做统一注册、参数校验和权限前置。
我推荐用统一的技能注册表,每个技能声明它接收什么参数、需要什么角色权限、每秒最多调用多少次。比如:
SKILL_REGISTRY = { "update_user_points": { "description": "更新用户积分,仅限运营角色", "input_schema": { "user_id": "string", "delta": "integer" }, "allowed_roles": ["ops"], "rate_limit": "5/min", "audit_enabled": True }, "fetch_inventory": { "description": "查询供应链库存", "input_schema": {"sku": "string"}, "allowed_roles": ["ops", "analyst"], "rate_limit": "60/min" } }Harness 在执行工具调用之前,必须先查这张注册表,做三件事:校验角色权限、校验参数 schema、校验频率限制。凡是注册表里没有的技能,一律拒绝调用。这样一来,即使模型突发奇想编造了一个工具名,Harness 也能拦下来,而不是傻乎乎地把请求发到后端。
这里必须强调一个原则:Agent 安全不是靠模型自觉,而是在执行侧加护栏。模型只是在生成文本,它没有感知权限的能力。所有安全策略都必须下沉到 Harness 层强制执行。
2.3 沙箱隔离与执行边界
当 Agent 能执行代码、访问文件系统、调用外部命令时,Harness 必须提供沙箱。很多团队第一版 Agent 直接在宿主机上跑 shell 命令,一旦模型输出了一段危险命令,后果不堪设想。
我的做法是:每个会话分配一个临时目录,文件系统只能在这个目录内可见;网络访问走白名单,只允许访问预配置的内部服务和公开数据源;CPU 和内存设置上限,防止单会话耗尽集群资源。对应到容器化方案,就是把每个会话的执行环境做成一个一次性容器,通过 Harness 下发网络策略和挂载卷。
如果团队的容器化条件不成熟,最低限度也要做到“命令白名单 + 参数过滤”。比如只允许 Agent 读取某个目录下的数据,不允许执行 rm、mv 这类高危险命令。记住一句话:给 Agent 最小权限,让它够用就行。权限越大,线上事故的爆炸半径越大。
2.4 生命周期与记忆持久化
Harness 还负责记忆的生命周期管理。Agent 的“记忆”不是一个简单的聊天记录,而是会话过程中产生的完整决策轨迹:每一轮模型输出了什么推理、选择了什么工具、工具返回了什么结果、中间产出了什么文件。
我会把会话状态拆成两层:短期状态存 Redis,长期记忆落对象存储。会话结束之后,短期状态自动过期,长期记忆按用户维度归档。归档数据结构大致如下:
{ "session_id": "s_1024", "user_id": "u_88", "goal": "分析竞品价格变化", "events": [ {"step": 1, "reasoning": "需要获取竞品价格", "action": "web_search", "result_ref": "result://raw/price_1024.json"}, {"step": 2, "reasoning": "价格数据已获取", "action": "summarize", "result_ref": "result://summary/price_1024.md"} ], "archive_at": "2025-06-01T12:00:00Z" }这里有一个值得注意的细节:长期记忆里保存的是 result_ref 引用,而不是完整的结果内容。也就是说,具体结果放在对象存储里,记忆里只存访问路径。这样既能保留细节,又不会把上下文撑爆,还方便后续做审计回溯。关于上下文会不会膨胀的问题,我在 Loop 层会再展开。
3. Loop 层:Agent 的“想-做-查”循环
3.1 三种常用循环模式
Loop 是 Agent 单线自主工作的核心引擎。我不建议团队只会一种 ReAct 模式。不同的任务节奏,应该选不同的循环结构。
| 循环模式 | 工作方式 | 适用场景 | 主要失败模式 |
|---|---|---|---|
| ReAct | 推理 → 行动 → 观察 → 循环 | 有明确工具、依赖中间观察结果 | 工具报错时反复重试 |
| 规划执行 | 先产出计划 → 拆步骤 → 逐步执行 | 任务可拆分成固定步骤 | 计划过期后不重新规划 |
| 多 Agent 辩论 | 多个独立 Loop 互相检查 | 高风险决策、质检场景 | 成本翻倍、结论不稳定 |
这里要提醒一句:ReAct 是基础,但不要写成“模型想一步做一步,错了就一直做”。生产环境里的 Loop 必须有节奏控制。比如我在做财报分析 Agent 时,会让它先调用一次工具查看数据结构,然后强制它输出一份分析计划,确认计划之后再进入下一步。这样虽然多了一次模型调用,但整体成功率会明显提升。
3.2 循环终止条件:别让 Agent 永远转下去
Loop 最怕的事是什么?死循环。模型在一个失败的工具调用上反复尝试,或者不断重复相似动作,白白消耗 token 和下游接口资源。
我给循环控制写的伪代码如下:
def run_loop(harness, goal, max_steps=8): step = 0 history = [] while step < max_steps: # 调用一次 LLM,输入当前目标、历史摘要、可用技能 reasoning, action = await harness.think(goal, history) # 提前终止:模型自己判断目标已达成 if reasoning.get("is_done"): return reasoning, history # 检查重复动作:最近三次动作如果一样且结果不变,强制终止 if detect_repeated_action(history, action): return {"status": "aborted", "reason": "duplicate_action"}, history # 执行工具调用,并把结果写回 observation = await harness.execute(action) history.append((reasoning, action, observation)) # 错误阈值:连续三次工具报错,停止这个循环 if consecutive_errors(history) >= 3: return {"status": "error", "reason": "too_many_errors"}, history step += 1 return {"status": "max_steps_exceeded"}, history请注意终止条件的优先级:目标达成 > 重复动作 > 连续错误 > 最大步数。这四个条件必须同时生效,不能只设一个 max_steps。尤其重复动作检测,是很多 Agent 线上翻车的关键原因。模型经常在做不到某件事的时候,换一种说法做同样的事,如果只按动作名去重,很容易漏判。
3.3 并发下的 Loop:连接复用与退避
很多团队都在问“AI Agent 怎么扛并发”。Loop 层是这个问题的核心环节,因为每一轮循环都要调一次模型 API,模型 API 的限流和普通接口不一样,它对 QPS 的惩罚非常严格。
我的建议是从三个维度做控制。第一,所有模型请求必须走共享连接池,不要每个会话单独建 HTTP Client。我的实测数据是,连接池复用之后,同一压力下 429 报错率能下降一半以上。第二,对每个 Loop 实例做令牌桶限流,控制每秒钟发起模型调用的数量。第三,遇到 429 或 5xx 时执行指数退避,退避公式是sleep = 2 ^ retry_count * base + jitter。base 可以是 0.5 秒,jitter 随机范围在 0 到 0.2 秒之间,防止所有请求同时重试形成“惊群”。
调试期间可以用一个简单并发池来约束同时活跃的 Loop 数量:
from asyncio import Semaphore class LoopPool: def __init__(self, max_concurrency): self.sem = Semaphore(max_concurrency) async def execute(self, task): async with self.sem: return await task()注意,并发池的数值不是越大越好。它取决于下游模型端的 RPM 限制和工具接口的承压能力。说白了,模型 API 才是瓶颈链路,一旦超限,退避只会让单个请求变慢,不会提升整体吞吐。按我的经验,刚开始做并发时,把并发数设置为模型端 RPM 的五分之一,再逐步压测调整,是最稳妥的路径。
3.4 循环里的记忆压缩
Loop 每循环一轮,上下文都会膨胀。如果不做压缩,五轮之后上下文里塞满了工具返回的原始 JSON,token 成本飙升,模型理解能力反而下降。我在做会话跟踪时发现,上下文越长,模型越容易忽略早期信息。
我采用的压缩策略是“摘要 + 槽位”机制。先给每一轮生成一个 50 字到 100 字的摘要,替换掉原始的思考与输出。最近一轮则保留完整内容,因为最近一轮的信息往往对下一步决策最有用。另外,我会在上下文里固定几个槽位:任务目标占 5%,用户原始输入占 10%,最近对话占 50%,历史摘要占 30%,工具结果占 5%。这样分配的好处是,每一轮模型都能稳定看到“用户想要什么、我做到哪一步、最近发生了什么”。
一个实际例子:某轮工具返回了 3000 字的原始价格数据,如果直接把数据塞进上下文,下一轮就多了 3000 字。我的做法是让模型读一遍原始数据,输出“平均价格下降 5%,主要竞品集中在 A、B 两家”,然后把原始数据存到对象存储,只在上下文里保留摘要。这样既不影响后续分析,又不会拖慢推理速度。
4. Graph 层:从单线循环到多线编排
4.1 为什么 Loop 在多任务编排面前失效
单个 Loop 再强,也只是单线在推进。一旦任务变成“同时抓取多个数据源、各跑一套分析、最后汇总成报告”,Loop 就会显得笨拙:只能串行执行,A 数据源抓完再抓 B;中途某一步失败还会拖累整条链路;节点之间也没有清晰的失败隔离边界。
我举一个经常遇到的场景:一个季度经营分析 Agent,需要同时处理销售数据、客服满意度、供应链库存、竞品舆情,最后生成一份综合报告。这四块数据彼此独立,完全可以并行处理。但每一块内部又需要几轮工具调用和分析,这就是一个典型的单线 Loop 做不了的事。用 Graph 来建模,四个数据源就是四个节点,节点内部各跑一个 Loop,节点之间的依赖关系由 Graph 引擎统一调度。
4.2 图与状态机:分支、并行、重试
Graph 层的实现,本质上是一个有向无环图加状态机。我对 Graph 节点的定义一般包含这些字段:id、type、inputs、next、on_success、on_failure。节点类型至少要有 entry、tool、llm、condition、subgraph、sleep 六种。
一个简单的 Graph 配置示意如下:
{ "graph_id": "competitor_analysis_v17", "nodes": [ {"id": "start", "type": "entry", "next": ["fetch_sales", "fetch_csat", "fetch_stock", "fetch_news"]}, {"id": "fetch_sales", "type": "tool", "timeout": 60, "on_success": "analyze_sales", "on_failure": "retry"}, {"id": "analyze_sales", "type": "subgraph", "subgraph": "sales_analysis_flow", "next": "aggregate"}, {"id": "aggregate", "type": "llm", "inputs": ["analyze_sales", "analyze_service", "analyze_stock", "analyze_news"], "next": "gen_report"} ] }这段 JSON 表达的含义是:入口节点先把四个独立任务并行发出去;每个任务出问题后走 retry 节点,重试次数有限就触发 on_failure;四个分支全部完成后进入 aggregate 节点汇总。相比单个 Loop,Graph 最大的优势是失败隔离:其中一个节点跑了三次都失败,只影响这个分支,不会拖死其他三个分支。
我的习惯是,先在白板上画出节点依赖关系,再落到 JSON 里。没有依赖关系的节点一定要标注出来,因为它们就是 Graph 执行器可以并发的对象。很多团队把 Graph 引擎复杂化了,其实只要把每个节点的状态、输入输出、失败策略这三件事管好,执行器就够用了。
4.3 局部到全局:Graph 的调度策略
Graph 层面还有两种尺度要区分清楚。一种是一个 Agent 内部的局部图,就是把一次复杂任务的多个工具调用和中间 LLM 判断编排成一个 DAG;另一种是多 Agent 协作的全局图,由多个独立 Agent 子图组成,子图之间传递的是任务和结果,而不是共享同一份上下文。
我采用的设计原则是:局部图负责并行与分支,全局图负责任务分发与汇总。在设计数据流时,节点之间传递的应该是“结果引用”,而不是完整的中间产物。比如 fetch_sales 节点完成后,把结果写进对象存储并返回一个引用,后续 analyze_sales 节点拿着引用去读取数据。这样可以避免大量数据在网络中重复传输,也能让每个节点轻量化。
把这种策略落到监控上,要重点关注两个指标:一个是“扇出并发数”,就是入口节点同时发起了多少个子任务;另一个是“扇入等待时间”,就是汇总节点要等最慢的那个子任务多久。扇出并发数设太高会打爆下游服务,我目前会控制在 3 到 5 之间,宁可多分几批,也不要一次性全部轰出去。
4.4 Graph 版本化与回滚
生产环境里,Graph 配置的变化频率比代码还要高。业务方经常要调整步骤、新增数据源、修改提示词。如果直接改线上 JSON,出了问题想回退都没有依据。我的做法是把 Graph 配置当成一个正式制品来管理,每一个版本都跟着代码一起走 CI,发布到配置中心,并且每次发布都记录变更人、变更原因。
回到生产事故排查的语境,Graph 版本化最大的价值是“快速回退”。一旦某个新版本跑了十分钟指标异常,操作同学可以直接在配置中心把 flow 切回上一个稳定版本。这里有一个很实用的经验:Graph 配置里要避免用一个 master 版本名来命名的思路,而是直接用 graph_id + 版本号,像competitor_analysis_v17这样。这样回滚之后日志里还能明确看到哪个版本在跑,不会出现日志和线上逻辑对不上的情况。
5. 生产实践:三层架构落地的完整案例
5.1 需求拆解
下面用一个我实际做过的案例,把三层架构完整串一遍。需求是给运营团队做一个“多源竞品情报分析 Agent”。输入一个竞品关键词,输出一份情报日报,内容包括公开价格变化、舆情动态、供应链线索。流程设计要求是:四个数据源并行抓取,每个数据源内部允许三到五轮工具调用,最后汇总生成报告并推送到企业群。
这个需求典型到不能再典型,非常适合体现三层架构的价值。Harness 负责挂载网络抓取、报告生成、群消息推送三个技能,并限制只有运营角色的用户能触发。Loop 负责每个数据源内部的抓取和分析节奏。Graph 负责四个数据源的并行编排,以及最终报告节点的汇总。
5.2 Harness 配置示例
Harness 的 YAML 配置大致如下:
harness: session_ttl: 30m sandbox: type: docker network_policy: allow-internal-only tmp_dir: /sandbox/tmp cpu_limit: 0.5 memory_limit: 1Gi skills: - web_search - fetch_url - write_report - notify_group audit: enabled: true log_all_tools: true这里几个参数值得说明。session_ttl 设为 30 分钟,意味着一个任务最长执行半小时,超时强制销毁会话,这是对 Loop 死循环和 Graph 卡死的一层兜底。sandbox 的 network_policy 是 allow-internal-only,只允许访问内部服务,外部公开网络按域名白名单单独配置。audit 开启后,每一次工具调用的入参和出参都会记审计日志,方便出问题后回溯。
5.3 Loop 与 Graph 实现骨架
Loop 的骨架我已经在第 3 节展示过。这里把它封装成节点,注册进 Graph 引擎:
async def fetch_sales_node(context, loop_engine): async with context.sandbox() as sb: raw = await loop_engine.run( goal="抓取指定竞品近30天的公开销售价格", tools=["web_search", "fetch_url"], max_steps=5 ) return await sb.store(raw, prefix="sales") async def fetch_news_node(context, loop_engine): async with context.sandbox() as sb: raw = await loop_engine.run( goal="抓取最近7天与竞品相关的舆情新闻摘要", tools=["web_search", "fetch_url"], max_steps=4 ) return await sb.store(raw, prefix="news")Graph 引擎调度时,会把 fetch_sales_node 和 fetch_news_node 这两个节点作为并行节点同时下发。每个节点内部是一个独立的 Loop,互不干扰。这里有个实现细节:节点返回的不是原始数据,而是sb.store(...)生成的引用,后续 aggregate 节点再根据引用读取结果。
5.4 压测结果与监控指标
上线前我们做了一轮压测,压测条件为 20 个并发用户,每个任务平均要经历 6 次 LLM 调用和 4 次工具调用。第一版没有做共享连接池和退避策略,结果是 p95 延迟 42 秒,并且频繁出现模型端 429 报错。加上共享连接池、令牌桶限流、指数退避之后,p95 降到 19 秒,429 几乎清零。这个对比充分说明,很多问题不是模型速度决定的,而是客户端侧的并发控制不到位。
我实际用到的监控指标和告警阈值如下表:
| 指标 | 采集方式 | 告警阈值 |
|---|---|---|
| 单轮 LLM 耗时 | 埋点统计 | p95 > 10s |
| Loop 轮数分布 | 会话结束上报 | 平均轮数 > 10 |
| Graph 节点失败率 | 节点状态统计 | 失败率 > 2% |
| 上下文占用 token 数 | 每轮采样 | 单会话 > 30k |
| 沙箱隔离错误 | 容器日志 | 任意一次即告警 |
这些指标里,我会把“Loop 轮数分布”当作第一优先级观察对象。因为轮数一旦异常上涨,通常意味着循环缺少终止条件,或者任务本身超出了 Agent 的能力范围。Graph 节点失败率则负责暴露流程编排的问题,比如某一条分支反复失败,多半是工具报错或超时时间设置不合理。
5.5 部署与回滚注意
部署方面,Harness 本身设计为无状态服务,会话状态全放 Redis,方便水平扩展。Graph 引擎独立部署,和 Harness 之间通过 gRPC 通信,这样 Graph 的版本迭代不会影响到 Harness 的稳定性。发布时先灰 5% 流量,观察节点失败率和 Loop 轮数分布,确认没问题再全量放量。一旦指标异常,立即在配置中心回退 Graph 版本,不需要重新发布代码。
另外,我强烈建议给每个 Agent 会话预留一个强制停止接口。这个接口不经过模型,而是直接由 Harness 执行:把当前 Loop 停掉,把 Graph 里所有正在运行的节点取消,把沙箱销毁。线上出问题时,这个强制停止按钮比什么智能暂停都好用。
6. 常见问题与排查技巧实录
6.1 高频问题速查表
我整理了在团队里被问得最多的问题和对应的排查方案:
| 问题 | 根因 | 解决方案 |
|---|---|---|
| Agent 无限循环不停止 | 终止条件缺失或重复动作未识别 | 补上 is_done、重复检测、错误阈值 |
| 上下文越跑越大 | 每轮结果全量保留 | 用摘要 + 槽位机制压缩历史 |
| Graph 卡在某个失败节点 | on_failure 没有配置或重试无限 | 为每个节点定义有穷重试策略 |
| 并发一高就 429 超时 | 没有共享连接池和退避 | 复用 HTTP Client,加指数退避 |
| 工具调用权限失控 | Harness 缺少白名单与角色校验 | 引入技能注册表,执行前校验 |
| 流程回滚困难 | Graph 配置没有版本管理 | 每个版本走配置中心,支持一键回退 |
| 会话半路中断丢状态 | 短期状态只在内存 | 状态写 Redis,支持残点恢复 |
这些问题的共同点,都是没有把边界划分好。每个根因背后,都对应了三层架构中某一层的职责缺失。
6.2 三条避坑经验
第一条,先跑通 Loop,再上 Graph。不要一上来就把流程画得特别复杂,先把一个节点内的 Loop 做到稳定,再逐步增加分支和并行。我见过太多团队第一个版本就把 Graph 画了十几个节点,结果一出问题根本不知道是哪个节点崩的。
第二条,Harness 的硬停止能力比智能暂停重要得多。智能暂停还需要模型配合,硬停止直接由 Harness 杀掉所有子任务。Agent 这种系统,最怕的不是“做错了”,而是“停不下来”。在做架构评审时,我会先问一个问题:如果模型输出完全不可控,Harness 能兜住吗?兜不住,这个 Agent 不要上线。
第三条,关于技术选型。有人问过我用 Rust 写 Agent 是不是更好,我的观点是:Rust 适合写 Harness 和 Graph 引擎这类底层组件,性能好、并发安全;但业务逻辑层的 Loop 和技能脚本,用 Python 或 TypeScript 迭代效率更高。不要为了用 Rust 而用 Rust,生产系统通常都是混合技术栈,边界清晰才是重点。
我个人在实际操作中的体会是,三层架构并不是什么高深理论,而是事故总结出来的边界。现在每设计一个新 Agent,我都会先想清楚三件事:Harness 的权限边界画在哪里,Loop 的终止条件是否完备,Graph 的节点依赖是否标注清楚。每次踩坑之后回顾,几乎都能在这三件事里找到原因。
最后再分享一个小技巧:不管团队用什么框架,先拿一个坏例子做复盘。找一篇需要 N 轮工具调用的失败会话,手工把每一轮拆成状态记录,再对照三层架构补设计。你会发现大量线上问题,在这个阶段就能提前暴露。把边界定清楚,把循环控住,把流程图画完整,Agent 上生产这件事,远没有想象中那么玄。