Add generic memory gateway v1

This commit is contained in:
2026-05-05 16:18:31 +08:00
parent ba84b1ddb3
commit e65731a273
54 changed files with 4082 additions and 49 deletions

View File

@ -0,0 +1,767 @@
# 通用 Memory Gateway 方案与 POC 骨架
本文基于当前仓库的轻量 FastAPI + MCP + OpenViking + Obsidian 能力扩展不把系统设计成重平台。第一阶段目标是先跑通多用户隔离、namespace routing、记忆检索、写入、session commit 和人工 review 草稿,后续再替换持久化、向量索引和 EverMemOS worker。
## A. 总体架构图
```mermaid
flowchart TB
subgraph Agents["Agent Frameworks"]
Nanobot[Nanobot]
Hermes[Hermes Agent]
OpenClaw[OpenClaw]
Other[Other Agents]
end
subgraph Gateway["Memory Gateway"]
HTTP[HTTP API /v1]
MCP[MCP tools]
Auth[Auth / API Key / Future Login]
ACL[ACL & Visibility Policy]
Router[Namespace Router]
Audit[Audit Log]
Retrieval[Retrieval Orchestrator]
Writeback[Writeback Orchestrator]
end
subgraph Skills["Skills Layer"]
Ingest[ingest]
Extract[extract]
Classify[classify]
Retrieve[retrieve]
Commit[commit]
Merge[merge]
Prune[prune]
Summarize[summarize]
end
subgraph OpenViking["OpenViking"]
OVFS[context filesystem]
OVMem[memory]
OVRes[resources]
OVSkills[skills]
OVWorkspace[workspace]
end
subgraph EverMemOS["EverMemOS"]
LTE[long-term extraction]
Consolidation[consolidation]
Decay[decay]
Dedup[dedup]
Profile[profile evolution]
end
subgraph Obsidian["Obsidian"]
Vault[human editable memory vault]
Reviews[review queue]
Profiles[profiles]
LongTerm[long-term notes]
end
subgraph Storage["Storage"]
DB[(metadata DB)]
Vector[(vector index)]
Files[(object / file storage)]
end
Nanobot --> HTTP
Hermes --> MCP
OpenClaw --> HTTP
Other --> HTTP
Other --> MCP
HTTP --> Auth --> ACL --> Router
MCP --> Auth
Router --> Retrieval
Router --> Writeback
ACL --> Audit
Retrieval --> Skills
Writeback --> Skills
Skills --> OpenViking
Skills --> EverMemOS
Skills --> Obsidian
Gateway --> DB
Gateway --> Vector
Gateway --> Files
OpenViking --> DB
OpenViking --> Vector
Obsidian --> Files
EverMemOS --> DB
EverMemOS --> Vector
```
## B. 核心数据模型
代码骨架见 `memory_gateway/schemas.py`。核心模型如下。
### User
```json
{
"id": "user_tom",
"display_name": "Tom",
"status": "active",
"profile_namespace": "user/user_tom/profile",
"preferences": {"language": "zh-CN"},
"created_at": "2026-04-30T10:00:00Z",
"updated_at": "2026-04-30T10:00:00Z"
}
```
### Agent
```json
{
"id": "agent_hermes_default",
"name": "Hermes Default Agent",
"framework": "hermes",
"owner_user_id": "user_tom",
"created_at": "2026-04-30T10:00:00Z"
}
```
### Workspace
```json
{
"id": "ws_memory_gateway",
"name": "Memory Gateway POC",
"owner_user_id": "user_tom",
"member_user_ids": ["user_tom"],
"allowed_agent_ids": ["agent_hermes_default"]
}
```
### Session
```json
{
"id": "sess_20260430_001",
"user_id": "user_tom",
"agent_id": "agent_hermes_default",
"workspace_id": "ws_memory_gateway",
"status": "open",
"expires_at": "2026-05-07T10:00:00Z"
}
```
### MemoryRecord
```json
{
"id": "mem_abc123",
"user_id": "user_tom",
"agent_id": "agent_hermes_default",
"workspace_id": "ws_memory_gateway",
"session_id": "sess_20260430_001",
"namespace": "user/user_tom/long_term",
"memory_type": "preference",
"content": "用户偏好中文输出,结构化但不要过度平台化。",
"summary": "中文、结构化、轻量 POC 优先。",
"tags": ["preference", "style"],
"importance": 0.8,
"confidence": 0.9,
"visibility": "private",
"source": "conversation",
"created_at": "2026-04-30T10:00:00Z",
"updated_at": "2026-04-30T10:00:00Z",
"expires_at": null,
"version": 1
}
```
### EpisodeRecord
短期过程记录,默认不进入 Obsidian不自动成为长期记忆。
```json
{
"id": "epi_abc123",
"user_id": "user_tom",
"agent_id": "agent_hermes_default",
"workspace_id": "ws_memory_gateway",
"session_id": "sess_20260430_001",
"namespace": "session/sess_20260430_001/episodic",
"content": "本轮讨论了 Memory Gateway POC 范围。",
"summary": "确认 POC 优先做隔离、检索、写入和整理。",
"events": [],
"tags": ["design"]
}
```
### ProfileRecord
```json
{
"id": "profile_user_tom",
"user_id": "user_tom",
"namespace": "user/user_tom/profile",
"display_name": "Tom",
"stable_facts": ["正在设计通用 Memory Gateway"],
"preferences": {"language": "Chinese"},
"working_style": ["偏好可落地 POC"],
"updated_from_memory_ids": ["mem_abc123"],
"version": 3
}
```
### ACL / Visibility
`visibility` 四档:
- `private`:仅 `user_id` 相同可读写。
- `agent-only`:同一 `user_id` 且同一 `agent_id` 可读写。
- `workspace-shared`:在同一 `workspace_id` 且通过 workspace membership 授权后可读。
- `global`:可公开检索,只能由受信任 actor 写入。
### AuditLog
```json
{
"id": "audit_abc123",
"actor_user_id": "user_tom",
"actor_agent_id": "agent_hermes_default",
"action": "memory_search",
"target_type": "memory",
"target_id": "mem_abc123",
"namespace": "user/user_tom/long_term",
"decision": "allow",
"reason": "private owner",
"created_at": "2026-04-30T10:00:00Z"
}
```
## C. Namespace 与隔离设计
推荐 namespace
```text
user/{user_id}/profile
user/{user_id}/preferences
user/{user_id}/long_term
agent/{agent_id}/memory
workspace/{workspace_id}/shared
session/{session_id}/episodic
global/public
```
隔离规则:
- 用户隔离:所有 `user/{user_id}/...` 默认只允许同一 `user_id` 访问。Gateway 先校验 actor再把 namespace 映射到 OpenViking URI。
- Agent 隔离:`agent/{agent_id}/memory` 用于某个 agent 的工具经验、失败教训、prompt working notes。默认 `agent-only`
- Workspace 共享:`workspace/{workspace_id}/shared` 必须检查用户是否属于 workspaceagent 是否在 `allowed_agent_ids` 内。
- Session 过期:`session/{session_id}/episodic` 必须有 TTL。过期后不可检索只保留必要 audit。
- 可跨 agent 共享:用户显式确认的 profile、preferences、user long_term、workspace shared、global public。
- 不可跨 agent 共享agent-only memory、未 commit 的 session episodic、低置信度候选记忆、含敏感凭据或临时日志的内容。
OpenViking URI 映射:
```text
viking://memory/user/{user_id}/long_term/{memory_id}.json
viking://resources/workspace/{workspace_id}/shared/{slug}.md
viking://skills/memory-gateway/{skill_name}
```
## D. API 设计
第一阶段代码已挂载 `/v1` router`memory_gateway/api_v1.py`
### POST /v1/users
Request:
```json
{"user_id": "user_tom", "display_name": "Tom", "preferences": {"language": "zh-CN"}}
```
Response:
```json
{"id": "user_tom", "display_name": "Tom", "profile_namespace": "user/user_tom/profile", "status": "active"}
```
### GET /v1/users/{user_id}
Response:
```json
{"id": "user_tom", "display_name": "Tom", "status": "active"}
```
### POST /v1/memory/search
Request:
```json
{
"user_id": "user_tom",
"agent_id": "agent_hermes_default",
"workspace_id": "ws_memory_gateway",
"query": "中文输出偏好",
"namespaces": ["user/user_tom/long_term"],
"limit": 5
}
```
Response:
```json
{
"results": [
{
"memory": {
"id": "mem_abc123",
"namespace": "user/user_tom/long_term",
"summary": "中文、结构化、轻量 POC 优先。"
},
"score": 2.7
}
],
"total": 1
}
```
### POST /v1/memory
Request:
```json
{
"user_id": "user_tom",
"agent_id": "agent_hermes_default",
"workspace_id": "ws_memory_gateway",
"memory_type": "preference",
"content": "用户偏好中文输出。",
"summary": "中文输出偏好",
"tags": ["preference"],
"importance": 0.8,
"confidence": 0.9,
"visibility": "private",
"source": "manual"
}
```
Response:
```json
{"id": "mem_abc123", "namespace": "user/user_tom/long_term", "version": 1}
```
### GET /v1/memory/{memory_id}
Request query:
```text
?user_id=user_tom&agent_id=agent_hermes_default&workspace_id=ws_memory_gateway
```
Response:
```json
{"id": "mem_abc123", "content": "用户偏好中文输出。", "visibility": "private"}
```
### PATCH /v1/memory/{memory_id}
Request:
```json
{"summary": "用户偏好中文、结构化、少废话。", "importance": 0.9}
```
Response:
```json
{"id": "mem_abc123", "version": 2, "importance": 0.9}
```
### DELETE /v1/memory/{memory_id}
Response:
```json
{"deleted": true, "id": "mem_abc123"}
```
### POST /v1/episodes
Request:
```json
{
"user_id": "user_tom",
"agent_id": "agent_hermes_default",
"workspace_id": "ws_memory_gateway",
"session_id": "sess_001",
"content": "本轮完成了 namespace 和 ACL 设计。",
"tags": ["design"]
}
```
Response:
```json
{"id": "epi_abc123", "namespace": "session/sess_001/episodic"}
```
### POST /v1/sessions/{session_id}/commit
Request:
```json
{
"user_id": "user_tom",
"agent_id": "agent_hermes_default",
"workspace_id": "ws_memory_gateway",
"promote": true,
"min_importance": 0.6,
"target_namespace": "user/user_tom/long_term"
}
```
Response:
```json
{"session_id": "sess_001", "episodes": 3, "promoted": [{"id": "mem_def456"}]}
```
### GET /v1/users/{user_id}/profile
Response:
```json
{"user_id": "user_tom", "namespace": "user/user_tom/profile", "preferences": {"language": "zh-CN"}}
```
### POST /v1/memory/{memory_id}/feedback
Request:
```json
{"user_id": "user_tom", "feedback": "incorrect", "comment": "这是一次临时偏好,不应长期保留。"}
```
Response:
```json
{"status": "ok", "memory_id": "mem_abc123", "feedback": "incorrect"}
```
### GET /v1/namespaces
Request query:
```text
?user_id=user_tom&agent_id=agent_hermes_default&workspace_id=ws_memory_gateway&session_id=sess_001
```
Response:
```json
[
{"namespace": "user/user_tom/profile", "visibility": "private"},
{"namespace": "agent/agent_hermes_default/memory", "visibility": "agent-only"},
{"namespace": "workspace/ws_memory_gateway/shared", "visibility": "workspace-shared"}
]
```
### GET /v1/audit
Response:
```json
[{"action": "upsert_memory", "target_type": "memory", "decision": "allow"}]
```
### MCP tools
目标 v1 tools 见 `memory_gateway/mcp_tools_v1.py`
- `memory_search`
- `memory_upsert`
- `memory_append_episode`
- `memory_commit_session`
- `memory_get_profile`
- `memory_list_namespaces`
- `memory_delete`
- `memory_feedback`
示例 MCP call:
```json
{
"name": "memory_search",
"arguments": {
"user_id": "user_tom",
"agent_id": "agent_hermes_default",
"workspace_id": "ws_memory_gateway",
"query": "项目 POC 决策",
"limit": 5
}
}
```
## E. Skills 设计
代码骨架位于 `memory_gateway/skills/`
| Skill | 功能 | 输入 | 输出 | 触发时机 | 组件 | 写长期记忆 |
|---|---|---|---|---|---|---|
| `ingest_skill` | 标准化对话、文件、任务事件 | raw text/file/events | normalized payload | agent 写入 episode 前 | Gateway, file storage | 否 |
| `extract_memory_skill` | 从 episode/session 抽取候选记忆 | episode/session content | memory candidates | session commit / worker 定时 | LLM, EverMemOS | 否 |
| `classify_memory_skill` | 判断 memory_type、visibility、namespace | candidate memory | classification | 写入前 | ACL, namespace router | 否 |
| `retrieve_context_skill` | 聚合用户、agent、workspace 上下文 | query + context ids | ranked contexts | agent 调用前 | OpenViking, vector index | 否 |
| `commit_memory_skill` | 写入长期记忆 | MemoryRecord | stored record | 人工确认或 commit 通过 | DB, OpenViking | 是 |
| `summarize_episode_skill` | 压缩 episode | episode content | summary | session commit | LLM | 否 |
| `merge_memory_skill` | 合并重复或相近记忆 | memory ids | merged memory | EverMemOS 整理 | DB, vector index | 是 |
| `prune_memory_skill` | 衰减、归档、删除低质记忆 | policy + memory ids | archived/deleted list | 定时 worker | EverMemOS | 是 |
| `export_to_obsidian_skill` | 生成 Obsidian review draft | high-value memory | markdown draft | 高价值或需人工确认 | Obsidian | 否 |
| `import_from_obsidian_skill` | 从人工维护笔记导入记忆 | markdown path | MemoryRecord | vault sync | Obsidian, OpenViking | 是 |
## F. Obsidian Vault 设计
推荐目录:
```text
obsidian-vault/
├── Users/
│ └── {user_id}/
│ ├── Profile.md
│ ├── Preferences.md
│ └── LongTerm/
├── Agents/
│ └── {agent_id}/Experience.md
├── Workspaces/
│ └── {workspace_id}/Shared.md
├── Memories/
│ ├── LongTerm/
│ └── Archived/
├── Profiles/
├── Reviews/
│ ├── Queue/
│ ├── Accepted/
│ └── Rejected/
├── Exports/
└── Templates/
```
进入 Obsidian 的内容:
- 人工可维护 profile、preferences、长期总结。
- 高价值 workspace 知识、项目决策、复用经验。
- EverMemOS 标记为 `needs_review` 的长期记忆草稿。
不进入 Obsidian 的内容:
- 全量原始对话。
- 高频工具日志、临时 session trace。
- 低置信度候选记忆。
- 敏感凭据、token、临时错误栈。
标签体系:
```text
#memory/profile
#memory/preference
#memory/long-term
#memory/workspace
#memory/agent-experience
#memory/review
#memory/conflict
#memory/deprecated
#source/evermemos
#source/manual
#visibility/private
#visibility/workspace-shared
```
模板文件已加入 `obsidian-vault/05_Templates/`
## G. OpenViking 设计
OpenViking 作为统一 context 层Gateway 不要求 agent 直接理解 OpenViking 内部结构。
组织方式:
```text
viking://memory/user/{user_id}/profile
viking://memory/user/{user_id}/preferences
viking://memory/user/{user_id}/long_term
viking://memory/agent/{agent_id}/memory
viking://memory/workspace/{workspace_id}/shared
viking://resources/user/{user_id}/obsidian/{note_id}.md
viking://skills/memory-gateway/{skill_name}
```
检索路径:
1. Agent 调用 Gateway `/v1/memory/search` 或 MCP `memory_search`
2. Gateway 执行 Auth、ACL、namespace expansion。
3. Gateway 查询 metadata DB 和 vector index必要时调用 OpenViking search。
4. 返回统一 `MemoryRecord` 或 context chunk不暴露底层差异。
同步:
- Obsidian accepted note 通过 `import_from_obsidian_skill` 写回 Gateway再同步 OpenViking resource。
- EverMemOS consolidation 后写入 `user/{user_id}/long_term``workspace/{workspace_id}/shared`
- Gateway 保存 `source_ref`,避免 OpenViking 与 Obsidian 互相重复导入。
## H. EverMemOS 设计
输入来源:
- `EpisodeRecord`对话片段、任务执行摘要、agent 过程事件。
- `SessionRecord`session commit 包。
- `MemoryFeedback`incorrect、duplicate、outdated 等反馈。
- Obsidian review 结果accepted/rejected/edited。
整理流程:
1. 抽取:从 episode 中提炼候选事实、偏好、决策、经验。
2. 打分:根据重要性、稳定性、重复出现次数、来源可信度打分。
3. 去重:按 semantic hash + embedding 相似度查找近似 MemoryRecord。
4. 合并:相同事实合并 evidence更高置信度覆盖低置信度。
5. 冲突检测:同一 subject 的相反陈述标记 `needs_review`,不自动覆盖。
6. 衰减:长时间未命中且低反馈的记忆降低 importance。
7. 归档:过期、错误、低置信度、被人工拒绝的记忆转 archived。
8. profile evolution只有稳定、重复、高置信偏好进入 ProfileRecord。
污染控制:
- session 临时内容不直接提升为长期记忆。
- LLM 抽取结果默认是 candidate需阈值或人工确认。
- 每条长期记忆保留 source、confidence、version、feedback。
- 对 profile 更新采用 evidence count禁止一次对话永久改写强偏好。
## I. 工程目录结构
当前仓库保留 `memory_gateway/` 包名,目标结构如下:
```text
memory-gateway/
├── memory_gateway/
│ ├── api_v1.py # v1 HTTP API
│ ├── mcp_tools_v1.py # v1 MCP tool contract
│ ├── schemas.py # User/Memory/Episode/Profile/ACL/Audit
│ ├── namespace.py # namespace builder + ACL helpers
│ ├── services.py # orchestration service
│ ├── repositories.py # POC in-memory repo; later DB repo
│ ├── security/ # future auth, RBAC, audit policy
│ ├── skills/
│ │ ├── ingest_skill.py
│ │ ├── extract_memory_skill.py
│ │ ├── classify_memory_skill.py
│ │ ├── retrieve_context_skill.py
│ │ ├── commit_memory_skill.py
│ │ ├── summarize_episode_skill.py
│ │ ├── merge_memory_skill.py
│ │ ├── prune_memory_skill.py
│ │ ├── export_to_obsidian_skill.py
│ │ └── import_from_obsidian_skill.py
│ ├── adapters/
│ │ ├── openviking.py
│ │ ├── evermemos.py
│ │ └── obsidian.py
│ └── workers/
│ └── evermemos_worker.py
├── obsidian-vault/
├── integrations/
│ ├── nanobot/
│ ├── hermes/
│ └── openclaw/
└── tests/
```
如果未来迁移到更标准的 `app/`,可把 `memory_gateway/api_v1.py` 对应到 `app/api``schemas.py` 对应到 `app/schemas``services.py` 对应到 `app/services`
## J. 2 到 4 周 POC 实施计划
第一周:
- 完成 `/v1/users``/v1/memory``/v1/memory/search``/v1/episodes`
- 实现 namespace router、visibility、基础 audit。
- 存储先用 SQLite 或当前内存 repo搜索先用 lexicalOpenViking 作为可选后端。
第二周:
- 接入 OpenViking URI 写入和检索。
- 实现 `retrieve_context_skill``commit_memory_skill``summarize_episode_skill`
- 给 Hermes/Nanobot/OpenClaw 提供最小 client 示例。
第三周:
- 加 EverMemOS worker 原型session commit、candidate extraction、dedup、merge。
- 增加 feedback 流程incorrect、duplicate、outdated 影响 prune/merge。
- 生成 Obsidian review draft而不是直接写入最终知识库。
第四周:
- Obsidian import/export 双向同步。
- 增加 profile evolution 的阈值和 evidence 机制。
- 补充权限测试、污染测试、重复记忆测试、跨 agent 检索测试。
先做:
- 用户隔离、namespace、memory CRUD、episode append、session commit、basic search、audit。
暂不做:
- 完整登录系统、复杂 RBAC、多租户计费、实时同步、复杂 UI、全量向量数据库治理。
POC 成功指标:
- 不同 `user_id` 之间无法互相读写 private memory。
- 同一 workspace 的共享记忆可被授权 agent 检索。
- session 记忆不会自动污染长期记忆。
- 10 条重复候选能合并到 1 到 2 条长期记忆。
- 错误反馈后,该记忆不再进入默认 retrieval。
- Hermes/Nanobot/OpenClaw 至少两个框架能通过统一 API 调用。
## K. 推荐默认方案
第一阶段最合理默认方案:
- FastAPI 提供 `/v1` 统一 HTTP API。
- MCP 先保留现有 `/mcp/rpc`,新增 `memory_gateway/mcp_tools_v1.py` 作为目标 contract。
- 存储使用 SQLite metadata + 本地文件存 object当前代码先用 in-memory repo 验证接口。
- 搜索先用 OpenViking search + 简单 lexical fallback向量索引第二阶段引入。
- Obsidian 只保存人工可读的高价值长期记忆和 review draft。
- EverMemOS 第一阶段不做独立大系统,只做 worker 模块extract、dedup、merge、prune、profile update。
第一阶段实现 API
- `POST /v1/users`
- `GET /v1/users/{user_id}`
- `POST /v1/memory/search`
- `POST /v1/memory`
- `GET /v1/memory/{memory_id}`
- `POST /v1/episodes`
- `POST /v1/sessions/{session_id}/commit`
- `GET /v1/users/{user_id}/profile`
- `GET /v1/namespaces`
第一阶段实现 skills
- `ingest_skill`
- `summarize_episode_skill`
- `retrieve_context_skill`
- `commit_memory_skill`
- `export_to_obsidian_skill`
第二阶段再补:
- `extract_memory_skill`
- `classify_memory_skill`
- `merge_memory_skill`
- `prune_memory_skill`
- `import_from_obsidian_skill`
- 更完整的 EverMemOS consolidation 和 profile evolution。
角色分工:
- Obsidian 第一阶段review draft、人类确认 profile/长期知识。第二阶段:双向同步。
- OpenViking 第一阶段:统一 context/resource 检索入口。第二阶段:承载多 namespace context filesystem 和 skill registry。
- EverMemOS 第一阶段session commit worker。第二阶段长期记忆治理、衰减、冲突检测、profile evolution。