【AgentScope】1. HarnessAgent 总览详解
2026/6/24 22:42:01 网站建设 项目流程

HarnessAgent 总览 详解

一句话概括

HarnessAgent 就是给"只有金鱼记忆的 AI 助手"装上一整套工程化装备——记忆系统、会话管理、上下文控制——让它能像一个靠谱的员工一样长期稳定工作。

你能学到什么

  • 理解"裸 Agent"和"工程化 Agent"的本质区别
  • 掌握 Hook 机制与工作区、双层记忆等核心概念
  • 能跑通 Quickstart 示例并理解每一行代码
  • 理解"三根支柱"设计思想:身份持续、上下文可控、状态可恢复
  • 建立全局认知,知道后续每个文档对应哪个能力模块

前置知识

详见 README.md 前置知识部分。本篇额外需要:

  • ReAct 模式(可选):了解 Agent 的"推理(Reason) -> 行动(Act) -> 观察(Observe)"循环即可

核心概念

裸 Agent vs 工程 Agent —— 就像实习生 vs 正式员工

想象你招了两个员工:

  • 实习生(裸 Agent / ReActAgent):你问他一个问题,他思考一下、查个资料、给你回答。但下班(对话结束)他就全忘了。第二天来上班,完全不知道昨天干了什么。
  • 正式员工(HarnessAgent):有笔记本记东西(记忆系统),有工牌知道自己的身份(工作区),有档案柜存历史记录(会话持久化),遇到大任务会拆分给同事(子 Agent),工作量太大知道怎么精简(对话压缩)。

裸的ReActAgent只有"请求 -> 推理 -> 工具 -> 回复"这一轮循环。它回答不了这些问题:

问题类比
下一轮怎么办?明天上班还记得昨天的事吗?
下一天怎么办?一个月后还记得客户名字吗?
上下文爆了怎么办?笔记本写满了怎么办?
状态丢了怎么办?电脑重启后工作还在吗?
任务太重怎么办?一个人干不完怎么办?

HarnessAgent 不替换推理循环本身,而是在循环的关键时机"插入"各种能力(通过 Hook 机制),把这些问题的默认工程答案打包好。

===

裸 Agent(ReActAgent)的工作流: 用户提问 → Agent 思考 → 调用工具 → 回复用户 ↑ ↓ └─── 一次结束,什么都不记得 ────────┘ HarnessAgent 的工作流: 用户提问 → [记忆注入] → Agent 思考 → 调用工具 → [记忆保存] → 回复用户 ↑ ↓ └── 自动恢复上次状态 ←── 会话持久化 ──────────┘

Hook 机制 —— 就像流水线上的检查站

生活类比:想象一条汽车生产流水线。汽车(Agent 的推理过程)经过不同的工位(Hook),每个工位负责一件事——有人装方向盘,有人喷漆,有人质检。每个工位有自己的优先级顺序,互不干扰,但都在同一条流水线上工作。

Hook 是在推理循环关键时机自动执行的模块。每个 Hook 只做一件事,通过priority(优先级数字)排好执行顺序,可以独立开关,互不影响。完整 Hook 列表和优先级见 02-architecture.md。


工作区(Workspace) —— 就像员工的办公桌

生活类比:想象一个正式员工的办公桌。桌上有工牌(AGENTS.md,定义身份)、笔记本(MEMORY.md,记录长期记忆)、参考书(KNOWLEDGE.md,领域知识)、技能手册(skills/,可复用能力)。每天上班时,员工会把桌上重要的东西看一遍,这就是"工作区上下文注入"。

工作区就是一个目录结构,所有关于 Agent 的信息都以 Markdown 文件的形式存放在这里:

workspace/ ← 默认路径: .agentscope/workspace ├── AGENTS.md ← 工牌:人格 / 行为约定 ├── MEMORY.md ← 笔记本:整理过的长期记忆 ├── knowledge/ │ └── KNOWLEDGE.md ← 参考书:领域知识 ├── memory/ │ └── YYYY-MM-DD.md ← 日记:每日记忆流水账 ├── skills/<skill-name>/SKILL.md ← 技能手册:自定义技能 └── subagents/<id>.md ← 下属名单:子 Agent 声明

每次 Agent 开始推理前,WorkspaceContextHook会自动读取这些文件,把它们"喂"给模型。这样 Agent 就知道自己是谁、知道什么、能做什么。


双层记忆 —— 就像"日记本 + 整理后的笔记"

生活类比:你每天写日记(第一层),把当天发生的事流水账式地记下来。周末你花时间把日记整理成要点笔记(第二层),扔掉重复的、合并相似的。日常工作用日记查细节,用要点笔记快速回忆大局。

HarnessAgent 的记忆也分两层:

  • 第一层:日流水账(memory/YYYY-MM-DD.md——每次对话结束后,LLM 自动从对话中提炼关键事实,追加到当天的流水账文件里。高频写入,质量一般。
  • 第二层:长期记忆(MEMORY.md——后台有个"整理员"(MemoryConsolidator),定期把流水账合并、去重、重写成高质量的长期记忆。低频更新,质量高。
对话内容 → LLM 提炼事实 → 追加到当日流水账 → 后台整理 → 更新 MEMORY.md (6小时一轮)

对话压缩 —— 就像"给塞满的抽屉瘦身"

生活类比:你的抽屉里塞满了收据(对话历史)。有一天抽屉关不上了(上下文溢出)。你需要把旧的收据整理成一张摘要表,只保留最近几张重要的,这样抽屉就又有空间了。

当对话消息积累到一定数量(默认 30 条),CompactionHook会自动触发压缩:

  1. 先把对话中的关键事实提取到日流水账(不丢失信息)
  2. 用 LLM 把旧对话生成一份摘要
  3. 用摘要替换掉旧消息,只保留最近几条

极端情况下,如果模型真的报了"context overflow"错误,HarnessAgent会捕获这个错误,强制压缩一次,然后自动重试——这就是"溢出恢复"。


会话持久化 —— 就像"员工下班打卡,明天打卡上班接着干"

生活类比:你每天下班时,把没做完的工作整理成一份交接单(状态快照)。第二天上班时,读一下交接单,就能接着昨天的进度继续。如果公司搬家了(进程重启),只要交接单还在,工作就不会断。

在 HarnessAgent 里,每次call()结束时:

  1. SessionPersistenceHook把 Agent 的当前状态(Memory、上下文等)保存到文件
  2. 下次用同一个sessionId调用时,bindRuntimeContext自动从文件恢复状态

关键是:只要sessionId不变,即使你重启了 Java 进程,Agent 也能记得之前聊过什么。


大工具结果卸载 —— 就像"快递太大放不进信箱,先放驿站"

生活类比:你收到一个巨大的快递包裹(工具返回的超大结果),但你的信箱(上下文窗口)装不下。快递员把包裹放在驿站(文件系统),在你信箱里留一张取件通知(占位符 + 预览)。你需要的时候再去驿站取。

ToolResultEvictionHook在工具返回结果超过 80K 字符时自动触发:把完整结果保存到文件,上下文里只留一个简短的占位符。Agent 需要详细信息时,可以用read_file工具按需读取。


子 Agent 编排 —— 就像"经理分配任务给下属"

生活类比:你是部门经理(主 Agent),手下有几个专员(子 Agent),各有所长。遇到复杂任务时,你把子任务分配给合适的专员去做。你可以等专员做完汇报(同步),也可以说"你先做着,我稍后来问"(后台异步)。

SubagentsHook为主 Agent 注册tasktask_output两个工具。主 Agent 可以:

  • 同步委派:等子 Agent 做完,直接拿到结果
  • 后台委派:让子 Agent 在后台执行,后续通过task_output查询进度和结果

RuntimeContext —— 就像"当次通话的来电显示"

生活类比:每次客户打电话来,客服系统会显示来电号码(sessionId)和客户姓名(userId)。客服看到这些信息就知道该恢复哪份档案、以什么身份和客户沟通。电话挂了,这个"来电显示"信息就消失了——它不会被永久记录。

RuntimeContext就是每次call()的身份载体:

  • sessionId:决定状态存放在哪里,日志归档到哪里
  • userId:决定文件系统的命名空间(天然的多租户隔离)
  • extra:可以携带额外的自定义数据

不会被持久化,只在当次调用的 Hook 和工具之间共享。


关键代码解读

下面逐行解读 Quickstart 示例代码。这是你接触 HarnessAgent 的第一个程序,每个部分都很重要。

1. 引入依赖

<dependency><groupId>io.agentscope</groupId><artifactId>agentscope-harness</artifactId><!-- 使用项目定义的版本变量 --><version>${agentscope.version}</version></dependency>

这一步只是告诉 Maven:“我要用 agentscope-harness 这个库”。类似于你在手机上安装一个 App。

2. 准备工作区

// 1. 准备工作区:第一次运行生成 AGENTS.md,后续运行复用Pathworkspace=Paths.get(".agentscope/workspace");initWorkspaceIfAbsent(workspace);
  • Paths.get(".agentscope/workspace"):指定工作区的路径。相对于当前运行目录。
  • initWorkspaceIfAbsent(workspace):如果工作区目录不存在就创建,如果AGENTS.md不存在就写入默认内容。
privatestaticvoidinitWorkspaceIfAbsent(Pathworkspace)throwsException{// 创建工作区目录(如果已存在则不报错)Files.createDirectories(workspace);// 定位 AGENTS.md 文件PathagentsMd=workspace.resolve("AGENTS.md");// 如果文件已存在,直接返回——不覆盖用户的自定义内容if(Files.exists(agentsMd))return;// 首次运行:写入默认的人格定义Files.writeString(agentsMd,""" # 笔记助手 你是一个帮助用户整理笔记和知识的助手。 ## 行为约定 - 主动记录用户提到的关键事实(姓名、计划、偏好等) - 回答用简洁中文,必要时给出要点列表 - 对不确定的内容要主动说明,不要臆造 """);}

这段代码的要点:只在首次运行时创建。之后你再运行,它会检测到AGENTS.md已经存在,直接跳过。这意味着你可以手动修改AGENTS.md来定制 Agent 的人格,不用担心被覆盖。

3. 构建模型

// 2. 构建模型Modelmodel=DashScopeChatModel.builder()// 从环境变量读取 API Key——永远不要把密钥硬编码在代码里.apiKey(System.getenv("DASHSCOPE_API_KEY"))// 指定使用哪个模型.modelName("qwen-max")// 启用流式输出(边生成边返回,不用等全部生成完).stream(true).build();
  • DashScopeChatModel:阿里云的通义千问模型客户端。你也可以换成其他模型。
  • System.getenv("DASHSCOPE_API_KEY"):从环境变量读取密钥,安全做法。
  • stream(true):流式模式,模型一边思考一边返回结果,用户体验更好。

4. 构建 HarnessAgent(核心!)

// 3. 构建 HarnessAgentHarnessAgentagent=HarnessAgent.builder()// 给 Agent 起个名字,方便在日志里识别.name("quickstart-agent")// 系统提示词:告诉 Agent 它的基本职责.sysPrompt("你是一个帮助用户做笔记的助手。")// 绑定上面创建的模型.model(model)// 指定工作区路径——Agent 的"办公桌"在哪里.workspace(workspace)// 配置对话压缩:何时压缩、保留多少、压缩前先提取事实.compaction(CompactionConfig.builder()// 消息达到 30 条时触发压缩.triggerMessages(30)// 压缩后保留最近 10 条消息.keepMessages(10)// 压缩前先把事实提取到日流水账,避免信息丢失.flushBeforeCompact(true).build()).build();

这段代码体现了 HarnessAgent 的"组装"思想:你通过 Builder 模式,把各个能力"装配"到 Agent 上。不配置compaction就没有压缩能力;配置了就自动拥有。

5. 创建 RuntimeContext

// 4. 同一个 RuntimeContext 发起两轮对话// sessionId 相同 → 第二轮自动从 Session 恢复第一轮的状态RuntimeContextctx=RuntimeContext.builder()// 会话 ID:决定了状态存储位置。同一个 ID = 同一个会话.sessionId("demo-session")// 用户 ID:决定了文件命名空间,天然隔离不同用户的数据.userId("alice").build();

RuntimeContext是"当次调用的身份证"。sessionId是最关键的字段——它决定了 Agent 从哪里恢复状态、把状态保存到哪里。

6. 第一轮对话

// 第一轮:告诉 Agent 一些信息Msgturn1=agent.call(// 构建用户消息Msg.builder().role(MsgRole.USER).textContent("我叫天宇,今天准备一个关于 ReAct 的技术分享。").build(),// 传入 RuntimeContextctx).block();// .block() 同步等待结果;流式模式见 10-streaming.md// 打印 Agent 的回复System.out.println("[turn1] "+turn1.getTextContent());
  • Msg.builder().role(MsgRole.USER).textContent(...):构建一条用户角色的消息。
  • agent.call(msg, ctx):发起调用,返回一个响应式对象。
  • .block():同步阻塞,等待 Agent 完成推理后返回结果。

7. 第二轮对话(验证记忆恢复)

// 第二轮:问 Agent 之前告诉它的信息Msgturn2=agent.call(Msg.builder().role(MsgRole.USER).textContent("我叫什么?我今天要干什么?").build(),// 注意:用的是同一个 ctx,sessionId 相同ctx).block();System.out.println("[turn2] "+turn2.getTextContent());

关键是:同一个sessionId。因为用了同一个ctx,第二轮call()会在开头自动从 Session 恢复第一轮的对话状态,所以 Agent 能回答"你叫天宇,今天要准备 ReAct 的技术分享"。


整体流程图

一次agent.call(msg, ctx)只需理解三个阶段:

  1. 绑定身份bindRuntimeContext(ctx):把 sessionId、userId 分发给所有 Hook,如果是已有会话则从文件恢复状态
  2. ReAct 循环— 委托给内部 ReActAgent 的推理循环,各 Hook 在推理前/后/工具执行时自动介入
  3. 收尾持久化— 提取对话事实到记忆、保存会话状态到文件,返回最终回复

如果 LLM 报 ContextOverflow 错误,HarnessAgent 会强制压缩一次并自动重试。

完整时序(含每个 Hook 的触发时机和详细步骤)见 02-architecture.md。


三根支柱:能力如何协同工作

HarnessAgent 的七大核心能力不是孤立的,它们分别支撑着"持续稳定运行"的三根支柱:

支柱组成效果
身份持续工作区上下文注入 + 双层持久记忆 + 技能自动加载每轮推理前,Agent 的人格和知识被重新"喂"给模型;对话事实沉淀回工作区。人格和知识不断累积,不随单次调用消失
上下文可控对话压缩 + 工具结果卸载 + 溢出恢复对话太长自动摘要,工具结果太大卸载到文件,溢出时强制压缩并重试。任意长度会话都不会压垮模型
状态可恢复会话持久化 + RuntimeContext + 可插拔文件系统每次调用结束保存状态到文件,下次调用自动恢复。进程重启、机器宕机,Agent 都能从断点继续

这三根支柱靠三个共享对象串起来:

共享对象类比职责
WorkspaceManager办公桌管理员谁来读写工作区的文件
AbstractFilesystem文件柜/网盘工作区的文件实际存在哪里
RuntimeContext来电显示当次调用是谁在说话

每个 Hook 只做自己的事,通过这三个对象和其他 Hook 协作——这就是 HarnessAgent 把一组独立能力合成一个"持续稳定 Agent"的方式。


与其他模块的关系

本文是总览,建立了全局认知。以下是后续各文档与本文概念的对应关系:

下一篇文档对应的核心能力关键问题
02-architecture.md全部Hook 怎么驱动一切?一次 call() 经历了什么?
03-workspace.md工作区上下文注入AGENTS.md / MEMORY.md 怎么注入到 prompt?
04-session.md会话持久化状态怎么跨进程保留?
05-memory.md双层持久记忆 + 对话压缩日流水账和 MEMORY.md 怎么协作?
06-filesystem.md可插拔文件系统文件存在本地还是远端还是沙箱?
07-tool.md内置工具Agent 自带哪些工具?
08-skill.md技能加载怎么给 Agent 装"插件"?
09-subagent.md子 Agent 编排主 Agent 怎么分配任务给子 Agent?
10-streaming.md流式输出怎么实时看到 Agent 的"思考过程"?
11-sandbox.md沙箱怎么隔离执行环境?

建议阅读顺序:按编号顺序读,每篇约 15-25 分钟,总计约 3.5 小时。


运行验证

如果你想亲手跑一下 Quickstart 示例,可以按以下步骤操作:

# 1. 设置 API Key(替换成你自己的)exportDASHSCOPE_API_KEY=your_key_here# 2. 首次运行需要安装依赖到本地 Maven 仓库mvn-plagentscope-examples/agents/harness-examples/harness-quickstart-aminstall\-DskipTests-Dspotless.check.skip=true-Dmaven.javadoc.skip=true-q# 3. 执行示例程序mvn-plagentscope-examples/agents/harness-examples/harness-quickstart exec:java\-Dexec.mainClass=io.agentscope.harness.example.QuickstartExample\-Dspotless.check.skip=true-q

运行后你应该观察到:

  1. .agentscope/workspace/AGENTS.md被自动创建
  2. 第二轮提问"我叫什么"能正确回答"天宇"
  3. 多聊几轮后(消息数 >= 30),workspace/memory/目录下会出现日流水账文件
  4. 重启进程、用同一个sessionId再调用,Agent 依然记得之前的内容

学习要点

  • 能说清楚"裸 Agent"和"HarnessAgent"的区别——前者只有一轮循环,后者有七大工程化能力
  • 理解 Hook 机制:每个 Hook 只做一件事,通过 priority 排序,可以独立开关
  • 理解工作区 + 双层记忆:Agent 的"办公桌"与"日记本 + 笔记"的记忆模式
  • 能跑通 Quickstart 示例,理解每一行代码的作用
  • 理解三根支柱设计思想:身份持续、上下文可控、状态可恢复

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询