Agent 文档功能 — 交接文档
日期:2026-08-09
分支:feat/agent-independence
Commits:b62a417 → 10688be(共 16 个 commit)
一、功能概述
Agent 文档功能让 agent 能够跨会话持久化信息,分为两种类型:
| 类型 |
用途 |
上下文行为 |
memory |
内部记忆(用户偏好、身份信息等) |
每次对话自动注入 system prompt |
document |
交付物(周报、需求文档、会议纪要等) |
不自动注入,需主动调用工具读取 |
核心能力
- Agent 工具:LLM 可通过
document 工具 create/read/update/delete/list 文档
- 记忆注入:
type=memory 的文档在每次对话时自动拼接到 system prompt,LLM 无需调工具即可使用
- 用户界面:右侧抽屉组件,在会话页面内查看/搜索/删除文档,不跳转页面
- REST API:5 个端点供前端直接操作文档
- 权限隔离:所有操作强制
WHERE userId = ?,用户级隔离
- 审批机制:delete 操作默认需用户审批
二、架构设计
┌─────────────────────────────────────────────────────────┐
│ 前端 │
│ ┌──────────────┐ ┌──────────────────────────────────┐ │
│ │ AgentToolbar │ │ AgentSidebar (desktop + mobile) │ │
│ │ 文档按钮 │ │ 文档按钮 │ │
│ └──────┬───────┘ └────────────┬─────────────────────┘ │
│ │ emit('showDocuments')│ │
│ └──────────┬────────────┘ │
│ ▼ │
│ ┌──────────────────────────────────────────────────┐ │
│ │ AgentChatArea → emit('showDocuments') │ │
│ └──────────────────────┬───────────────────────────┘ │
│ ▼ │
│ ┌──────────────────────────────────────────────────┐ │
│ │ chat/[agentSlug]/index.vue │ │
│ │ showDocumentDrawer = true │ │
│ │ ┌────────────────────────────────────────────┐ │ │
│ │ │ AgentDocumentDrawer.vue │ │ │
│ │ │ ┌──────────┐ ┌────────────────────────┐ │ │ │
│ │ │ │ 列表视图 │ │ 详情视图 (markdown) │ │ │ │
│ │ │ │ 筛选+搜索 │ │ 删除按钮 │ │ │ │
│ │ │ │ 删除按钮 │ │ AgentMarkdown 渲染 │ │ │ │
│ │ │ └──────────┘ └────────────────────────┘ │ │ │
│ │ └────────────────────────────────────────────┘ │ │
│ └──────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────┐
│ 后端 │
│ │
│ ┌─────────────┐ ┌──────────────┐ ┌───────────────┐ │
│ │ REST API │ │ chat-engine │ │ agent-tool │ │
│ │ 5 endpoints │ │ │ │ registry │ │
│ │ │ │ getMemory │ │ │ │
│ │ GET /api │ │ Context() │ │ document │ │
│ │ POST /api │ │ → systemPrompt│ │ executor │ │
│ │ GET /:id │ │ │ │ │ │
│ │ PATCH /:id │ └──────────────┘ └───────┬───────┘ │
│ │ DELETE /:id │ │ │
│ └──────┬──────┘ │ │
│ │ │ │
│ ▼ ▼ │
│ ┌──────────────────────────────────────────────────┐ │
│ │ agent-document service (CRUD + 权限隔离) │ │
│ │ createDocument / getDocumentById / listDocuments│ │
│ │ updateDocument / deleteDocument │ │
│ │ getMemoryContext (查 memory 类型,拼接上下文) │ │
│ └──────────────────────┬───────────────────────────┘ │
│ ▼ │
│ ┌──────────────────────────────────────────────────┐ │
│ │ SQLite: agent_documents 表 │ │
│ │ id / userId / sessionId / type / title / │ │
│ │ content / summary / tags / createdAt / updatedAt │ │
│ └──────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────┘
三、文件清单
数据层
| 文件 |
说明 |
packages/drizzle-pkg/lib/schema/agent-document.ts |
agentDocuments 表 schema,createdAt/updatedAt 用 mode: "timestamp_ms" |
packages/drizzle-pkg/lib/schema/agent-tool.ts |
AgentToolTypes 新增 "document" |
packages/drizzle-pkg/migrations/0002_noisy_ronan.sql |
建表迁移 |
packages/drizzle-pkg/seed.ts |
document 工具种子数据(id=tool_document) |
Service 层
| 文件 |
说明 |
server/service/agent-document/index.ts |
CRUD + getMemoryContext() + deserializeTags |
server/service/agent-document/index.test.ts |
32 个单元测试 |
Executor 层
| 文件 |
说明 |
server/service/agent-tool/executors/document/config.ts |
document executor config(审批白名单、maxContentLength 等) |
server/service/agent-tool/executors/document/document.ts |
executor 实现,5 种 action 分发,toISO() 序列化 Date |
server/service/agent-tool/registry.ts |
ToolContext 扩展 approved?: boolean |
server/service/agent-tool/index.ts |
document executor 注册(line 128-133) |
API 层
| 文件 |
说明 |
server/api/agent-documents/index.get.ts |
GET 列表(分页 + 类型筛选 + 关键词搜索) |
server/api/agent-documents/index.post.ts |
POST 创建 |
server/api/agent-documents/[id].get.ts |
GET 详情 |
server/api/agent-documents/[id].patch.ts |
PATCH 更新 |
server/api/agent-documents/[id].delete.ts |
DELETE 删除 |
前端
| 文件 |
说明 |
app/components/agent/AgentDocumentDrawer.vue |
右侧抽屉(列表 + 详情 + 删除 + 筛选 + 搜索) |
app/components/agent/AgentToolbar.vue |
工具栏文档按钮 → emit showDocuments |
app/components/agent/AgentSidebar.vue |
侧边栏文档按钮(desktop + mobile)→ emit showDocuments |
app/components/agent/AgentChatArea.vue |
透传 showDocuments 事件 |
app/pages/chat/[agentSlug]/index.vue |
接入 showDocumentDrawer 状态 + 渲染抽屉 |
app/pages/agent-documents/index.vue |
独立列表页(保留,导航入口在 TopNav) |
app/pages/agent-documents/[id].vue |
独立详情页(保留) |
Chat Engine
| 文件 |
说明 |
server/service/agent/chat-engine.ts |
记忆注入 system prompt(line 250-260)+ buildModelMessages 修复 |
测试
| 文件 |
说明 |
e2e/specs/agent-documents.spec.ts |
10 个 E2E 测试用例 |
文档
| 文件 |
说明 |
docs/superpowers/specs/2026-08-09-agent-document-tool-design.md |
设计文档 |
docs/superpowers/plans/2026-08-09-agent-document-tool.md |
实施计划 |
docs/handover/2026-08-09-agent-document-tool.md |
本交接文档 |
四、关键实现细节
4.1 记忆注入上下文
// chat-engine.ts line 250-260
let systemPrompt = baseSystemPrompt;
if (user) {
const memoryContext = await getMemoryContext(user.id);
if (memoryContext) {
const memoryInstruction =
"\n\n# 持久记忆(已注入上下文)\n" +
"以下 <memory> 标签内的信息是你的持久记忆,每次对话开始时自动加载,始终是最新的。\n" +
"**禁止调用 document 工具的 read 或 list 操作来读取这些记忆** — 它们已经在你的上下文中了。\n" +
"直接使用 <memory> 中的内容回答用户问题即可。\n" +
"只有需要创建新记忆或修改已有记忆时,才使用 document 工具。";
systemPrompt = systemPrompt
? `${systemPrompt}${memoryInstruction}\n${memoryContext}`
: `${memoryInstruction}\n${memoryContext}`;
}
}
- 实时查询:每次
executeChat 调用时从数据库查 type=memory 的文档,文档更新后下一轮对话自动生效
- 格式:每条记忆用
## {title}\n{content} 格式,整体包裹在 <memory> 标签内
- 防冗余调用:system prompt 中明确禁止 LLM 调用 document 工具读取已有记忆
4.2 getMemoryContext 实现
// agent-document/index.ts
export async function getMemoryContext(userId: number): Promise<string | null> {
const rows = await dbGlobal
.select({ title: agentDocuments.title, content: agentDocuments.content })
.from(agentDocuments)
.where(and(eq(agentDocuments.userId, userId), eq(agentDocuments.type, "memory")))
.orderBy(desc(agentDocuments.updatedAt));
if (rows.length === 0) return null;
const parts = rows.map((r) => `## ${r.title}\n${r.content}`);
return `<memory>\n${parts.join("\n\n")}\n</memory>`;
}
4.3 Date 序列化问题
drizzle timestamp_ms mode 返回 Date 对象,AI SDK Zod 校验拒绝 Date(期望 string)。executor 中用 toISO() helper 序列化:
function toISO(date: Date | null | undefined): string | null {
return date ? date.toISOString() : null;
}
4.4 buildModelMessages 审批修复
approval-responded + hasResult=false 时只 push tool-approval-request,不 push 无 output 的 tool-call(AI SDK 校验失败)。
4.5 抽屉组件
Teleport to="body" + Transition 实现滑入动画
- 列表视图:类型筛选 tabs(全部/文档/记忆)+ 搜索框(400ms 防抖)+ 卡片列表
- 详情视图:返回按钮 + 标题 +
AgentMarkdown 渲染 + 删除按钮
- 每次
show 变 true 都重新 fetch(不缓存)
- 删除带
confirm() 确认,删除后自动从列表移除
4.6 agent-tool 关联
工具必须通过 agent_tool_associations 表关联到 agent 才能被 LLM 使用,仅 enabled=1 不够。已为 default agent(id=1)手动插入关联记录。
五、数据库表结构
CREATE TABLE agent_documents (
id INTEGER PRIMARY KEY AUTOINCREMENT,
userId INTEGER NOT NULL,
sessionId TEXT,
type TEXT NOT NULL DEFAULT 'document', -- 'memory' | 'document'
title TEXT NOT NULL,
content TEXT NOT NULL,
summary TEXT,
tags TEXT, -- JSON array string
createdAt INTEGER NOT NULL, -- timestamp_ms
updatedAt INTEGER NOT NULL -- timestamp_ms
);
-- 索引
CREATE INDEX idx_agent_documents_userId ON agent_documents(userId);
CREATE INDEX idx_agent_documents_userId_type ON agent_documents(userId, type);
CREATE INDEX idx_agent_documents_userId_updatedAt ON agent_documents(userId, updatedAt);
六、API 接口
| 方法 |
路径 |
说明 |
| GET |
/api/agent-documents?page=1&pageSize=20&type=memory&keyword=xxx |
列表查询 |
| POST |
/api/agent-documents |
创建文档 |
| GET |
/api/agent-documents/:id |
获取详情 |
| PATCH |
/api/agent-documents/:id |
更新文档 |
| DELETE |
/api/agent-documents/:id |
删除文档 |
响应格式:{ code: 0, message: 'success', data: ... }
七、Agent 工具接口
LLM 可调用的 document 工具,通过 action 字段分发:
| action |
必填参数 |
可选参数 |
说明 |
| create |
title, content |
type, tags |
创建文档/记忆 |
| read |
id |
fullContent |
读取详情(默认仅返回 summary) |
| update |
id |
title, content, tags |
更新文档 |
| delete |
id |
— |
删除文档(默认需审批) |
| list |
— |
type, keyword, page, pageSize |
列表查询 |
八、已知问题与注意事项
- seed.ts 不是 upsert:admin 用户已存在时重跑 seed 会 UNIQUE 冲突;开发库需手动插入新工具记录
- guess-who 残留:
packages/drizzle-pkg/seed.ts line 215-225 有 guess-who 种子数据,但 executor 不存在
- 前端无 agent 编辑页:无法通过 UI 管理 agent-tool 关联,需手动插库
- typecheck 预先存在错误:search、chat、favorite、ideas、llm 模块有 TS 错误,与本次改动无关
- 记忆无 token 上限:当前
getMemoryContext 拼接所有 memory 文档,如果记忆数量多可能撑爆上下文。后续可加 token 预算截断
九、Commit 历史
| Commit |
说明 |
b62a417 |
feat: add agent_documents table schema, migration, and seed data |
2bd451a |
feat: add agent-document service layer with CRUD, permission isolation, and tests |
b40bfa4 |
feat: add document tool executor with config, registration, and approved flag |
e5d7e68 |
feat: add REST API endpoints for agent documents |
d06a642 |
feat: add agent-documents frontend pages (list + detail) and nav entry |
0c8d51a |
feat: add POST create endpoint and E2E tests for agent-documents |
6b381d6 |
docs: add agent-documents E2E test entry to AGENTS.md |
322accb |
fix: buildModelMessages approval-responded without result should not emit tool-call |
0ae318a |
feat(agent): add document entry in sidebar footer and toolbar |
50f0ca4 |
fix(agent-document): serialize Date fields to ISO strings in executor output |
c5e9ae9 |
fix(agent-document): deserialize tags in listDocuments + null guard in template |
72659af |
feat(agent-documents): replace page navigation with right-side drawer |
bf88935 |
feat(agent-documents): add delete in drawer + inject memory into system prompt |
c2bf347 |
fix(chat-engine): instruct LLM not to call document tool for existing memory |
10688be |
fix(chat-engine): strengthen memory instruction to prevent redundant tool calls |
十、后续可优化方向
- 记忆 token 预算:
getMemoryContext 加 token 上限,超限时按优先级截断
- 向量检索:文档量大时用向量检索替代 SQL 模糊搜索
- agent 编辑页:前端 UI 管理 agent-tool 关联,避免手动插库
- 记忆分类:memory 细分为 preference/identity/fact 等,按类别注入
- 文档分享:document 类型支持生成分享链接
- 版本历史:文档更新时保留历史版本