Skip to main content

设计理念

工作区(workspace)是 HarnessAgent 智能体定义与进化的 source of truth。智能体”是什么”以及”在运行中学到了什么”,全部以一个目录 + 普通 Markdown / JSON 文件来组织——不散落在代码里,不绑死在某个数据库表上。 它的设计有四条线索: 1. 智能体定义与进化的 source of truth。 智能体的定义(它是谁、怎么行事)可以完全在工作区中声明:
以上全部可选。 每个工作区文件都有完全对等的 API 配置方式:你可以通过 builder 方法(.sysPrompt(...)、.skill(SkillDeclaration...)、.subagent(SubagentDeclaration...)、.toolsConfig(...) 等)传入同等配置。工作区与 API 始终等价——用哪种完全取决于你。 那为什么还要用工作区? 因为把”定义”表达成文件(而不是代码),正是让一个 agent 天然多租户的关键:同一套 agent 逻辑,可以为不同用户携带不同的人格、知识库、技能集——只需放一个用户级覆盖目录,无需代码分支、无需多套部署。详见下文 同一套 agent 逻辑,按用户定制。
智能体的进化(跨会话累积学到的一切)由框架自动写入工作区,无需手动管理生命周期:
  • 长期记忆(MEMORY.md + memory/)—— 从对话中提取的事实,由后台任务维护与压缩,每次 call 加载为参考上下文。
  • 自学习技能(skills/)—— agent 从成功模式中起草新技能;经过可选的审批闸门后成为可复用能力,再由后台 curator 把长期未用的老化、归档。
  • 计划文件(plans/)—— Plan Mode 中写下的计划会持久化、跨调用保留,让”想清楚”与”做出来”解耦。
  • 工具结果落盘(compaction)—— 超大工具输出写到磁盘,上下文里只留 head/tail 预览 + read_file 指针,agent 之后可按需重读而不撑爆 prompt。
  • 会话日志 —— 原生 Session Log 保存完整执行事实和 checkpoint;agents/<agentId>/sessions/ 的 JSONL 保留为兼容消息视图。
这些进化数据默认长期有效:记忆无限累积,会话日志只追加不自动清除。每条通道如何产生与维护,详见下文 智能体如何进化。 AgentState 描述当前工作状态;默认 EVENT_LOG 模式将它保存为原生日志中的 checkpoint。旧 AgentStateStore 仍用于 legacy 和显式迁移,详见 会话日志与恢复。 2. 内容按生命周期分三类,互不混淆。 混在一棵树里只是为了部署方便(一个目录拷贝走就是完整 agent),框架内部走不同的读写路径。
区分文件产物和恢复状态。 计划、技能和记忆是 Workspace 文件;AgentState 是结构化运行状态。默认 EVENT_LOG 模式将状态 checkpoint 放进原生 Session Log,日志复用 Filesystem 的保留分区或自定义后端。LEGACY 才以独立 AgentStateStore 为恢复来源。
3. 原生多租户隔离。 工作区数据(记忆、会话、任务、技能、沙箱状态)由单一的 IsolationScope 分桶——无需应用层手写任何分区逻辑。Scope 决定谁和谁共享一个桶: 选定的 scope 在不同 filesystem 模式下落地方式不同(本机为路径前缀、共享存储为 KV 命名空间、沙箱为状态 slot)。完整语义、降级规则、并发说明见 filesystem — IsolationScope。
IsolationScope 决定 Filesystem 分桶。原生日志在桶内继续以 (userId, stableAgentId, sessionId) 隔离;旧 AgentStateStore 以 (userId, sessionId) 寻址。改变 scope 或日志后端前应迁移数据。
多用户应用推荐共享 Builder、每请求创建 Agent;用户和会话身份、隔离范围与访问权限由应用明确配置,见实例生命周期。 4. 工作区与 filesystem 解耦。 同一份目录布局可以落在三种地方:本机磁盘、共享 KV 存储(Redis / JDBC)、沙箱容器。这是 HarnessAgent 能”代码不动、部署形态切换”的根因。详细见 filesystem 的三种模式。

工作区目录布局

这棵树是逻辑布局,不是固定的磁盘路径。 它画成 .agentscope/workspace/...,但那只是默认的本机落点。同一份布局可以物理上落在本机磁盘、落在远端分布式存储(Redis / JDBC / OSS,通过 RemoteFilesystemSpec),或映射进沙箱容器(SandboxFilesystemSpec)——下面这些相对路径在三种模式下完全一致,变的只是后端存储,你的 agent 代码不用动。用 filesystem 选后端;本文其余内容都是按这套逻辑布局来写的。
唯一你真正需要写的是 AGENTS.md(不写也能跑,只是少一段人格注入)。其他目录在你启用对应能力时自动出现:
  • 启用记忆压缩(.compaction(...))→ memory/ + MEMORY.md
  • 放子 agent spec → subagents/
  • 装技能 → skills/
  • 启用 Plan Mode → plans/
  • 任何 call() 跑过一次 → agents/<agentId>/

Builder 配置

workspace 的解析顺序

不显式调用 workspace(...) 时,build() 按以下优先级(从高到低)确定工作区目录: 系统属性和环境变量主要服务于镜像打包 / 容器部署:应用代码里不写死路径,构建镜像或启动容器时通过环境变量灵活注入。例如:
空白值(如 " ")视为未设置,会继续往下一级回退。
AGENTS.md 至少写一份骨架:
可关掉的子系统(生产很少用,调试或自管时有用):

工作区内容如何被加载

因为工作区是逻辑布局(见上方提示框),“加载”从不假设它是一个普通本机目录——每次读取都经过配置的 AbstractFilesystem,所以无论文件落在本机磁盘、远端存储还是沙箱里,同一套逻辑都成立。下面的两层读正是把这种”与后端无关”落到实处的机制;各模式如何在物理上解析路径,见 filesystem。

工作区材料如何进入请求

工作区材料每次 Agent call 读取一次,再由最终 Context 构建器分层组织,不直接把全部文件追加进 System。 maxContextTokens 默认 8000,用于工作区材料准备,不代表最终模型输入上限。 最终预算还包括 System、历史、状态和工具 Schema;仍超限则拒绝请求。 MEMORY.md 在记忆工具和 Hooks 都关闭时不加载。 知识目录只加载入口和索引,其余文件由模型按需读取。 AGENTS.md 不需要 XML 标签。同一次 call 内文件变化不自动刷新; 下一次 call 重新加载。消息示例、动态业务来源及预算设置见 上下文管理。

两层读架构(filesystem-first + 本地兜底)

对所有”被注入到 prompt 的关键文件”(AGENTS.md / MEMORY.md / knowledge/KNOWLEDGE.md / additionalContextFile),WorkspaceManager.readWithOverride() 都走两层读:
写入永远走第 1 层(filesystem 后端),从不直接落到本地磁盘。 这套两层读的价值在共享存储模式最明显:第一个副本启动时本地磁盘上有团队 git 同步过来的 AGENTS.md 模板,立刻可用;之后任何节点对 AGENTS.md 的覆盖(例如管理台编辑)会写到共享 KV,所有副本下一次 call() 就读到最新版本。模板是 fallback,远端覆盖是事实。

多用户同一工作区时的覆盖优先级

RuntimeContext.userId 用于标识当前用户;共享 Builder 构建的各请求实例通过它选择对应的工作区数据。 对运行时数据(sessions / tasks / memory),框架按 NamespaceFactory 配的命名空间给路径加前缀(本机模式是路径前缀、远端模式是 KV 命名空间、沙箱模式是状态 slot)。详见下一节”运行时数据与 Memory 怎么存”。 对静态资产(特别是 skills/ 和 subagents/),用户级目录可以覆盖工作区共用版:
调用时只要 RuntimeContext.userId="alice",框架就会先看 alice/skills/code-reviewer/,没有再退到 skills/code-reviewer/。下层独有的 skill 仍然可见,只在重名时被上层挡住。完整优先级表参考 技能 — 同名冲突谁说了算。

同一套 agent 逻辑,按用户定制

同一份 Agent 定义可以服务多个租户:共享 Builder 构建请求实例后,工作区按用户身份加载对应配置。你只需维护一份二进制和公共定义,每个用户可以在此基础上定制自己的内容: 效果是两层多租户同时生效:定义按用户不同(靠覆盖目录),进化按用户隔离(靠命名空间)。共享底座对所有人通用,而每个用户的定制与学到的状态都不会跨租户泄漏——全部出自同一个 agent 进程。这正是前面 「以上全部可选」 强调的、把定义当数据带来的红利:因为定义就是数据,按用户定制只是多一个文件,而不是多一条代码分支。

三种 filesystem 模式下的加载行为

工作区只是”逻辑布局”,物理落点由 filesystem 选择。同一份目录在不同模式下被加载的方式不一样,这里分别举例。 模式 1 · 共享存储(RemoteFilesystemSpec)—— 模板 + 远端覆盖
  • 如何加载:每次推理时,AGENTS.md / MEMORY.md / tools.json 会被框架以”远端为上层、本机模板为下层”的 overlay 读出来。本地磁盘上的 <workspace>/AGENTS.md 是只读模板——是初始或多副本同步的种子;远端 KV 里如果存在该用户的同名 key,就以远端为准。
  • 路由规则:memory/ / skills/ / subagents/ / knowledge/ / agents/<id>/sessions/ / agents/<id>/tasks/ 都自动按 IsolationScope 进命名空间(默认 USER,每个 userId 一个命名空间;详见 filesystem — IsolationScope)。
  • 最佳实践:把团队约定的 AGENTS.md / knowledge/ / 共享 skills/ 用 git 同步到所有副本的本地磁盘作为模板;运行时产物(MEMORY.md、memory/、agents/<id>/...)让 KV 自己长。
模式 2 · 沙箱(DockerFilesystemSpec / K8s / E2B / AgentRun)—— Projection + Hydrate
  • 如何加载:沙箱启动时,框架把工作区里的”静态资产”(AGENTS.md / skills/ / subagents/ / knowledge/ 等 projection roots)打包成 tar,hydrate 进沙箱的 /workspace。AGENTS.md 等还是按两层读走(先沙箱内、后宿主模板)。
  • 去重 & 增量:projection 按内容 hash 比对,没变就跳过 hydrate;变了的文件按 SHA-256 增量重写。
  • 运行时产物:MEMORY.md、memory/、agents/<id>/... 都落在沙箱内,沙箱快照会一起保留——下一次 call() 用同 sessionId 时连同 node_modules、pip install 一并恢复。
  • 最佳实践:把代码执行 / shell 命令都隔离在沙箱里,宿主只保留工作区”种子”(团队 git 同步过来的人格 + 共享技能 + 知识库)。这也是生产部署不可信代码的首选模式。
模式 3 · 本机 + shell(默认 LocalFilesystemSpec 或不配 filesystem)—— 直读直写
  • 如何加载:所有文件直接从 <workspace>/ 下读,没有 overlay。<userId>/skills/ 这种用户覆盖按目录前缀切。
  • 路径安全:默认 ROOTED 模式,绝对路径只允许 workspace 和 project(shell cwd)这两个根之下;.. 路径会被路径策略拦掉。
  • 最佳实践:单进程 / 本机开发 / 单元测试 / 信任环境。生产不要直接用这个模式跑不可信代码——execute 是宿主 sh -c。

运行时数据与 Memory 怎么存

默认 EVENT_LOG 下,AgentState 的 checkpoint 与完整执行事实保存在原生 Session Log 中;文件产物和长期记忆仍各有生命周期。

Agent 状态与原生日志

每次调用沿用相同身份和后端即可自动恢复已提交 checkpoint 及后续可应用事实。共享 Workspace Filesystem 可以同时提供分布式日志存储;它需要真实原子 CAS 能力。Sandbox 或不具备该能力的后端应显式提供日志 store。完整配置和恢复检查见 会话日志与恢复。

兼容会话文件

Memory(长期记忆)

两层结构:
写入路径:
  • 对话压缩前,MemoryFlushMiddleware 把对话前缀里的新事实抽到 memory/YYYY-MM-DD.md(追加);
  • 后台节流任务定期把 memory/ 合并去重,重写 MEMORY.md;
  • MEMORY.md 每次 call 加载,并以受预算控制的方式进入参考消息。
读取路径:
  • 框架自动读 MEMORY.md(两层读,filesystem 优先);
  • agent 可主动调 memory_search / memory_get 找老内容(详见 记忆)。

命名空间隔离怎么落到路径上

WorkspaceManager.resolveRuntimeDataPath() 会问 NamespaceFactory 当前 RuntimeContext 应该走哪个命名空间。命名空间最终怎么变成”具体的存储位置”,按 filesystem 模式分: 不传 userId 时走单租户默认,所有人共享一个根。
静态资产 vs 运行时数据:AGENTS.md、tools.json、knowledge/ 这些静态资产不自动按 userId 切——它们对所有用户共享,只能由「用户覆盖目录」(<userId>/skills/...、<userId>/subagents/...)来差异化。会跟着 userId 走的是运行时数据(sessions、tasks、memory)。

智能体如何进化

除了静态定义,工作区还是智能体累积经验的落点。五条通道自动累积——打开对应能力,数据就开始在工作区里堆积,并和其它内容一样按租户隔离。每条都有自己的深入文档,这张表是索引: 记忆、技能、计划和卸载结果沿用 Workspace 的租户隔离与 Filesystem 路由。原生日志另需原子存储能力,并通过保留分区保护提交结构;兼容 JSONL 不能替代完整执行记录。

重点目录深入

skills/

技能是”写好的能力包”——一份目录里放 SKILL.md(说明 + 给 agent 看的指令),可以附带参考文档、脚本。
注册路径有四层(低 → 高优先级):
  1. projectGlobalSkillsDir(Path) —— 项目全局,例如 ~/.agentscope/skills/
  2. skillRepository(...) —— 市场后端(Git / Nacos / MySQL / classpath)
  3. workspace/skills/ —— 工作区共用
  4. <userId>/skills/ —— 用户隔离(覆盖以上所有)
下层独有的 skill 都保留,重名时上层覆盖。每次 call 前 HarnessSkillMiddleware 重新合成 → 在 system prompt 里渲染成 <available_skills> 块(只列 name + description),agent 看到觉得相关才会 load_skill_through_path 拉详情。完整工作机制见 技能。

subagents/

每个 <agent-id>.md 是一份子 agent 声明(文件名即 agent_id),YAML frontmatter 描述身份、模型、工具白名单、工作区策略等,正文是子 agent 的系统提示。
加载:AgentSpecLoader 在构建期非递归地扫 workspace/subagents/*.md,再合并你通过 .subagent(SubagentDeclaration...) 编程注册的声明。主 agent 通过 agent_spawn agent_id="reviewer" task="..." 调用。 完整细节(同步 / 后台、远程子 agent、流式转发、任务存储)见 子 Agent。

tools.json

工作区根目录下的 JSON,框架在 build() 时一次性读完:
行为细节:
  • MCP server 在构建期一次性注册到 toolkit,agent 看到的就是这些 server 暴露的工具。
  • allow / deny 在所有工具注册完之后才应用——所以也会过滤掉 Harness 的内置工具(read_file / memory_search / agent_spawn 等)。用 allow 列白名单时务必把要保留的内置工具一并列出,否则会一起被砍掉。
  • ${ENV_VAR} 语法做环境变量替换;未设置时给 warning 并替换成空字符串。
  • 不想用文件?直接 builder.toolsConfig(ToolsConfig.builder()...) 编程注入;要完全关掉读取用 disableToolsConfig()。
  • 在共享存储模式下,tools.json 走前面说的”远端为上层、本机模板为下层”的 overlay。

plans/

Plan Mode 写下的计划文件落在这里。默认 plans/PLAN.md,可用 .planFileDirectory("design-docs") 改路径。
PlanModeContext(阶段和计划路径)属于 AgentState,默认随原生日志 checkpoint 恢复;legacy 使用 AgentStateStore。plans/ 保存 Markdown 文件本身,文件内容仍需独立持久保存。详见 Plan Mode。

agents/<agentId>/

这是运行时根,框架自己写,你一般不动它:
上图仅展示任务记录。原生 Session Log 位于 Filesystem 保留分区:本地为 .agentscope-runtime/,远端为 __agentscope_session_log_v1__。通过 sessionTranscript()、session_history 和 session_list 读取历史;不再写旧 JSONL、sessions.json 或分段 transcript。
跨节点恢复时,原生日志、任务/文件产物、sandbox 元数据等都应使用适合的共享后端;仅配置共享 AgentStateStore 不会自动共享本地 Session Log。见 会话日志 与 Filesystem。

knowledge/

加载时:
  • KNOWLEDGE.md 全文进 <domain_knowledge_context>;
  • 同目录下其它文件(任意深度)只列路径清单进 prompt,让 agent 用 read_file / grep_files / glob_files 按需读取。
这种”目录里全是细节,prompt 里只放索引”的模式,避免把整个知识库塞进 token 预算。

写入工作区的安全规则

additionalContextFile、writeUtf8WorkspaceRelative、memory_get 这些接口接受工作区相对路径。框架做基本的 path-traversal 校验(拒绝 ../../etc/passwd 这种逃出工作区的写法)。 需要写文件时,通过 HarnessAgent#getWorkspaceManager() 而不是 java.nio.Files——后者在沙箱或共享存储模式下会写到错的地方(写到了宿主磁盘而不是沙箱里 / KV 里)。例外:builder 装配时的初始化脚本(例如 initWorkspaceIfAbsent 这种生成 AGENTS.md 种子),那时还没有运行时上下文,用 java.nio.Files 是 OK 的——它本来就是要落本地模板。

相关文档

  • 架构 — system prompt 是怎么拼出来的,能力之间如何协作
  • 文件系统 — 工作区在物理上落到哪里(本机 / 沙箱 / 共享存储)、IsolationScope、多用户隔离
  • Context — AgentState 与 AgentStateStore 的持久化、跨节点恢复
  • 记忆 — MEMORY.md / memory/ 的生成与维护、压缩、卸载
  • 技能 — 四层合成、自学习闭环、<available_skills> 块
  • 子 Agent — subagents/ 声明文件、同步/后台、流式转发
  • Plan Mode — plans/ 计划文件、只读阶段、HITL 退出