Skip to content

feat(memory-mcp): 专家记忆 MCP 增强——外部调用者追溯 + capture 自动提取流水线 - #445

Open
ArvinQi wants to merge 8 commits into
TencentCloud:mainfrom
ArvinQi:feat/memory-mcp-extension
Open

feat(memory-mcp): 专家记忆 MCP 增强——外部调用者追溯 + capture 自动提取流水线#445
ArvinQi wants to merge 8 commits into
TencentCloud:mainfrom
ArvinQi:feat/memory-mcp-extension

Conversation

@ArvinQi

@ArvinQi ArvinQi commented Aug 26, 2026

Copy link
Copy Markdown

Summary(摘要)

本次改动基于 feat/memory-mcp-server 分支(基线 fea4c59),对专家记忆 MCP 服务(/mcp/memory,Streamable HTTP)做三方面增强,全部改动集中在 src/octop/infra/agents/memory_mcp.py 一个文件:

  1. 外部接入方也能把内容沉淀成原子记忆memory_capture 写入 L0 原始事件后,自动触发专家记忆提取流水线(extract → candidate → promote → atom),外部 Agent / Bot 无需 octop 原生 session 也能通过 MCP 沉淀长期记忆。
  2. 调用者身份追溯(多用户隔离):新增 X-Octop-User-Id HTTP Header(或工具 user 参数),所有记忆工具(recall / search_raw / capture / save / update)都能感知"是谁在调用",每次写入落库可追溯,按人区分使用偏好。
  3. 工具定位与稳定性:工具描述明确"日常用 recall/capture、显式记忆用 save/update"的分层;FastMCP 启用 stateless HTTP,避免服务重启后 Mcp-Session-Id 失效导致的 -32600 Session not found

Why(背景与动机)

Octop 专家的记忆(事实、对话、决策)目前只有 Octop 控制台 / 进程内 Agent 能访问。社区 feat/memory-mcp-server 已把记忆通过 MCP 暴露给外部 Agent,但存在两个缺口:

  • 外部调用无法沉淀成原子记忆:提取流水线按 session_id 分组,而外部接入方(coding agent、bot)没有 octop 原生 session,memory_capture 记录后永远停留在 L0 原始层,无法蒸馏成可召回的 atom。
  • 无法区分调用者:MCP 用单一共享 token 鉴权,所有外部调用者写入同一份专家记忆,无法追溯"这条记忆是谁记录的"、无法按人区分偏好。

本 PR 解决这两个问题,同时顺带提升 MCP 服务的稳定性(stateless HTTP)。


What(改动明细)

改动 说明
capture 自动触发提取 memory_capture 写 L0 后调用 _trigger_extract,复用专家进程内 MemoryService 异步执行 extract(incremental=True, promote=True),raw 事件自动蒸馏 → 候选 → 晋升为 atom(best-effort,依赖运行时的 MemoryService,缺失时静默跳过不报错)
缺省 session 自动派生 session_id 可选;缺省时派生为 ext:{source}:{user}——同来源同用户的多次 capture 落入同一分组,提取器可聚合上下文蒸馏;不同用户自动隔离
调用者 user 追溯 X-Octop-User-Id Header 经 ASGI 中间件 → contextvar → 工具读取(或显式 user 参数覆盖);add_raw / store 落库 user 字段;recall / search_raw 返回 caller 字段
工具描述分层 memory_recall / memory_capture 标记为日常使用(自动提取);memory_save / memory_update 标记为显式记忆 / 更新(明确知道事实时才用)
stateless HTTP FastMCP(stateless_http=True):每次请求独立会话,杜绝服务重启后 Mcp-Session-Id 失效(代价是每次调用多一次 initialize)
mypy strict 修复 server.services / app_runtime 非空断言、_memory() -> Any 类型、ctx: Context 泛型兼容(保持 mcp 工具注入可用)

How it works(工作机制)

外部调用 → 原子记忆的完整链路:

外部 Agent ──MCP──▶ memory_capture(content, source)
                        │
                        ├─ add_raw(L0, user=调用者, session=ext:source:user)
                        │
                        └─ _trigger_extract() ──▶ MemoryService.extract(incremental, promote)
                                                      │
                                                      ├─ 候选 (L1)
                                                      └─ 晋升 → atom (L2) ✅ 可经 memory_recall 召回

调用者隔离:

Header: X-Octop-User-Id: user-alice ──▶ session 派生 ext:review-bot:user-alice(分组隔离)
Header: X-Octop-User-Id: user-bob   ──▶ session 派生 ext:review-bot:user-bob(分组隔离)
专家级 atom 记忆共享,但每次写入/调用可追溯调用者

Verification(验证结果)

  • memory_capture 不带 session_id → 自动派生 ext:{source}:{user}extract_scheduled: true
  • ✅ 20 秒后 L0 → 候选 → atom 产出(14 个 promoted + 1 个 rejected 去重)
  • X-Octop-User-Id Header 生效:L0 落库 user 字段(user-alice / user-bob 分属不同 session 组)
  • memory_recall 返回 caller 字段(识别调用者)
  • ✅ 5 个工具全部注册(recall / search_raw / capture / save / update)
  • ✅ ruff lint 通过、memory_mcp.py mypy strict 通过、单测 9 passed

Compatibility(兼容性说明)

  • stateless_http:依赖 mcp>=1.28(上游 uv.lock 已锁 1.28.1)✅
  • _trigger_extract:依赖 harness 运行时注入的 agent._memory_runtime.service(octop 仓库内无此符号)。代码用 getattr 防御性访问——上游环境若无该属性会静默跳过提取(extract_scheduled: false),不会报错;提取触发为 best-effort
  • 调用者 user(contextvar)_AgentRouter 中间件写入 contextvar → 工具读取,不依赖 mcp SDK 内部 request_context(stateless 模式下 SDK 不提供),跨 ASGI→工具稳定传递
  • 上游基线:仅增强 memory_mcp.py,不新增挂载、不改其他文件

Target branch

  • Base is develop (feature / fix — default)
  • Base is main (release/* or hotfix/* only)

Type of change

  • Bug fix
  • New feature
  • Breaking change
  • Documentation
  • Refactor / chore
  • Release / hotfix

Test plan

  • make all passes locally
  • Added/updated tests

Checklist

  • Updated CHANGELOG.md (if user-facing)
  • README / docs updated (if needed)

ArvinQi and others added 6 commits August 20, 2026 13:56
Add a memory MCP server (Streamable HTTP at /mcp/memory) so external
agents (coding agents, bots) can read/write Octop expert memory directly,
aligned with the in-process MemoryService capabilities.

Tools (per expert, bound at connect time via X-Octop-Agent-Id header):
- memory_recall(query, limit): full recall pipeline (tokenize + FTS +
  rerank), returns structured memories + rendered markdown
- memory_save(content, source, topic?): persist a structured fact directly
  into the atom/tree (durable, no extraction)
- memory_capture(content, source, session_id?): write an L0 raw event
  (extraction pipeline); visible immediately via memory_search_raw
- memory_search_raw(query, limit): FTS-search L0 raw events (capture
  visible before extraction)
- memory_update(atom_id, new_content, source): deprecate old atom + save new

Auth: independent token via OCTOP_MEMORY_MCP_TOKEN (fail-closed if unset);
authorization via Authorization: Bearer or X-Octop-Memory-Token.

Implementation:
- Lives in infra/agents/memory_mcp.py (no api-layer dependency; opens the
  agent Memory instance via open_memory_kwargs)
- One FastMCP per agent, routed by X-Octop-Agent-Id header at /mcp/memory
- DNS rebinding protection disabled (server runs behind a reverse proxy)
- streamable_http task groups wired into the FastAPI lifespan

Tests: tests/unit/agents/test_memory_mcp.py (tools, header routing, token
middleware, unified mount).
保留两者:
- main: enable_mobile 路由挂载
- PR: memory_mcp MCP server 挂载
- Add assert for server.services to satisfy mypy strict mode
- Add return type annotation to _memory closure
基于社区 feat/memory-mcp-server (fea4c59) 的独立扩展分支,仅改 memory_mcp.py:
- memory_capture 写 L0 后自动触发提取流水线(extract -> promote -> atom)
- 缺省 session 派生 ext:{source}:{user},外部调用无需 octop 原生 session
- X-Octop-User-Id header / user 参数,全部工具支持调用者追溯
- 工具描述区分日常(recall/capture)与显式(save/update)
- stateless HTTP + mypy strict 修复
参考 DSH 记忆工具设计,把记忆生产流水线每环节暴露为 MCP 工具:
- memory_raws: L0 原始事件结构化列表(session/host/user 过滤)
- memory_candidates: L1 候选队列查询(pending/promoted/rejected)
- memory_extract: 手动调度提取(L0→L1,promote 可直达 L2)
- memory_promote: 审核晋升候选(L1→L2)
- memory_reject: 拒绝候选(标记+审计)
- memory_atoms: L2 原子记忆结构化查询
全部工具经 MemoryService / Memory 分层 API 实现,与 capture 自动流水线互补,
支持外部按需调度记忆生产。
- 删除 memory_atoms(memory_recall 已覆盖 L2 查询)
- 删除 memory_search_raw(memory_raws 增加 query 参数走 FTS,覆盖 L0 搜索)
- 保留 9 个:recall/save/capture/update(读写)+
  raws/candidates/extract/promote/reject(分层查询+流水线调度)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant