Agent 工程三层架构:Harness、Loop、Graph 实战指南
发布时间:2026/10/6 11:16:10 作者:尧图编辑部 阅读量:1,286

1. Agent 工程到底在解决什么问题1.1 从一个真实困境说起去年下半年我接手了一个内部工具项目需求很明确让一个 AI Agent 自动完成“读取需求文档 → 拆解任务 → 调用代码生成 → 跑测试 → 提交结果”这条链路。听起来不复杂但真正动手之后我踩了一连串的坑。第一个坑是状态丢失。Agent 执行到第三步的时候突然忘了第一步读到的需求约束生成出来的代码完全跑偏。第二个坑是错误累积。某一步调用工具返回了格式不对的结果Agent 没有识别出来继续往下走最后整个链路崩掉排查了半天才发现是第二步就错了。第三个坑是并发失控。当我把这个 Agent 开放给团队五个人同时用的时候资源争抢、上下文串台、任务互相覆盖问题一个接一个。这三个坑本质上对应的是 Agent 工程里三个不同层面的问题执行环境的问题、控制流程的问题、任务编排的问题。后来我逐渐摸索出一套分层思路把这些问题拆开来看、分开来解才算是把整个系统跑稳了。这套思路我把它归纳为三层Harness 层、Loop 层、Graph 层。这篇文章就是把这套三层架构从头到尾讲清楚。不管你是刚接触 Agent 开发的新手还是已经在做 Agent 项目但被各种工程问题折磨的同行我都希望这里的内容能帮你少走一些弯路。我会从每一层“为什么需要它”讲起再落到“具体怎么做”最后分享一些我在实际项目中踩过的坑和总结出来的经验。1.2 三层架构的分工逻辑先给一个整体印象。你可以把 Agent 系统想象成一家餐厅Harness 层是厨房本身——灶台、刀具、食材、调料以及厨房的规章制度。它决定了“你能用什么工具做菜”“食材怎么存放”“卫生标准是什么”。对应到 Agent 系统里Harness 就是 Agent 的运行环境、工具集、权限边界和安全约束。Loop 层是厨师的工作流程——先切菜还是先热锅炒到什么程度出锅尝味道不对怎么调整。它决定了“做菜的步骤怎么走”“遇到问题怎么回退”“什么时候算做完”。对应到 Agent 系统里Loop 就是 Agent 的核心执行循环感知、决策、行动、观察、再决策。Graph 层是餐厅的运营调度——哪个厨师负责哪道菜两道菜之间有没有依赖客人加单了怎么插进去高峰期怎么分配工位。它决定了“多个任务怎么协同”“依赖关系怎么管理”“整体流程怎么编排”。对应到 Agent 系统里Graph 就是多 Agent 或多任务之间的编排层。这三层各司其职但又紧密配合。Harness 提供能力Loop 驱动执行Graph 负责协调。缺了任何一层系统都会出问题。下面我逐层展开讲。2. Harness 层Agent 的运行环境与能力边界2.1 Harness 到底是什么很多人第一次听到 Harness 这个词会有点懵。它在英文里的原意是“马具”——套在马身上用来控制马的方向和速度的那套装备。用在 Agent 工程里这个比喻其实非常贴切Harness 就是套在 Agent 身上的一套“控制装备”它决定了 Agent 能做什么、不能做什么、怎么做、做的时候有什么限制。具体来说一个 Harness 层通常包含以下几个部分工具集Tool SetAgent 可以调用的所有外部能力比如读写文件、执行命令、发起网络请求、查询数据库、调用其他 API 等。每个工具都有明确的输入输出定义。权限系统Permission SystemAgent 对每个工具的访问权限。比如某个 Agent 只能读文件不能写文件某个 Agent 只能访问特定目录某个 Agent 执行命令需要人工确认。沙箱环境SandboxAgent 执行操作时的隔离环境。确保 Agent 的操作不会影响到宿主系统的安全和稳定。上下文管理Context ManagementAgent 在工作过程中产生的所有状态、记忆、中间结果的存储和读取机制。安全护栏Guardrails对 Agent 行为的约束规则比如禁止执行某些危险操作、限制单次操作的资源消耗、设置超时和重试策略等。这五个部分合在一起构成了 Agent 的“能力边界”。边界之内Agent 可以自由发挥边界之外一律不允许。2.2 为什么需要 Harness 层没有 Harness 层的 Agent 系统是什么样的我见过也写过。基本上就是一段代码里直接调 LLM API拿到返回结果之后直接执行执行完了把结果拼到下一轮 prompt 里。简单场景下能跑但一旦稍微复杂一点问题就来了。第一个问题是安全。Agent 直接执行 LLM 生成的命令如果 LLM 抽风生成了一个删除文件的命令那就真的删了。我有个朋友在做内部工具的时候就遇到过这种情况Agent 把测试环境的配置文件覆盖了导致整个测试环境挂了半天。第二个问题是不可控。Agent 执行到一半卡住了你不知道它卡在哪一步、为什么卡住、当前状态是什么。没有日志、没有追踪、没有中间状态记录排查问题全靠猜。第三个问题是不可复现。同一个任务今天跑成功明天跑失败你完全不知道差异在哪里。因为每次执行的上下文、工具返回结果、LLM 的随机性都不一样没有一套固定的环境来保证一致性。Harness 层就是为了解决这些问题而存在的。它把 Agent 的执行环境标准化、可控化、可观测化。有了 Harness你才能知道 Agent 在什么环境下、用什么工具、在什么权限范围内、按照什么规则在执行任务。2.3 Harness 层的实操设计要点设计一个 Harness 层我总结下来有五个关键决策点。第一工具粒度怎么定。工具太粗Agent 灵活性不够工具太细Agent 调用次数爆炸。我的经验是按照“一个完整的原子操作”来定义工具。比如“读取文件内容”是一个工具“写入文件内容”是另一个工具而不是把“打开文件、读取、关闭文件”拆成三个工具。同时对于高频组合操作可以提供一个“复合工具”来减少调用轮次。第二权限怎么分级。我通常把权限分为四级只读、受限写入、完全写入、危险操作。只读权限允许 Agent 查看信息但不能修改任何东西受限写入允许 Agent 在指定范围内修改完全写入允许 Agent 在沙箱内自由操作危险操作比如执行系统命令、访问网络需要额外审批或人工确认。分级的好处是你可以根据任务的风险等级来分配不同的权限而不是一刀切。第三沙箱怎么选。沙箱方案有很多种从轻到重依次是进程级隔离、容器级隔离、虚拟机级隔离。进程级隔离最轻量用独立的进程和受限的系统调用来实现适合轻量级任务容器级隔离用 Docker 之类的容器技术隔离性更好适合大多数场景虚拟机级隔离最重但隔离性最强适合高安全要求的场景。选哪个取决于你的任务风险等级和资源预算。第四上下文怎么管。Agent 的上下文包括对话历史、工具调用记录、中间结果、长期记忆等。这些东西不能无限增长否则会撑爆 LLM 的上下文窗口。我的做法是分层管理最近的 N 轮对话保留完整信息更早的对话做摘要压缩工具调用记录只保留关键结果长期记忆存到外部存储按需检索。第五护栏怎么设。护栏包括硬性限制和软性提醒。硬性限制比如单次命令执行不超过 30 秒、单次文件写入不超过 10MB、总调用轮次不超过 50 轮。软性提醒比如当 Agent 连续三次调用同一个工具都失败时触发告警并暂停执行。这些护栏参数需要根据实际任务特点来调整没有万能值。2.4 一个 Harness 配置的参考示例下面是我在一个代码生成类 Agent 项目中实际使用的 Harness 配置用 YAML 格式展示你可以直接参考这个结构来设计自己的配置。harness: name: code-gen-agent version: 1.2 tools: - name: read_file permission: read_only params: path: string timeout: 5s - name: write_file permission: restricted_write params: path: string content: string constraints: allowed_paths: [/workspace/src/**, /workspace/tests/**] max_size: 1MB timeout: 10s - name: run_tests permission: sandbox_execute params: test_path: string constraints: max_duration: 120s allowed_commands: [pytest, npm test, go test] timeout: 130s - name: git_commit permission: requires_approval params: message: string timeout: 15s sandbox: type: container image: agent-sandbox:latest resources: cpu: 2 memory: 4Gi disk: 10Gi network: disabled context: max_rounds: 50 recent_window: 10 summary_threshold: 20 memory_store: redis://localhost:6379/0 guardrails: max_tool_calls_per_round: 5 max_consecutive_failures: 3 total_timeout: 600s on_failure: pause_and_alert这份配置里每个工具都有明确的权限等级、参数定义、约束条件和超时设置。沙箱用容器实现禁用了网络访问。上下文管理设置了最近窗口和摘要阈值。护栏定义了失败重试和告警策略。这套配置跑下来整个 Agent 系统的稳定性提升非常明显。注意allowed_paths和allowed_commands这类白名单一定要写全不要图省事用通配符放开。我见过有人把allowed_paths设成[/**]结果 Agent 把系统日志文件给改了。3. Loop 层Agent 的核心执行循环3.1 Loop 的本质是什么如果说 Harness 是 Agent 的“身体”那 Loop 就是 Agent 的“心跳”。它决定了 Agent 如何一步一步地推进任务从初始状态走到目标状态。最基础的 Agent Loop 可以用四个字概括感知-决策-行动-观察。Agent 先感知当前状态读取上下文、查看工具返回结果然后决策下一步做什么LLM 推理接着执行行动调用工具最后观察行动结果获取工具返回值然后进入下一轮循环。这个循环看起来简单但真正写好并不容易。因为在实际项目中你会遇到各种边界情况工具调用失败了怎么办LLM 返回了无法解析的格式怎么办任务执行到一半发现方向错了怎么办循环什么时候该终止这些问题都需要在 Loop 层解决。3.2 基础 Loop 的常见变体根据任务特点不同Loop 有几种常见的变体我分别说一下适用场景。ReAct Loop是最经典的一种。它的核心思想是让 LLM 在每一步都先输出“思考”Reasoning再输出“行动”Action然后根据行动结果继续下一轮。这种 Loop 的优点是推理过程透明便于调试缺点是每轮都要输出思考过程token 消耗比较大。适合需要复杂推理的任务比如多步数学计算、逻辑推理、策略规划。Plan-and-Execute Loop是先规划再执行。第一轮让 LLM 生成一个完整的执行计划然后按计划逐步执行执行过程中可以根据实际情况调整后续步骤。这种 Loop 的优点是全局视野好不容易跑偏缺点是计划可能跟不上变化需要频繁重规划。适合步骤相对固定的任务比如数据清洗流水线、报告生成。Reflexion Loop是在基础 Loop 上加了“反思”环节。每执行完一个阶段让 LLM 回顾一下前面的执行过程总结经验教训然后带着这些经验进入下一阶段。这种 Loop 的优点是自我纠错能力强缺点是额外增加了反思的 token 开销。适合需要高质量输出的任务比如代码生成、文案撰写。Tree-of-Thought Loop是在每一步都探索多条路径然后选择最优的一条继续。这种 Loop 的优点是能找到更优解缺点是计算成本成倍增加。适合解空间大、需要探索的任务比如复杂问题求解、创意方案生成。3.3 Loop 终止条件的设置Loop 什么时候停这是一个看似简单实则很容易出问题的地方。我见过太多项目因为终止条件没设好要么提前停了任务没完成要么死循环烧了一堆 token。终止条件通常有以下几类我建议组合使用目标达成Agent 明确判断任务已完成。这是最理想的终止条件但需要设计好“完成判断”的逻辑。我的做法是让 Agent 在每轮结束时输出一个状态标记比如DONE、CONTINUE、FAILED然后根据标记决定是否继续。最大轮次设置一个硬性的轮次上限比如 50 轮。超过就强制停止避免无限循环。超时限制设置总执行时间上限比如 10 分钟。超过就停止。连续失败连续 N 轮工具调用失败或 LLM 输出无法解析就停止并报错。人工中断允许人工在任意时刻中断 Loop这在调试和紧急情况下非常有用。这几类条件要同时设置任何一条满足就终止。我通常把最大轮次设为预期轮次的 2-3 倍超时设为预期时间的 3-5 倍这样既能给 Agent 足够的发挥空间又不会失控。3.4 Loop 层的状态管理Loop 每执行一轮都会产生新的状态新的对话消息、新的工具调用记录、新的中间结果。这些状态怎么管理直接影响到 Agent 的表现。我的做法是把状态分为三层短期状态是最近几轮的完整信息包括完整的对话消息、工具调用的输入输出。这部分直接放在 LLM 的上下文窗口里保证 Agent 能看清最近的执行细节。中期状态是稍早一些的信息做摘要压缩后保留。比如把前 10 轮的工具调用记录压缩成一段摘要“前 10 轮中Agent 读取了 3 个文件执行了 2 次测试第一次测试失败因为缺少依赖第二次测试通过。”这样既保留了关键信息又节省了 token。长期状态是跨任务、跨会话的信息存到外部存储里。比如 Agent 在之前任务中学到的经验、用户的偏好设置、项目的背景知识等。这部分按需检索不占用当前上下文窗口。这三层状态的管理策略需要根据任务特点来调整。短期窗口太小Agent 容易忘事短期窗口太大token 消耗高。我的经验值是短期窗口保留最近 8-12 轮中期摘要覆盖前 30-50 轮长期存储按需检索。3.5 一个 Loop 实现的伪代码参考下面是一个 ReAct Loop 的伪代码实现展示了核心的控制流程。这段代码不是可以直接运行的但结构是完整的你可以根据自己用的框架来适配。def agent_loop(task, harness, max_rounds50, timeout600): context initialize_context(task) start_time now() for round_num in range(max_rounds): # 检查超时 if now() - start_time timeout: return {status: timeout, context: context} # 感知构建当前 prompt prompt build_prompt(context) # 决策调用 LLM try: response llm_call(prompt) except LLMError as e: context.add_error(e) if context.consecutive_failures 3: return {status: llm_failed, context: context} continue # 解析 LLM 输出 parsed parse_response(response) if parsed is None: context.add_error(parse_failed) continue # 检查是否完成 if parsed.status DONE: return {status: success, result: parsed.result, context: context} if parsed.status FAILED: return {status: failed, reason: parsed.reason, context: context} # 行动执行工具调用 if parsed.action: tool harness.get_tool(parsed.action.name) if tool is None: context.add_error(funknown_tool: {parsed.action.name}) continue if not harness.check_permission(tool, parsed.action.params): context.add_error(fpermission_denied: {parsed.action.name}) continue try: result tool.execute(parsed.action.params) context.add_tool_result(parsed.action, result) except ToolError as e: context.add_error(e) if context.consecutive_failures 3: return {status: tool_failed, context: context} # 观察更新上下文 context.add_round(round_num, parsed, result if parsed.action else None) context.maybe_compress() return {status: max_rounds, context: context}这段代码里每一轮都包含感知、决策、行动、观察四个环节同时有超时检查、失败计数、权限校验、上下文压缩等保护机制。实际项目中你还需要根据具体框架来补充错误处理、日志记录、状态持久化等细节。实操心得maybe_compress()这个函数非常关键。我一开始没做上下文压缩跑到 30 轮左右的时候 token 就爆了。后来加了压缩逻辑把早期轮次做摘要才把上下文控制在合理范围内。压缩的时机建议在每轮结束后检查如果当前 token 数超过阈值的 70%就触发压缩。4. Graph 层多任务与多 Agent 的编排4.1 为什么需要 Graph 层单 Agent 单 Loop 能解决的问题是有限的。当你的任务变得复杂需要多个 Agent 协作、多个任务并行、任务之间有依赖关系的时候就需要 Graph 层来编排。举个实际例子。我之前做过一个“自动生成项目文档”的 Agent 系统整个流程包括读取代码仓库 → 分析代码结构 → 提取函数签名和注释 → 生成 API 文档 → 生成使用示例 → 生成 README → 汇总输出。这里面有好几个步骤是可以并行的比如“提取函数签名”和“生成使用示例”可以同时做也有严格的依赖关系比如“生成 API 文档”必须在“提取函数签名”之后。如果只用单 Loop 串行执行效率很低而且一旦某一步失败整个流程都要重来。用 Graph 层来编排就可以把任务拆成节点定义节点之间的依赖关系让能并行的并行、有依赖的按顺序执行、失败的节点单独重试。4.2 Graph 层的核心概念Graph 层有几个核心概念需要搞清楚。节点Node是 Graph 的基本单元每个节点代表一个任务或一个 Agent 的执行单元。节点有输入、输出、执行逻辑和状态。边Edge连接两个节点表示依赖关系。边可以是有向的A 必须在 B 之前也可以是无向的A 和 B 可以并行。边还可以带条件比如“只有当 A 成功时才执行 B”。状态State是 Graph 执行过程中的全局状态所有节点都可以读取和写入。状态管理是 Graph 层最复杂的部分之一因为多个节点可能同时读写状态需要处理好并发和一致性问题。调度器Scheduler负责决定哪个节点在什么时候执行。调度器需要考虑依赖关系、资源可用性、优先级等因素。检查点Checkpoint是 Graph 执行过程中的快照用于故障恢复。当某个节点失败时可以从最近的检查点恢复而不需要从头开始。4.3 常见的 Graph 编排模式根据任务特点Graph 编排有几种常见模式。串行链Sequential Chain是最简单的模式节点一个接一个执行前一个的输出是后一个的输入。适合步骤固定、依赖明确的流程。并行扇出Parallel Fan-out是一个节点触发多个并行节点等所有并行节点完成后汇总结果。适合可以并行处理的子任务比如同时分析多个文件、同时调用多个 API。条件分支Conditional Branch是根据某个节点的输出决定走哪条分支。适合需要根据情况选择不同处理路径的场景比如根据代码语言选择不同的分析器。循环Loop in Graph是某个节点或某组节点需要重复执行直到满足条件。适合需要迭代优化的场景比如反复修改代码直到测试通过。子图Subgraph是把一组节点封装成一个子图作为一个整体被上层 Graph 调用。适合模块化设计把复杂流程拆成多个可复用的子流程。这几种模式可以组合使用。实际项目中的 Graph 往往是多种模式的混合比如一个串行链里嵌套一个并行扇出并行扇出的结果又进入一个条件分支。4.4 Graph 层的状态管理策略Graph 层的状态管理比 Loop 层复杂得多因为多个节点可能同时读写状态。我总结了几条实践经验。第一状态要分片。不要把所有的状态都放在一个大对象里而是按节点或按功能分片。每个节点只读写自己关心的那部分状态减少冲突。第二写入要加锁。当多个节点可能同时写入同一片状态时必须加锁。锁的粒度要合适太粗会影响并发太细会增加复杂度。我的做法是按状态分片加锁每个分片一把锁。第三读取要快照。节点读取状态时应该读取一个快照而不是实时读取。这样可以避免读到一半状态被其他节点改了导致数据不一致。第四状态要可序列化。为了支持检查点和故障恢复状态必须能够序列化和反序列化。这意味着状态里不能放函数、连接、文件句柄这类不可序列化的东西。第五状态要可观测。每个节点的状态变化都要有日志记录方便排查问题。我通常会在状态变更时记录谁改的、改了什么、什么时候改的、改成什么了。4.5 一个 Graph 编排的配置示例下面是一个文档生成 Agent 系统的 Graph 配置示例用 JSON 格式展示。{ graph_name: doc-gen-pipeline, version: 1.0, state_schema: { repo_path: string, code_structure: object, function_signatures: array, api_docs: string, usage_examples: string, readme: string, final_output: string }, nodes: [ { id: read_repo, type: agent, agent: repo-reader, inputs: [repo_path], outputs: [code_structure], timeout: 60 }, { id: extract_signatures, type: agent, agent: signature-extractor, inputs: [code_structure], outputs: [function_signatures], timeout: 120 }, { id: gen_api_docs, type: agent, agent: api-doc-writer, inputs: [function_signatures], outputs: [api_docs], timeout: 180 }, { id: gen_examples, type: agent, agent: example-generator, inputs: [function_signatures], outputs: [usage_examples], timeout: 180 }, { id: gen_readme, type: agent, agent: readme-writer, inputs: [code_structure, api_docs, usage_examples], outputs: [readme], timeout: 120 }, { id: merge_output, type: function, function: merge_docs, inputs: [api_docs, usage_examples, readme], outputs: [final_output], timeout: 30 } ], edges: [ {from: read_repo, to: extract_signatures}, {from: extract_signatures, to: gen_api_docs}, {from: extract_signatures, to: gen_examples}, {from: gen_api_docs, to: gen_readme}, {from: gen_examples, to: gen_readme}, {from: gen_readme, to: merge_output} ], checkpoint: { enabled: true, store: redis://localhost:6379/1, interval: node_complete }, retry_policy: { max_retries: 2, backoff: exponential, retry_on: [timeout, tool_error] } }这个配置里extract_signatures之后有两个并行节点gen_api_docs和gen_examples它们都完成后才进入gen_readme。每个节点都有超时设置整个 Graph 有检查点和重试策略。这种结构比单 Loop 串行执行效率高很多而且某个节点失败时可以单独重试不影响其他节点。4.6 Graph 层的并发控制并发是 Graph 层最棘手的问题之一。多个节点同时执行会争抢资源、互相干扰。我总结了几个并发控制的要点。资源池化。把 LLM 调用、工具执行、存储访问这些资源做成资源池节点执行时从池里取资源用完归还。这样可以避免资源被某个节点独占也能控制总并发数。优先级调度。不是所有节点都同等重要。关键路径上的节点应该优先调度非关键路径的节点可以延后。我通常会给每个节点设一个优先级调度器按优先级从高到低调度。限流。对 LLM 调用这类有速率限制的资源必须做限流。我的做法是用令牌桶算法每个资源池有一个令牌桶节点执行前先取令牌取不到就等待。超时和熔断。每个节点都有超时超时后强制终止。如果某个节点连续失败多次触发熔断暂时不再调度这个节点避免浪费资源。死锁检测。当节点之间有循环依赖时可能发生死锁。需要在调度器里做死锁检测发现死锁时打破循环比如终止优先级最低的节点。踩坑记录我曾经在一个 Graph 里设置了两个节点互相等待对方的输出结果整个 Graph 卡死。后来加了死锁检测才解决。死锁检测的逻辑不复杂就是在调度前检查依赖图里有没有环有环就报错。5. 三层架构的协同与生产实践5.1 三层之间怎么配合Harness、Loop、Graph 这三层不是孤立的它们之间有明确的接口和协作关系。Graph 调用 Loop。Graph 层的每个 Agent 节点内部就是一个 Loop。Graph 负责决定“什么时候启动这个 Loop”“给这个 Loop 什么输入”“Loop 的输出怎么传给下一个节点”。Loop 执行完毕后把结果返回给 GraphGraph 再决定下一步。Loop 依赖 Harness。Loop 在执行过程中每次需要调用工具时都通过 Harness 来调用。Harness 负责权限校验、沙箱执行、结果返回。Loop 不需要关心工具具体怎么执行只需要关心“我要调用什么工具、传什么参数、拿到什么结果”。Graph 也依赖 Harness。Graph 层的节点调度、状态管理、检查点存储也需要 Harness 提供的基础能力支持。比如状态存储需要 Harness 的存储工具检查点需要 Harness 的持久化能力。这三层的关系可以用一句话概括Graph 管流程Loop 管执行Harness 管能力。流程决定做什么执行决定怎么做能力决定能做什么。5.2 生产环境中的部署考量把这套三层架构部署到生产环境有几个实际问题需要考虑。第一可观测性。生产环境最重要的是能看清系统在干什么。我通常会在三层都加日志和指标Harness 层记录每次工具调用的输入输出和耗时Loop 层记录每轮的决策和状态变化Graph 层记录每个节点的启动、完成、失败和重试。这些日志汇总到一个可观测性平台方便排查问题。第二弹性伸缩。生产环境的负载是波动的需要能弹性伸缩。我的做法是把 Loop 执行器做成无状态的可以水平扩展Graph 调度器做成有状态的但支持主备切换Harness 的工具执行器根据负载动态调整实例数。第三故障恢复。生产环境难免出故障关键是要能快速恢复。Graph 层的检查点机制在这里非常重要每个节点完成后都存检查点故障后从最近的检查点恢复不需要从头重跑。Loop 层也要支持断点续跑把当前状态持久化恢复后继续执行。第四版本管理。Agent 系统迭代很快prompt、工具、Graph 配置都会频繁变更。我建议对每个变更都做版本管理记录变更内容、变更时间、变更人。这样出问题时可以快速回滚到上一个稳定版本。第五成本控制。LLM 调用是主要成本来源。我通常会在 Harness 层做 token 计数和成本统计在 Loop 层做上下文压缩和缓存在 Graph 层做节点复用和结果缓存。多管齐下把成本控制在合理范围内。5.3 一个完整的生产案例说一个我实际做过的项目。这是一个“自动代码审查”的 Agent 系统需求是当有新的 Pull Request 提交时自动审查代码检查代码风格、潜在 bug、安全问题并给出修改建议。整个系统的架构是这样的Harness 层定义了代码审查 Agent 能用的工具读取 PR diff、读取相关文件、查询代码规范、调用静态分析工具、写入审查意见。权限方面读取类工具是只读权限写入审查意见需要审批。沙箱用容器实现禁用了网络访问。护栏设置了单次审查不超过 5 分钟、最多读取 50 个文件。Loop 层用的是 ReAct Loop 加 Reflexion。Agent 先读取 PR diff然后逐个文件分析每分析完一个文件做一次反思总结发现的问题然后继续下一个文件。终止条件是所有文件分析完毕或达到最大轮次。Graph 层把整个审查流程拆成节点获取 PR 信息 → 并行分析多个文件 → 汇总审查意见 → 生成审查报告 → 提交审查意见。文件分析节点可以并行执行汇总节点等所有分析节点完成后执行。这套系统上线后代码审查的效率提升很明显。以前一个人审查一个中等规模的 PR 要半小时到一小时现在 Agent 几分钟就能给出初步审查意见人工只需要复核和补充。当然也遇到过问题比如 Agent 有时候会误报把正常的代码模式当成问题。后来在 Loop 层加了“置信度评估”让 Agent 对每个发现的问题给出置信度低置信度的只提示不报错误报率就降下来了。5.4 三层架构的常见误区在实践中我见过一些常见的误区这里列出来供你参考。误区一把三层混在一起。有些人写 Agent 系统把工具调用、循环控制、任务编排全写在一个大函数里代码又长又乱改一处影响一片。正确的做法是分层解耦每层只关心自己的职责。误区二Harness 层太薄。有些人觉得 Harness 层就是简单包装一下工具调用不需要太复杂。结果权限控制、沙箱隔离、护栏限制都没做系统上线后各种安全问题。Harness 层是安全的基础不能省。误区三Loop 层没有终止保护。我见过一个项目Loop 没有设置最大轮次结果 Agent 陷入死循环一晚上烧了几百美元的 token。终止保护是必须的宁可提前停不可无限跑。误区四Graph 层过度设计。有些人一上来就搞复杂的 Graph 编排节点几十个边几百条结果调试困难、维护成本高。我的建议是从简单开始先串行跑通再逐步引入并行和条件分支。误区五忽视可观测性。生产环境出问题时没有日志和指标排查全靠猜。可观测性要从一开始就设计进去不能等出了问题再加。6. 常见问题与排查技巧实录6.1 常见问题速查表下面这张表整理了我在实际项目中遇到的高频问题、原因和解决方法你可以当作速查手册用。问题现象可能原因排查方法解决方法Agent 执行到一半卡住Loop 死循环或工具调用阻塞查看当前轮次和最后调用的工具设置最大轮次和工具超时加死锁检测Agent 忘记之前的上下文上下文窗口溢出或压缩过度检查 token 数和压缩日志调整压缩阈值增加短期窗口大小工具调用权限被拒Harness 权限配置过严查看权限校验日志调整权限等级或白名单Graph 节点执行顺序错乱依赖关系配置错误检查 Graph 的边定义修正依赖关系加死锁检测并发任务互相干扰状态管理没有分片或加锁检查状态读写日志状态分片加锁读取用快照LLM 输出格式无法解析prompt 不够明确或模型不稳定查看原始 LLM 输出优化 prompt加格式校验和重试成本超预算token 消耗过大或调用次数过多统计 token 和调用次数上下文压缩结果缓存限流故障后无法恢复没有检查点或状态不可序列化检查检查点配置和状态结构加检查点确保状态可序列化6.2 几个典型问题的深入排查问题一Agent 反复调用同一个工具。这个问题的表现是 Agent 在 Loop 里反复调用同一个工具每次都得到相似的结果但就是不走下一步。原因通常是 LLM 没有正确理解工具返回的结果或者 prompt 里没有明确告诉 LLM“这个工具已经调用过了不要再调”。排查方法是看 Loop 的日志找到重复调用的轮次看 LLM 的输入和输出。解决方法有两个一是在 prompt 里加“已调用工具”的记录让 LLM 知道哪些工具已经调过了二是在 Loop 层加检测如果连续三轮调用同一个工具且参数相同就强制中断并报错。问题二Graph 节点执行超时。Graph 里某个节点执行时间过长导致整个 Graph 超时。原因可能是节点内部的 Loop 没有终止保护或者工具调用阻塞了。排查方法是看节点的执行日志找到耗时最长的环节。解决方法是在节点级别设置超时超时后强制终止节点并根据重试策略决定是否重试。同时检查节点内部的 Loop 是否有终止保护。问题三状态不一致。多个节点并发读写状态导致状态不一致。表现是某个节点读到的状态和预期不符或者状态被覆盖。排查方法是看状态变更日志找到冲突的读写操作。解决方法是状态分片、加锁、读取用快照。如果冲突频繁考虑把并发节点改成串行或者用更细粒度的状态分片。6.3 独家避坑技巧最后分享几个我在实践中总结的避坑技巧都是踩过坑之后才悟出来的。技巧一先串行跑通再并行。不要一上来就搞复杂的并行 Graph先用串行把整个流程跑通确认每个节点都能正常工作再逐步引入并行。这样出问题时容易定位。技巧二给每个工具加 dry-run 模式。在 Harness 层给每个工具加一个 dry-run 模式只返回模拟结果不实际执行。这样在调试 Loop 和 Graph 的时候可以快速验证流程不用担心副作用。技巧三prompt 里加“当前进度”。在 Loop 的每轮 prompt 里明确告诉 LLM 当前是第几轮、已经完成了哪些步骤、还剩哪些步骤。这样可以减少 LLM 的迷茫感提高执行效率。技巧四状态变更要记审计日志。每次状态变更都记录谁改的、改了什么、什么时候改的、改成什么了。出问题时可以快速回溯。技巧五定期做全链路压测。生产环境上线前做一次全链路压测模拟高并发场景看看系统在压力下的表现。我见过太多系统平时跑得好好的一上量就崩。技巧六保留人工干预入口。不管 Agent 多智能都要保留人工干预的入口。当 Agent 卡住或跑偏时人工可以随时介入暂停、修改、重启。这在生产环境非常重要。这套三层架构不是银弹它解决的是 Agent 工程中的结构性问题。具体到每个项目还需要根据任务特点、团队情况、资源预算来调整。但只要你把这三层的职责分清楚把每层的核心问题解决好Agent 系统的稳定性和可维护性就会有质的提升。我在实际项目中最大的体会是Agent 工程的难点不在于让 Agent 变聪明而在于让 Agent 变得可控。Harness 控制能力边界Loop 控制执行流程Graph 控制任务编排三层合在一起才是一个可控的 Agent 系统。