AI Agent Harness 七个子系统:从能跑到能扛的工程实践

1. 拆开 AI Agent 的“干活引擎”:Harness 到底在管什么

很多人第一次听到 Harness 这个词,脑子里浮现的是线束、马具、安全带,跟 AI 八竿子打不着。但在 AI Agent 的语境里,Harness 指的是把大模型从“只会聊天”变成“能真正干活”的那套运行时骨架。你可以把它理解成 Agent 的操作系统:模型是 CPU,Harness 就是主板、总线、中断控制器和驱动层的集合体。没有它,模型再强也只是个被困在对话框里的嘴炮王者。

我最早接触这个概念是在做一个自动化工单处理的 Agent 项目。当时天真地以为,把提示词写好、把工具函数注册进去就完事了。结果上线第一天就翻车:模型连续调用同一个搜索工具七次,上下文爆了,任务还没完成。那一刻我才意识到,Agent 的瓶颈从来不在模型智商,而在 Harness 的工程成熟度。后来我花了两周时间把整个运行时拆开重写,才慢慢摸清楚它内部到底需要哪些子系统来支撑。

这篇文章适合三类人看:一是正在从零搭建 AI Agent、被各种循环和状态搞晕的开发者;二是用现成框架但总觉得“哪里不对劲”、想深入理解底层机制的工程师;三是产品经理或技术负责人,需要判断一个 Agent 产品到底靠不靠谱。我会把 Harness 拆成七个子系统,逐个讲清楚它们各自管什么、为什么必须存在、以及我在实操中踩过的坑。读完你至少能明白:为什么有些 Agent 能连续干活半小时不崩,有些三分钟就开始胡言乱语。

提示:Harness 这个词在不同团队里叫法不一样,有的叫 Agent Runtime,有的叫 Orchestration Layer,还有的叫 Agent Engine。本文统一用 Harness,指代“模型之外、让 Agent 能持续执行任务的全部工程设施”。

2. 七个子系统的整体架构与设计逻辑

2.1 为什么是七个子系统,而不是三个或十个

先把结论摆出来:一个能稳定干活的 Harness,最少需要七个职责明确的子系统。少了任何一个,Agent 都会在某个场景下出现结构性缺陷。多了也不是不行,但会增加不必要的复杂度和调试成本。

这七个系统分别是:Agent Loop(主循环)、LLM Integration(模型接入层)、Tool Registry(工具注册与调度)、Memory & Context(记忆与上下文管理)、State Manager(状态管理)、Guardrail & Safety(护栏与安全)、Observability(可观测性)。它们之间的关系不是简单的线性调用,而是围绕主循环形成一个协作网络。

我试过把 Memory 和 State 合并成一个模块,结果发现两者的生命周期完全不同:Memory 是面向对话历史的,可以压缩、摘要、丢弃;State 是面向任务执行的,必须精确、不可丢失。混在一起写,代码很快就变成一团乱麻。也试过把 Guardrail 做成一个独立的中间件层,结果发现它需要在 Loop 的多个节点介入,单独抽出来反而增加了跨层调用的复杂度。最终定下来的七个子系统,是在“职责清晰”和“实现简洁”之间反复权衡后的结果。

2.2 各子系统之间的数据流与协作关系

理解 Harness 的关键,不是记住七个名字,而是搞清楚它们之间的数据流。我用一个实际的任务场景来说明:用户说“帮我查一下上周的销售数据,生成一份周报,发到我的邮箱”。

Agent Loop 接收到这个输入后,首先调用 LLM Integration 把用户意图和当前上下文发给模型。模型返回一个“调用销售数据查询工具”的指令。Tool Registry 负责找到对应的工具函数并执行,拿到原始数据。这时候 Memory & Context 要把这次工具调用的结果记录下来,同时判断是否需要压缩之前的对话历史。State Manager 则要更新任务状态:从“理解意图”推进到“数据获取完成”。Guardrail 在整个过程中持续检查:工具调用是否越权、返回数据是否包含敏感信息、模型输出是否符合格式要求。Observability 在后台记录每一步的耗时、token 消耗和中间结果,方便出问题时回溯。

这个流程看起来顺理成章,但实际写代码的时候,最容易出问题的就是状态同步。比如工具执行到一半超时了,State Manager 需要知道是重试还是放弃,Memory 需要决定要不要把这次失败记录进上下文,Guardrail 需要判断是否触发了异常阈值。这些判断逻辑如果散落在各个模块里,维护起来就是噩梦。我的做法是:所有跨模块的状态变更,必须通过 State Manager 统一收口,其他模块只能读取状态,不能直接修改。

2.3 从“能跑”到“能扛”的关键设计取舍

很多教程教你搭 Agent,跑通一个 demo 就结束了。但 demo 和生产的差距,比人和狗还大。我总结下来,Harness 设计中最关键的三个取舍是:

第一,同步还是异步。工具调用如果全部同步阻塞,Agent 的响应速度会非常慢,尤其是涉及网络请求的工具。但如果全异步,状态管理会变得极其复杂。我的经验是:LLM 调用和工具执行走异步,但状态更新走同步队列,保证顺序一致性。

第二,上下文全量保留还是动态压缩。全量保留会导致 token 爆炸,动态压缩又可能丢失关键信息。我目前的方案是分层:最近三轮对话全量保留,更早的历史做摘要,工具返回的大块数据只保留结构化摘要和原始数据的引用 ID。

第三,错误处理是重试还是降级。模型调用失败、工具超时、格式解析错误,每种错误的处理策略都不一样。我的原则是:LLM 调用失败可以重试两次,工具超时只重试一次,格式解析错误直接让模型重新生成,但最多三次。超过阈值就触发降级策略,把任务标记为“需要人工介入”。

3. 核心子系统深度拆解与实操要点

3.1 Agent Loop:整个 Harness 的心脏

Agent Loop 是 Harness 里最核心的子系统,它决定了 Agent 是“单次问答”还是“持续干活”。一个完整的 Loop 通常包含以下步骤:

  1. 接收输入(用户消息或系统事件)
  2. 组装当前上下文(历史对话 + 任务状态 + 工具描述)
  3. 调用 LLM 获取下一步动作
  4. 解析模型输出(是直接回复还是调用工具)
  5. 如果是工具调用,执行工具并获取结果
  6. 更新状态和上下文
  7. 判断任务是否完成,未完成则回到步骤 2

这个循环看起来简单,但实际写的时候有几个关键点。第一,循环终止条件必须明确。我见过太多 Agent 陷入死循环,就是因为没有设置最大迭代次数和超时时间。我的配置是:最大迭代 15 次,单次任务总超时 120 秒,超过就强制终止并返回当前进度。

第二,每轮循环的上下文组装要控制成本。不是把所有历史都塞进去,而是根据当前任务阶段动态选择。比如在“数据收集”阶段,重点保留工具返回结果;在“报告生成”阶段,重点保留用户需求和数据摘要。

第三,循环中的异常要分级处理。我通常把异常分为三级:可恢复异常(如网络抖动)自动重试,可降级异常(如某个工具不可用)切换到备用方案,致命异常(如状态损坏)立即终止并告警。

注意:Agent Loop 的最大迭代次数不是拍脑袋定的。我的经验值是:简单任务 5 次以内,中等任务 10 次左右,复杂任务 15 次封顶。超过 15 次还没完成,大概率是任务定义本身有问题,继续跑只是浪费 token。

3.2 LLM Integration:模型接入层的稳定性设计

LLM Integration 看起来只是“调个 API”,但实际上它是 Harness 里最容易出稳定性问题的环节。我踩过的坑包括:API 限流导致请求失败、模型返回格式不符合预期、流式输出中断、多模型切换时上下文不兼容。

限流处理是第一个要解决的问题。我的做法是在接入层实现一个令牌桶限流器,同时维护一个请求队列。当触发限流时,请求进入队列等待,而不是直接失败。队列长度超过阈值时,触发降级策略,比如切换到备用模型或返回“系统繁忙”提示。

格式校验是第二个关键点。模型返回的 JSON 经常有各种问题:多了个逗号、少了引号、嵌套层级不对。我的方案是在接入层做两层校验:第一层用正则做快速检查,第二层用 JSON Schema 做严格校验。校验失败时,把错误信息反馈给模型,让它重新生成,最多重试三次。

多模型切换是第三个难点。不同模型的上下文格式、工具调用协议、输出风格都不一样。我的做法是定义一个统一的内部消息格式,每个模型适配器负责把内部格式转换成该模型需要的格式。这样切换模型时,只需要换适配器,上层逻辑不用动。

# 简化的 LLM 接入层结构示意 class LLMAdapter: def __init__(self, model_name, api_key, base_url): self.model_name = model_name self.client = build_client(api_key, base_url) self.rate_limiter = TokenBucket(rate=10, capacity=20) async def call(self, messages, tools=None, max_retries=3): for attempt in range(max_retries): await self.rate_limiter.acquire() try: response = await self.client.chat( model=self.model_name, messages=self._format_messages(messages), tools=self._format_tools(tools) ) return self._parse_response(response) except RateLimitError: await asyncio.sleep(2 ** attempt) except FormatError as e: messages.append({"role": "user", "content": f"格式错误:{e},请重新生成"}) raise LLMCallFailed("超过最大重试次数")

3.3 Tool Registry:工具注册与调度的工程细节

Tool Registry 管的是“Agent 能用什么工具、怎么找到工具、怎么执行工具”。听起来简单,但实际做起来有一堆细节。

工具描述的质量直接决定模型能不能正确调用。我见过很多团队把工具描述写得极其简略,比如“查询数据”四个字,结果模型根本不知道什么时候该用、参数怎么传。好的工具描述应该包含:功能说明、适用场景、参数含义、返回值格式、调用示例。我通常要求每个工具的描述不少于 100 字,并且必须有一个完整的调用示例。

工具执行要支持超时和取消。有些工具(比如网络请求)可能卡住很久,如果不设超时,整个 Agent 就挂在那里了。我的配置是:每个工具默认超时 30 秒,可以在注册时单独指定。超时后触发取消信号,同时记录日志。

工具权限要分级。不是所有工具都对所有任务开放。比如“发送邮件”这种有副作用的工具,应该只在特定任务类型下可用。我的做法是在工具注册时打上标签,Agent Loop 根据当前任务类型动态过滤可用工具列表。

工具类型典型示例超时设置权限级别重试策略
只读查询搜索、数据库查询15秒低重试2次
计算处理代码执行、数据转换30秒中重试1次
有副作用发邮件、写文件20秒高不重试
外部集成第三方API调用45秒中重试1次

3.4 Memory & Context:让 Agent 记住该记住的

Memory 和 Context 是 Harness 里最容易被低估的子系统。很多人觉得“不就是把历史对话塞进去吗”,结果 token 消耗爆炸,模型还经常“忘记”关键信息。

我的做法是把记忆分成三层:短期记忆、工作记忆、长期记忆。短期记忆是最近几轮对话,全量保留;工作记忆是当前任务的中间结果,结构化存储;长期记忆是跨任务的知识,用向量数据库存储,按需检索。

上下文组装的时候,不是简单拼接,而是根据当前任务阶段动态选择。比如在“理解需求”阶段,重点放用户原始输入和历史偏好;在“执行工具”阶段,重点放工具描述和最近一次工具返回结果;在“生成回复”阶段,重点放任务状态和关键数据摘要。

上下文压缩是必须做的。我的策略是:当 token 超过模型上限的 70% 时,触发压缩。压缩不是简单截断,而是让模型对早期对话做摘要,保留关键信息。摘要的提示词我调了很多版,最终稳定下来的版本是:“请用不超过 200 字总结以下对话的核心信息,保留所有任务相关的数据、决策和约束条件,丢弃寒暄和重复内容。”

实操心得:上下文压缩的时机很关键。压得太早,模型会丢失细节;压得太晚,token 已经爆了。我实测下来,70% 阈值比较稳。另外,压缩后的摘要要标记来源,方便出问题时回溯原始对话。

3.5 State Manager:任务状态的精确管理

State Manager 管的是“任务当前进行到哪一步了、下一步该做什么、哪些条件已经满足”。它和 Memory 的区别在于:Memory 面向对话,State 面向任务。

一个任务的状态通常包含:任务 ID、任务类型、当前阶段、已完成步骤、待完成步骤、关键变量、错误计数、开始时间、超时时间。这些状态必须精确,不能有歧义。我见过因为状态不同步导致的 bug:工具已经执行成功了,但状态还停留在“执行中”,导致 Agent 重复调用同一个工具。

我的实现方案是用一个状态机来管理。每个任务类型定义一套状态转移规则,State Manager 只允许合法的状态转移。比如“数据收集”只能转移到“数据分析”或“失败”,“数据分析”只能转移到“报告生成”或“失败”。非法转移直接抛异常,方便早期发现问题。

状态持久化也很重要。Agent 执行过程中如果进程崩溃,状态不能丢。我的做法是每次状态变更都写一次数据库,同时保留最近三次的状态快照。恢复时从最新快照继续执行。

3.6 Guardrail & Safety:让 Agent 不闯祸的护栏

Guardrail 是 Harness 里最容易被忽视、但出事时最要命的子系统。我见过 Agent 把测试数据写到生产数据库、把内部文档发到外部邮箱、执行了删除操作。这些问题的根源都是缺少护栏。

我的护栏体系分三层:输入护栏、执行护栏、输出护栏。输入护栏检查用户请求是否包含敏感操作、是否超出 Agent 权限范围;执行护栏检查工具调用参数是否合法、是否触发频率限制;输出护栏检查模型生成的内容是否包含敏感信息、是否符合格式要求。

工具调用的参数校验是执行护栏的重点。比如“发送邮件”工具,收件人必须在白名单内,附件大小不能超过限制,邮件内容不能包含特定关键词。这些规则在工具注册时配置,执行前自动校验。

频率限制也很关键。有些 Agent 会陷入疯狂调用工具的状态,比如连续搜索几十次。我的配置是:单个工具每分钟最多调用 10 次,单个任务总共最多调用 50 次。超过阈值就暂停任务,等待人工确认。

3.7 Observability:出问题时能查清楚的底气

Observability 管的是“Agent 执行过程中发生了什么、哪里慢了、哪里错了”。没有它,出问题只能靠猜。

我通常记录四类数据:调用链追踪、Token 消耗、工具执行日志、状态变更历史。调用链追踪记录每次 LLM 调用和工具调用的父子关系、耗时、输入输出摘要;Token 消耗按任务和模型维度统计;工具执行日志记录每次调用的参数、结果、异常;状态变更历史记录每次状态转移的时间和原因。

这些数据不只是用来排查问题,还能用来优化。比如我发现某个任务平均要调用 8 次工具,其中 3 次是重复查询,于是加了缓存层,平均调用次数降到 5 次,任务耗时减少了 40%。

观测维度记录内容用途存储周期
调用链父子关系、耗时、状态性能分析、问题定位7天
Token输入输出token数、模型名成本核算、优化依据30天
工具日志参数、结果、异常调试、审计14天
状态历史转移前后状态、时间流程优化、异常检测30天

4. 从零搭建 Harness 的实操流程

4.1 环境准备与技术栈选型

搭建 Harness 不需要多复杂的技术栈,但选型要考虑扩展性和调试便利性。我的推荐组合是:Python + FastAPI + LangChain/LangGraph + Redis + PostgreSQL。

Python 是 Agent 开发的主流语言,生态最全。FastAPI 提供异步 Web 服务,适合处理并发请求。LangChain 和 LangGraph 提供了 Agent Loop 和工具调用的基础抽象,但我不建议完全依赖它们,核心的 State Manager 和 Guardrail 最好自己实现,因为框架的抽象有时候会限制你的控制力。Redis 用来做短期记忆和状态缓存,PostgreSQL 用来做持久化存储和日志。

如果你不想用 LangChain,完全可以自己写。Agent Loop 的核心逻辑不超过 200 行代码,自己写反而更可控。我现在的项目就是自己实现的 Loop,只用了 OpenAI 的 SDK 和几个基础库。

# 基础依赖安装 pip install fastapi uvicorn openai redis sqlalchemy asyncpg pip install langchain langgraph # 可选,用于快速原型

4.2 最小可用 Harness 的搭建步骤

先搭一个能跑的最小版本,再逐步加子系统。我的建议顺序是:

第一步,实现 LLM Integration。封装一个统一的模型调用接口,支持至少一个模型,包含重试和格式校验。

第二步,实现 Tool Registry。定义工具注册的数据结构,实现工具查找和执行,加上超时控制。

第三步,实现 Agent Loop。把前两步串起来,实现“调用模型 -> 解析输出 -> 执行工具 -> 更新上下文 -> 继续循环”的基本流程。

第四步,加入 State Manager。定义任务状态结构,实现状态转移和持久化。

第五步,加入 Memory & Context。实现分层记忆和上下文压缩。

第六步,加入 Guardrail。实现输入校验、工具参数校验、输出校验。

第七步,加入 Observability。实现日志记录和调用链追踪。

这个顺序的好处是:每一步都能跑通,不会出现“写了三千行还没法测试”的情况。我见过有人一上来就搞全套架构,结果调试了两周还没跑通一个完整任务。

4.3 关键参数的计算与配置

Harness 里有很多参数需要根据实际情况调整。我列几个最关键的:

最大迭代次数:根据任务复杂度定。简单任务 5 次,中等任务 10 次,复杂任务 15 次。计算公式是:预估工具调用次数 × 1.5 + 3。比如预估需要调用 6 次工具,最大迭代设为 12 次。

上下文窗口阈值:模型上下文上限的 70%。比如模型支持 128K token,阈值设为 90K。超过就触发压缩。

工具超时时间:根据工具类型定。只读查询 15 秒,计算处理 30 秒,有副作用操作 20 秒,外部集成 45 秒。这些值可以根据实际 P99 耗时调整。

重试次数:LLM 调用 3 次,工具调用 1-2 次,格式解析 3 次。重试间隔用指数退避,初始 1 秒,最大 8 秒。

并发限制:单个 Agent 实例同时处理的任务数建议不超过 10 个。超过就排队。这个值取决于你的模型 API 限流和工具执行能力。

4.4 一次完整任务执行的现场记录

我拿一个实际任务来演示:用户要求“分析最近一周的销售数据,找出下降最多的三个品类,生成报告”。

第 1 轮循环:LLM 收到用户请求和可用工具列表,返回“调用销售数据查询工具,参数为最近 7 天”。Tool Registry 执行查询,返回原始数据。State Manager 更新状态为“数据获取完成”。Memory 记录工具调用结果。

第 2 轮循环:LLM 收到数据摘要,返回“调用数据分析工具,计算各品类环比变化”。工具执行,返回下降最多的三个品类。State Manager 更新状态为“分析完成”。

第 3 轮循环:LLM 收到分析结果,返回“调用报告生成工具,生成 Markdown 格式报告”。工具执行,返回报告内容。State Manager 更新状态为“报告生成完成”。

第 4 轮循环:LLM 判断任务完成,返回最终回复。Agent Loop 终止,返回结果给用户。

整个过程耗时 18 秒,消耗 token 约 12000,工具调用 3 次。Observability 记录了每一步的详细日志,包括每次 LLM 调用的输入输出摘要和工具执行耗时。

5. 常见问题与排查技巧实录

5.1 Agent 陷入死循环怎么办

死循环是最常见的问题,表现是 Agent 反复调用同一个工具或反复生成相似内容。排查思路:

先看 Observability 日志,确认循环模式。如果是反复调用同一个工具,检查工具返回结果是否为空或格式错误,导致模型认为没拿到数据。如果是反复生成相似内容,检查上下文是否没有正确更新,导致模型每次都看到相同的输入。

我的解决方案是三层防护:第一,设置最大迭代次数,硬性截断;第二,检测重复动作,如果连续三次调用同一个工具且参数相同,强制终止;第三,在上下文中加入“你已经调用过该工具,结果是 XXX”的提示,让模型知道不要重复。

5.2 工具调用参数总是传错

这个问题通常是因为工具描述不够清晰。模型不知道参数的类型、格式、取值范围,就会瞎传。我的改进方法是:

在工具描述中明确参数类型和示例。比如不要写“查询销售数据”,而是写“查询指定日期范围内的销售数据。参数:start_date(字符串,格式 YYYY-MM-DD),end_date(字符串,格式 YYYY-MM-DD)。示例:start_date='2026-01-01', end_date='2026-01-07'”。

另外,在 Tool Registry 层做参数校验,发现格式错误时,把错误信息和正确示例一起返回给模型,让它重新生成。

5.3 上下文爆炸导致模型变傻

上下文太长会导致两个问题:token 成本飙升,模型注意力分散。我的排查方法是看 Observability 里的 token 消耗曲线,如果发现某轮循环 token 突然暴涨,说明上下文组装有问题。

解决方案是分层压缩。最近三轮全量保留,第四到第十轮做摘要,更早的只保留关键结论。工具返回的大块数据只保留摘要和引用 ID,需要时再通过 ID 查原始数据。

5.4 模型返回格式不符合预期

这个问题在需要结构化输出时特别常见。模型可能返回 Markdown 而不是 JSON,或者 JSON 里多了注释。我的处理流程是:

第一层,用正则提取可能的 JSON 块。第二层,用 JSON Schema 校验。第三层,校验失败时,把错误信息和格式要求一起返回给模型,让它重新生成。如果三次都失败,降级为纯文本解析,同时记录告警。

问题现象可能原因排查方法解决方案
死循环工具返回空、上下文未更新查看调用链日志最大迭代限制、重复检测
参数传错工具描述不清检查工具注册信息完善描述、参数校验
上下文爆炸未压缩、全量保留查看token消耗曲线分层压缩、摘要
格式错误模型输出不稳定查看原始返回多层校验、重试
任务超时工具卡住、循环过多查看各步骤耗时超时控制、降级

5.5 多个任务并发时状态串了

并发场景下,状态管理最容易出问题。我遇到过一次:两个任务同时执行,任务 A 的状态被任务 B 覆盖了。原因是状态存储用了全局变量,没有按任务 ID 隔离。

解决方案很简单但容易被忽视:所有状态必须按任务 ID 隔离存储。Redis 的 key 要包含任务 ID,数据库查询要带任务 ID 条件,内存缓存也要用任务 ID 做命名空间。另外,状态更新要用乐观锁或分布式锁,防止并发写冲突。

避坑技巧:在开发阶段就模拟并发场景,用两个任务同时跑,观察状态是否串扰。这个问题在单任务测试时完全发现不了,上线后才会暴露。

6. 从能跑到能扛的进阶优化

6.1 性能优化的三个切入点

Harness 的性能瓶颈通常在三处:LLM 调用延迟、工具执行延迟、状态读写延迟。优化策略各不相同。

LLM 调用延迟优化:使用流式输出,让用户更早看到部分结果;对相同或相似的请求做缓存;在等待模型返回时,预加载可能用到的工具描述和上下文。

工具执行延迟优化:对只读查询加缓存;对可以并行的工具调用并行执行;对耗时长的工具做异步化,先返回“执行中”,完成后通过回调更新状态。

状态读写延迟优化:状态缓存到 Redis,定期持久化到数据库;批量更新状态,减少数据库写入次数;对状态查询加索引。

6.2 成本控制的实操方法

Token 成本是 Agent 运营的主要开销。我的控制方法有四个:

第一,上下文压缩。前面讲过,不再赘述。第二,模型分级。简单任务用便宜的小模型,复杂任务用大模型。第三,缓存。对相同输入做缓存,避免重复调用。第四,工具结果摘要。工具返回的大块数据只保留摘要,需要时再查原始数据。

我实测下来,这四个方法组合使用,token 成本能降低 60% 以上。其中效果最明显的是上下文压缩和模型分级。

6.3 可观测性的最小落地清单

如果你刚开始搭 Harness,Observability 不用做太复杂,但以下几项必须有:

  • 每次 LLM 调用的输入输出摘要和 token 数
  • 每次工具调用的参数、结果、耗时
  • 每次状态变更的前后状态和时间
  • 每个任务的开始时间、结束时间、总耗时、总 token 消耗
  • 异常和错误的完整堆栈

这些数据用日志文件记录就行,不一定非要上专业的可观测性平台。关键是出问题时能查到。我见过太多团队出问题后只能靠猜,就是因为没有记录这些基础数据。

6.4 后续扩展的方向

Harness 搭好之后,可以往几个方向扩展:多 Agent 协作,让多个 Agent 分工完成复杂任务;人机协同,在关键节点引入人工确认;自适应优化,根据历史执行数据自动调整参数;跨任务学习,把成功任务的经验沉淀到长期记忆中。

我个人最看好的是人机协同方向。完全自主的 Agent 在生产环境中风险太高,在关键决策点引入人工确认,既能保证安全,又能积累反馈数据来优化 Agent。

这个内容后续还可以这样扩展:把 Harness 的每个子系统做成可插拔的模块,根据不同业务场景组合不同的实现。比如客服场景重点强化 Guardrail,数据分析场景重点强化 Tool Registry,创意生成场景重点强化 Memory。这样一套 Harness 框架就能适配多种业务需求。