工作空间(Workspace)详解
一句话概括
工作空间是 Agent 的"办公桌"------人格、记忆、知识、技能都以文件形式存放在这里,每次"上班"自动加载,每次"下班"自动保存。
你能学到什么
- 工作空间的目录结构,每个文件/文件夹的作用
- 两层读写机制:为什么需要"优先读远端,兜底读本地"
- System Prompt 注入机制:工作空间内容如何变成 Agent 的"记忆"
- 如何配置自己的 Agent 工作空间
前置知识
详见 README.md。此外需了解 Hook 机制和 Agent 调用流程(参考 02-architecture.md)。
核心概念
工作空间 --- Agent 的"办公桌"
想象你是一名员工,每天上班需要:
- 坐在固定的办公桌前
- 桌上有你的工牌(告诉别人你是谁)
- 抽屉里有笔记本(记录重要的事情)
- 书架上有参考手册(查资料用)
- 旁边有工具箱(干活用的工具)
工作空间就是这个"办公桌"!它是 Agent 所有"随身物品"的存放地:
办公桌(workspace/) 对应 Agent 的什么?
├── AGENTS.md 工牌(我是谁、我该怎么做)
├── MEMORY.md 核心笔记(最重要的记忆)
├── knowledge/ 书架(领域知识、参考手册)
├── memory/ 日记本(每日流水账)
├── skills/ 工具箱(专业技能)
├── subagents/ 下属名单(可以叫谁帮忙)
└── agents/<agentId>/sessions/ 工作记录本(每次对话的详细记录)
AGENTS.md --- Agent 的"工牌"
生活类比:就像员工胸前挂的工牌,上面写着:
- 你是谁(名字、职位)
- 你的职责是什么(负责什么工作)
- 你该怎么做(工作准则、行为规范)
每次 Agent "上班"(被调用),第一件事就是读取这个工牌,知道自己是谁、该怎么表现。
技术实现:
markdown
# 我是客服助手小美
## 身份
我是一个友好的客服助手,专门帮助用户解决订单问题。
## 行为准则
1. 先确认用户身份
2. 礼貌回复,不急不躁
3. 遇到无法解决的问题,转接人工客服
## 能力范围
- 查询订单状态
- 处理退款申请
- 修改收货地址
MEMORY.md --- Agent 的"核心笔记"
生活类比:就像你办公桌上最重要的那本笔记本,里面记着你"必须记住"的事情:
- 老板的偏好
- 重要客户的习惯
- 上次项目遇到的坑
这本笔记会被"压缩"后注入到 Agent 的脑海中,但容量有限(默认 8000 tokens),超出会被截断。
技术实现:
markdown
# 长期记忆
## 用户偏好
- 张先生喜欢用邮件沟通
- 李女士每次下单都要加急
## 重要事件
- 2024-01-15:系统升级,暂停服务 2 小时
- 2024-01-20:新产品上线
knowledge/ --- Agent 的"书架"
生活类比:就像办公桌旁边的书架,放着各种参考书:
- 产品说明书
- 常见问题解答
- 操作手册
与 MEMORY.md 不同,书架上的书不会全部塞进脑子里,而是给 Agent 一个"目录",需要时再查阅。
技术实现:
knowledge/
├── KNOWLEDGE.md ← 书架目录(告诉 Agent 有哪些书)
├── faq.md ← 常见问题解答
├── product-guide.md ← 产品指南
└── troubleshooting.md ← 故障排查手册
memory/ --- Agent 的"日记本"
生活类比:就像你每天写的日记,记录当天发生的事情:
- 今天见了谁
- 处理了什么事
- 有什么感悟
这是"流水账",每天一个文件,由 Agent 运行时自动写入。
技术实现:
memory/
├── 2024-01-15.md ← 1月15日的日记
├── 2024-01-16.md ← 1月16日的日记
└── .consolidation_state ← 日记整理进度(内部状态)
sessions/ --- Agent 的"工作记录本"
生活类比:就像你的工作日志,详细记录每一次任务:
- 任务编号(sessionId)
- 任务摘要(summary)
- 完整的对话记录
技术实现:
agents/<agentId>/sessions/
├── sessions.json ← 任务索引(快速查找)
├── <sessionId>.jsonl ← 压缩后的对话上下文
└── <sessionId>.log.jsonl ← 完整对话日志
关键代码解读
目录结构详解
workspace/ ← 默认路径: .agentscope/workspace
│
├── AGENTS.md ← 人格定义(每次推理都注入)
│ - 定义 Agent 的身份、行为准则
│ - 缺失时 Agent 仍可工作,但会丢失"人格"
│
├── MEMORY.md ← 长期记忆(每次注入,有 token 限制)
│ - 整理过的核心记忆
│ - 超出预算会被截断,并提示使用 memory_search
│
├── knowledge/
│ ├── KNOWLEDGE.md ← 知识入口(告诉 Agent 有哪些资料)
│ └── * ← 其他参考文件(按需读取)
│
├── memory/
│ ├── YYYY-MM-DD.md ← 每日记忆流水账(运行时写入)
│ └── .consolidation_state ← 记忆整理器的内部状态
│
├── skills/<skill-name>/SKILL.md ← 自定义技能定义
│
├── subagents/<id>.md ← 子 Agent 声明(自动发现)
│
└── agents/<agentId>/ ← 每个 Agent 的私有空间
├── workspace/ ← 隔离子 Agent 的运行时根目录
└── sessions/
├── sessions.json ← 会话索引(id/summary/updatedAt)
├── <sessionId>.jsonl ← LLM 可见的压缩上下文
└── <sessionId>.log.jsonl ← 完整对话日志(用于审计)
两层读写机制
生活类比:想象你在公司有两个文件柜:
- 共享文件柜(AbstractFilesystem):云端的、多人共享的柜子
- 个人文件柜(本地磁盘):你桌边的小柜子
当你找文件时:
- 先去共享柜子找(因为可能有同事刚放进去的新文件)
- 共享柜子没有,再去个人柜子找(兜底)
当你存文件时:
-
默认存到共享柜子(其他人也能看到)
-
共享柜子出问题时,存到个人柜子(兜底)
读取流程:
┌─────────────┐ 读 ┌─────────────────────┐
│ Hook / Tool │ ─────────▶ │ WorkspaceManager │
└─────────────┘ └──────────┬──────────┘
│
┌──────────▼──────────┐
│ AbstractFilesystem │ ← 优先(云端/多租户)
│ (共享文件柜) │
└──────────┬──────────┘
│
没找到则 │
▼
┌─────────────────────┐
│ 本地磁盘 │ ← 兜底(本地文件)
│ (个人文件柜) │
└─────────────────────┘
代码解读:
java
// 创建工作空间管理器
// workspace: 工作空间根目录
// abstractFilesystem: 抽象文件系统(云端/多租户存储)
WorkspaceManager wm = new WorkspaceManager(workspace, abstractFilesystem);
// ========== 读取操作 ==========
// 读取 AGENTS.md(两层读:先云端,后本地)
wm.readAgentsMd();
// 读取 MEMORY.md(同上)
wm.readMemoryMd();
// 读取 knowledge/KNOWLEDGE.md
wm.readKnowledgeMd();
// 读取任意工作区文件(带路径穿越校验,防止恶意访问)
wm.readManagedWorkspaceFileUtf8("knowledge/faq.md");
// ========== 列表操作 ==========
// 列出知识库文件(两层并集,去重)
wm.listKnowledgeFiles();
// 列出记忆文件列表
wm.listMemoryFilePaths();
// 列出会话日志文件
wm.listSessionLogFiles();
// ========== 写入操作 ==========
// 追加内容到工作区文件(走 AbstractFilesystem)
wm.appendUtf8WorkspaceRelative("memory/2024-01-15.md", "新事件:用户咨询退款\n");
// 更新会话索引
wm.updateSessionIndex(agentId, sessionId, "会话摘要内容");
System Prompt 注入机制
生活类比:每次 Agent "上班"前,都要先"照镜子"------把办公桌上的重要物品信息"装进脑子"。
这个过程由 WorkspaceContextHook 在推理前自动完成:
推理前自动注入的内容:
┌─────────────────────────────────────────────────────────────┐
│ ## Session Context │
│ - 当前日期:2024-01-15 │
│ - 操作系统:Mac OS │
│ - 工作空间路径:.agentscope/workspace │
│ - 会话ID:session-12345 │
├─────────────────────────────────────────────────────────────┤
│ ## Workspace Guidance │
│ (内置模板:告诉 Agent 如何使用工作空间) │
├─────────────────────────────────────────────────────────────┤
│ <loaded_context> │
│ <agents_context> │
│ [AGENTS.md 全文内容] ← 没有长度限制 │
│ </agents_context> │
│ │
│ <memory_context> │
│ [MEMORY.md 内容] ← 受 token 预算限制(默认 8000) │
│ </memory_context> │
│ │
│ <domain_knowledge_context> │
│ [KNOWLEDGE.md 内容] │
│ 可用文件列表:faq.md, product-guide.md, ... │
│ </domain_knowledge_context> │
│ │
│ <SOUL.md> ← 额外配置的上下文文件 │
│ [文件内容] │
│ </SOUL.md> │
│ </loaded_context> │
└─────────────────────────────────────────────────────────────┘
Token 预算机制:
java
// 默认预算:8000 tokens
// 估算方式:字符数 / 4(粗略估算,中文可能不准确)
// 当 MEMORY.md 超出预算时:
// 1. 按字符截断
// 2. 追加提示:"... (memory truncated --- use memory_search for older entries) ..."
// 3. Agent 收到提示后,知道可以用 memory_search 工具查历史记忆
配置示例
java
HarnessAgent agent = HarnessAgent.builder()
.name("MyAgent") // Agent 名称
.model(model) // 使用的 LLM 模型
.workspace(Paths.get(".agentscope/workspace")) // 工作空间路径(不传则用默认)
.additionalContextFile("SOUL.md") // 额外注入的文件
.additionalContextFile("PREFERENCES.md") // 可以配置多个
.maxContextTokens(8000) // MEMORY.md 的 token 上限
.build();
配置说明:
| 配置项 | 作用 | 默认值 |
|---|---|---|
workspace |
工作空间根目录 | .agentscope/workspace |
additionalContextFile |
额外注入到 system prompt 的文件 | 无 |
maxContextTokens |
MEMORY.md 的最大 token 数 | 8000 |
整体流程图
┌─────────────────────────────────────┐
│ Agent 启动 │
│ HarnessAgent.build() │
└──────────────────┬──────────────────┘
│
▼
┌─────────────────────────────────────┐
│ 验证工作空间 │
│ WorkspaceManager.validate() │
│ 检查目录和 AGENTS.md 是否存在 │
│ (缺失只 warn,不报错) │
└──────────────────┬──────────────────┘
│
▼
┌─────────────────────────────────────┐
│ Agent 运行中 │
└──────────────────┬──────────────────┘
│
┌────────────┴────────────┐
│ │
▼ ▼
┌────────────────────────┐ ┌────────────────────────┐
│ PreReasoningEvent │ │ PostCallEvent 等 │
│ (推理前) │ │ (调用后) │
└───────────┬────────────┘ └───────────┬────────────┘
│ │
▼ ▼
┌────────────────────────┐ ┌────────────────────────┐
│ WorkspaceContextHook │ │ MemoryFlushHook │
│ (注入 system prompt) │ │ SessionPersistenceHook │
└───────────┬────────────┘ │ (写回记忆/会话) │
│ └───────────┬────────────┘
▼ │
┌────────────────────────┐ │
│ 读取并注入: │ │
│ - AGENTS.md │ │
│ - MEMORY.md │ │
│ - knowledge/ │ │
│ - 额外文件 │ │
└─────────────────────────┘ │
▼
┌────────────────────────┐
│ 写回到: │
│ - memory/YYYY-MM-DD.md │
│ - sessions/*.jsonl │
└────────────────────────┘
与其他模块的关系
⬅️ 上一篇:02-architecture | 📖 回到目录 | ➡️ 下一篇:04-session
学习要点
必须记住
- 工作空间 = Agent 的办公桌:所有 Agent 需要的"物品"都放在这里
- AGENTS.md 是人格:定义 Agent 是谁、该怎么做
- MEMORY.md 是核心记忆:有 token 限制,超出会被截断
- 两层读写机制:先读 AbstractFilesystem(云端),兜底读本地磁盘
容易混淆
-
MEMORY.md vs memory/:
MEMORY.md:整理过的核心记忆,每次都注入,有长度限制memory/:每日流水账,按需查询,不自动注入
-
knowledge/ vs MEMORY.md:
knowledge/:领域知识、参考手册,给 Agent 一个"目录"MEMORY.md:Agent 个人的记忆和经验
-
读取顺序:
- 不是"先本地后云端",而是**"先云端后本地"**
- 因为云端可能有最新的数据(多租户场景)
实践建议
- 最小可行配置 :至少创建一个
AGENTS.md,定义 Agent 的基本人格 - 合理使用 token 预算:MEMORY.md 不要写太多,重要内容放前面
- 善用 knowledge/:大文件放 knowledge/,让 Agent 按需读取
- 定期清理 memory/:旧日记可以归档,避免文件过多
调试技巧
java
// 查看工作空间路径
System.out.println(agent.getWorkspace());
// 查看注入的 system prompt
// (需要在日志中开启 DEBUG 级别)
// 可以看到 AGENTS.md、MEMORY.md 等是否正确注入
上一篇 :02-architecture.md --- 架构深潜
下一篇 :04-session.md --- 会话管理