remove repeat file
一个基于 MCP v2 (streamable-http) 的「时空螺旋记忆引擎」演示工程。
streamable-http
它把「记忆」建模成会随时间演化(形成 → 巩固 → 稳定 → 衰减 → 重巩固)的粒子, 在三维螺旋轨道上运动;后端负责记忆的存储 / 召回 / 演化,前端负责把这一切实时可视化。
技术栈:Python 3.13 + MCP SDK (后端) · React 19 + TypeScript + Vite + Canvas (前端) · BGE-M3 (本地向量召回) · DeepSeek API + MLX (Qwen3-4B / Qwen3-VL-4B) (端云协同推理)。
图 1 · 看板总览:左侧为 MCP Tool 模拟器(可直接对 memory 工具发起 store / recall / consolidate / decay / evolve 等 action)与三种记忆类型的数量统计; 中间为螺旋 Canvas —— 彩色粒子对应各轨道上的记忆节点,节点间连线表示语义关联,左下角给出各类型计数、平均强度与「时空速率」倍速滑杆; 右侧是实时「记忆流」(Memory Stream)与 MCP 调用日志;顶栏显示端侧 / 云端模型、DS API 与 MCP Server 连接状态、实时帧率。
memory
store
recall
consolidate
decay
evolve
图 2 · 记忆详情浮层:点击螺旋上的节点后弹出,展示记忆内容、基础属性(强度 / 访问次数 / 创建于 / 当前阶段)、 「时空螺旋演进轨迹」(如 t+0.0s → formation → 强度 30%),以及底部的 mcp.call("memory", {"action": "detail", "id": 15}) 协议映射,便于把界面现象与 MCP 调用一一对照。
t+0.0s → formation → 强度 30%
mcp.call("memory", {"action": "detail", "id": 15})
┌──────────────────────────────────────────────────────────────────────────┐ │ web/ React 可视化看板 │ │ TopBar · LeftPanel(Tool 模拟器) · CenterPanel(螺旋 Canvas) · RightPanel │ │ │ │ api/mcp.ts ─ JSON-RPC over HTTP ─┐ store/ClientStore (外部 store) │ │ api/stream.ts ─ subscriptions/listen (SSE) ┘ │ │ ⚠ 无 Mock 分支,前端始终直连真实 MCP 后端 │ └────────────────────────────────┬─────────────────────────────────────────┘ │ POST /mcp (vite proxy → 127.0.0.1:8000) ┌────────────────────────────────▼─────────────────────────────────────────┐ │ server.py · MCPServer("Memory Engine") │ │ │ │ tools: memory(action=...) · read_pic(uri) │ │ resources: memory://snapshot · memory://agent-log │ │ notif: notifications/resources/updated (500ms tick 后推送) │ │ │ action 分发(memory/actions.py) │ │ ┌────────────────┐ ▼ ┌──────────────────┐ ┌────────────────────────┐ │ │ │ dashboard/ │ │ memory/ │ │ llm/ │ │ │ │ Scheduler │──▶ │ actions.dispatch │ │ DeepSeekClient │ │ │ │ (500ms tick) │ │ ↓ │ │ MLXBrain / MLXVLM │ │ │ │ │ │ MemoryEngine │◀──│ │ │ │ └────────────────┘ └──────────────────┘ └────────────────────────┘ │ │ snapshot.py · seed.py · embedder.py · distiller.py │ └────────────────────────────────┬─────────────────────────────────────────┘ │ ┌──────────────────┴──────────────────┐ │ scripts/agent_loop.py (端侧 Agent) │ │ MLX 归一化 → DeepSeek tool-calling │ └─────────────────────────────────────┘
数据流(一次记忆写入)
scripts/agent_loop.py
MLXBrain
MLXVLM
blobs/
memory://blob/xxx
read_pic(uri)
memory(action="store")
server.py
memory/actions.py::dispatch()
MemoryEngine.store()
Scheduler
ResourceUpdated
memory://snapshot
evolve()
maybe_distill()
mcp_demo/ ├── server.py # MCP 服务入口:工具/资源声明、订阅总线、lifespan(纯协议层) ├── load_model.py # MLX 文本模型最小验证脚本 ├── load_vl_model.py # MLX 视觉模型最小验证脚本 ├── pyproject.toml # Python 依赖与工程元数据 (uv 管理) ├── uv.lock / .python-version # 锁定依赖与 Python 版本 (3.13) │ ├── memory/ # 记忆领域层(与 MCP 协议解耦) │ ├── engine.py # MemoryEngine:节点/连线/粒子 + store/recall/consolidate/decay/evolve/distill │ ├── actions.py # action 注册表 + dispatch():store/recall/consolidate/... 各一个 handler │ ├── snapshot.py # build_snapshot():内部状态 → 前端可消费的快照 │ ├── seed.py # load_seed_nodes():从 JSONL 灌入预置记忆(MOCK_SEED=true 时) │ ├── embedder.py # Embedder:BGE-M3 本地向量化(L2 归一化) │ ├── distiller.py # DeepSeekDistiller:episodic → semantic/procedural 凝练 │ ├── models.py # Pydantic 模型:MemoryRecord / LinkRecord │ └── store.py # SQLite + FTS5(含 CJK bigram 查询改写)持久化层 │ ├── dashboard/ │ └── scheduler.py # 后端 tick 循环:驱动引擎 + 发布资源更新通知 │ ├── llm/ # 模型层 │ ├── deepseek_client.py # DeepSeek API 客户端(OpenAI 兼容) │ ├── mlx_brain.py # 端侧 Qwen3-4B:碎片 → 通顺中文 │ ├── mlx_vlm_brain.py # 端侧 Qwen3-VL-4B:图片 → 初步描述 │ └── tools.py # memory 工具的 OpenAI function-calling schema │ ├── scripts/ # 端到端验证 / 演示脚本 │ ├── agent_loop.py # 完整链路:碎片/图片 → MLX → DeepSeek → MCP tools │ ├── check_c2.py # 验收①:DeepSeek 能否按 schema 产出 tool_calls │ └── check_read_pic.py # 验收②:read_pic + DeepSeek 多模态链路 │ ├── data/ │ └── mock_seed.jsonl # 30 条预置记忆(跑步主题,episodic/semantic/procedural 混合) │ ├── assets/ # README 截图:web_1.png(看板总览)/ web_2.png(记忆详情浮层) │ ├── web/ # 前端可视化看板 (React 19 + Vite) │ ├── src/ │ │ ├── api/ # mcp.ts (JSON-RPC) / stream.ts (SSE) / sseParser.ts │ │ ├── store/ # ClientStore (useSyncExternalStore 外部 store) │ │ ├── hooks/ # 流连接、Agent 日志轮询、tool 调用、速率控制 │ │ ├── renderer/ # SpiralRenderer (Canvas 螺旋渲染) + layout 坐标映射 │ │ ├── components/ # TopBar / LeftPanel / CenterPanel / RightPanel / DetailOverlay │ │ ├── constants/ # 记忆类型、阶段、示例内容元数据 │ │ └── types/ # Snapshot / MemoryNodeDTO 等 DTO 类型 │ └── vite.config.ts # 端口 5173,/mcp 反向代理到 127.0.0.1:8000 │ ├── bge-m3/ # 本地嵌入模型 (BAAI/bge-m3, 1024 维, 8192 tokens) ├── blobs/ # MCP blob 资源目录(图片附件,URI: memory://blob/xxx,运行时自动创建) └── logs/ └── agent_calls.jsonl # agent_loop 写入的 tool 调用日志(前端轮询展示,运行时自动创建)
episodic
#00f0ff
semantic
#b14aed
procedural
#ffc857
衰减率越高越持久:情景记忆最易遗忘,程序记忆最稳定。
formation(形成)→ consolidation(巩固)→ stabilization(稳定)→ decay(衰减)→ reconsolidation(重巩固)
formation
consolidation
stabilization
reconsolidation
recall()
consolidating
decaying
update(dt)
setTimeout
每个节点拥有 spiralAngle / spiralTargetRadius / spiralTargetY,前端据此建立极坐标映射(renderer/layout.ts)。 三个记忆类型各占一条螺旋轨道;evolve() 会让角度与半径向前推进,episodic 在「强度 > 0.7 且访问次数 > 3」时自动抽象为 semantic。
spiralAngle
spiralTargetRadius
spiralTargetY
renderer/layout.ts
引擎内部统一使用 time.monotonic() 防系统时钟跳变;对外通过 engine.to_wall_ms() 转成 Unix epoch 毫秒供前端 Date 使用。 occurredAt(事件实际发生时间)与 createdAt(写入时间)分离,支持把「回忆过去」正确锚定到时间轴。
time.monotonic()
engine.to_wall_ms()
Date
occurredAt
createdAt
前置:Python 3.13、uv、Node.js **20+**。
uv
# 1) Python 依赖 # 必须带 --extra embed:不带的话 uv sync 会把 embed extra 里的 mlx-lm / numpy / transformers # 以及下方手工安装的包识别为"多余包"并卸载,导致 server.py 无法导入。 uv sync --extra embed # 2) 运行期还需要的包(目前未声明在 pyproject.toml,需手工安装) uv pip install openai sentence-transformers mlx-vlm # 3) 前端依赖 cd web && npm install
依赖对应关系:memory/embedder.py → sentence-transformers + numpy;memory/models.py → pydantic; llm/deepseek_client.py → openai;端侧模型 → mlx-lm / mlx-vlm。 运行期降级只覆盖「模块可导入但初始化失败」:BGE-M3 模型加载失败时 recall 退化为子串匹配, DeepSeek 客户端初始化失败时 distill 关闭——服务仍能启动。若包本身缺失(ModuleNotFoundError), 由于 server.py 在模块顶层 import 这些依赖,进程会直接启动失败。详见第 9 节。
依赖对应关系:memory/embedder.py → sentence-transformers + numpy;memory/models.py → pydantic; llm/deepseek_client.py → openai;端侧模型 → mlx-lm / mlx-vlm。
memory/embedder.py
sentence-transformers
numpy
memory/models.py
pydantic
llm/deepseek_client.py
openai
mlx-lm
mlx-vlm
运行期降级只覆盖「模块可导入但初始化失败」:BGE-M3 模型加载失败时 recall 退化为子串匹配, DeepSeek 客户端初始化失败时 distill 关闭——服务仍能启动。若包本身缺失(ModuleNotFoundError), 由于 server.py 在模块顶层 import 这些依赖,进程会直接启动失败。详见第 9 节。
ModuleNotFoundError
DEEPSEEK_API_KEY
deepseek_client.py
MOCK_SEED
true
data/mock_seed.jsonl
export DEEPSEEK_API_KEY=sk-xxxxxxxx export MOCK_SEED=true # 可选:启动时加载演示种子数据
uv run server.py # 空引擎启动 MOCK_SEED=true uv run server.py # 启动即灌入 data/mock_seed.jsonl 的 30 条演示记忆 # → streamable-http @ http://127.0.0.1:8000/mcp # stateless_http=True, json_response=True
启动时 lifespan 依次:创建 MemoryEngine → 启动 Scheduler → 加载 BGE-M3 →(若 MOCK_SEED=true)灌入种子数据 → 挂载 DeepSeek 凝练器。 其中 BGE-M3 与凝练器加载失败会自动降级,不阻断服务启动。
MemoryEngine
MOCK_SEED=true
前端必须先启动后端:web/src/mock/ 已移除,前端没有任何 Mock 分支, api/client.ts 与 api/stream.ts 始终向 /mcp 发起真实 JSON-RPC 请求。
web/src/mock/
api/client.ts
api/stream.ts
/mcp
# 终端 1:后端 uv run server.py # 终端 2:前端 cd web npm run dev # → http://localhost:5173 (vite 将 /mcp 代理到 127.0.0.1:8000)
若后端未启动,看板会保持空状态并在控制台报 MCP request failed / stream error。 启动成功后的界面即上方「界面预览」中的图 1;点击任一节点可看到图 2 的详情浮层。
MCP request failed
stream error
需先启动后端,并配置 DEEPSEEK_API_KEY:
uv run scripts/agent_loop.py # 完整链路:文本/图片碎片 → 存储 uv run scripts/check_c2.py # 仅验证 DeepSeek tool-calling schema uv run scripts/check_read_pic.py # 验证 read_pic + 多模态链路 (需 blobs/person.png)
agent_loop.py 顶部的 INPUTS 列表可增删待处理碎片;取消注释 {"kind": "image", "path": "..."} 即可测试图片路径。
agent_loop.py
INPUTS
{"kind": "image", "path": "..."}
memory(action, ...) — 统一记忆操作入口
memory(action, ...)
content
memory_type
occurred_at
entities
attachment_uri
query
detail
id
evolution
set_speed
speed
所有 action 由 memory/actions.py 的 _ACTIONS 注册表分发,server.py 中的 memory() 只做参数收集,不再含 if/elif 分支。
memory/actions.py
_ACTIONS
memory()
if/elif
两点返回契约:
{ok, log:{time,method,status,detail}, affected:[id...]}
affected_node
ok
log
fetchMemory()
read_pic(uri) — 读取 blobs/ 下的图片,返回 {ok, uri, mime, size, base64}。 URI 必须形如 memory://blob/<name>,且 <name> 不允许包含 / 或 ..(防目录穿越)。
{ok, uri, mime, size, base64}
memory://blob/<name>
<name>
/
..
memory://agent-log
logs/agent_calls.jsonl
快照不走 SSE 直接推数据,而是 「通知 + 拉取」:
Scheduler 每 500ms ──▶ bus.publish(ResourceUpdated("memory://snapshot")) │ 前端 subscriptions/listen (filter: resourceSubscriptions=[memory://snapshot]) │ 收到 notifications/resources/updated ▼ resources/read("memory://snapshot") → 渲染
前端按 MCP v2 modern 路由规范发送 MCP-Protocol-Version / mcp-method / mcp-name 头。
MCP-Protocol-Version
mcp-method
mcp-name
data/mock_seed.jsonl 是每行一个 JSON 对象的种子文件,由 memory/seed.py::load_seed_nodes() 读取并逐条走 engine.store():
memory/seed.py::load_seed_nodes()
engine.store()
{"type": "episodic", "content": "今早绕湖跑了十公里,配速五分半", "entities": ["跑步", "湖", "十公里"]} {"type": "semantic", "content": "用户习惯早上六点起床跑步", "entities": ["跑步", "早起"]}
type
attachmentUri
unicode61
memory/store.py
read_pic
image_url
dashboard/scheduler.py
memory/engine.py
dispatch()
memory/seed.py
memory/snapshot.py
memory/distiller.py
MemoryRecord
LinkRecord
llm/mlx_brain.py
llm/mlx_vlm_brain.py
llm/tools.py
web/src/api/
web/src/store/
ClientStore
useSyncExternalStore
web/src/renderer/
web/src/components/
assets/web_*.png
spiral_memory一个以记忆工具为探针研究MCP Python SDK v2源码的端侧 Agent 项目。以运动场景驱动,基于端侧推理(MLX+Qwen3)+云端大脑(DeepSeek)推理的多模态融合Agent机制的记忆机制:MLX 小脑将模糊片段展开为提示,DeepSeek 大脑凝练出情景/语义/程序三类记忆。前端以时空螺旋实时可视化记忆的形成、凝练与衰减——记忆不会消失,只会变暗。
版权所有:中国计算机学会技术支持:开源发展技术委员会 京ICP备13000930号-9 京公网安备 11010802047560号
MCP v2 Memory Engine
一个基于 MCP v2 (
streamable-http) 的「时空螺旋记忆引擎」演示工程。它把「记忆」建模成会随时间演化(形成 → 巩固 → 稳定 → 衰减 → 重巩固)的粒子, 在三维螺旋轨道上运动;后端负责记忆的存储 / 召回 / 演化,前端负责把这一切实时可视化。
技术栈:Python 3.13 + MCP SDK (后端) · React 19 + TypeScript + Vite + Canvas (前端) · BGE-M3 (本地向量召回) · DeepSeek API + MLX (Qwen3-4B / Qwen3-VL-4B) (端云协同推理)。
界面预览
图 1 · 看板总览:左侧为 MCP Tool 模拟器(可直接对
memory工具发起store/recall/consolidate/decay/evolve等 action)与三种记忆类型的数量统计; 中间为螺旋 Canvas —— 彩色粒子对应各轨道上的记忆节点,节点间连线表示语义关联,左下角给出各类型计数、平均强度与「时空速率」倍速滑杆; 右侧是实时「记忆流」(Memory Stream)与 MCP 调用日志;顶栏显示端侧 / 云端模型、DS API 与 MCP Server 连接状态、实时帧率。图 2 · 记忆详情浮层:点击螺旋上的节点后弹出,展示记忆内容、基础属性(强度 / 访问次数 / 创建于 / 当前阶段)、 「时空螺旋演进轨迹」(如
t+0.0s → formation → 强度 30%),以及底部的mcp.call("memory", {"action": "detail", "id": 15})协议映射,便于把界面现象与 MCP 调用一一对照。1. 整体架构
数据流(一次记忆写入)
scripts/agent_loop.py收到生活碎片(文字 / 图片)。MLXBrain(Qwen3-4B)归一化成通顺中文;图片 →MLXVLM(Qwen3-VL-4B)初步描述,并复制进blobs/得到memory://blob/xxxURI。read_pic(uri)看原图,再调memory(action="store")落库。server.py的memory工具不做业务判断,直接把参数交给memory/actions.py::dispatch()路由到对应 handler。MemoryEngine.store()写入节点、按类型自动连线、生成粒子,并即时计算 BGE-M3 向量。Scheduler每 500ms 推进一次引擎状态并发布ResourceUpdated通知;前端收到通知后回拉memory://snapshot刷新螺旋画面。evolve()(螺旋演化 / 情景→语义抽象),每 20s 触发maybe_distill()(DeepSeek 把积累的 episodic 凝练成 semantic / procedural)。2. 目录结构
3. 核心概念
3.1 三种记忆类型
episodic#00f0ff青semantic#b14aed紫procedural#ffc857金衰减率越高越持久:情景记忆最易遗忘,程序记忆最稳定。
3.2 记忆生命周期(Phase)
formation(形成)→consolidation(巩固)→stabilization(稳定)→decay(衰减)→reconsolidation(重巩固)recall()命中的记忆会获得强度加成,若处于decay/stabilization则回到reconsolidation(记忆提取后重新变得不稳定,可被新信息改写)。consolidating/decaying是短暂的视觉态标志,由引擎在update(dt)中基于时间戳自动复位(服务端不再依赖setTimeout)。3.3 时空螺旋
每个节点拥有
spiralAngle/spiralTargetRadius/spiralTargetY,前端据此建立极坐标映射(renderer/layout.ts)。 三个记忆类型各占一条螺旋轨道;evolve()会让角度与半径向前推进,episodic在「强度 > 0.7 且访问次数 > 3」时自动抽象为semantic。3.4 时间语义:monotonic 与 wall clock
引擎内部统一使用
time.monotonic()防系统时钟跳变;对外通过engine.to_wall_ms()转成 Unix epoch 毫秒供前端Date使用。occurredAt(事件实际发生时间)与createdAt(写入时间)分离,支持把「回忆过去」正确锚定到时间轴。4. 环境准备
前置:Python 3.13、
uv、Node.js **20+**。环境变量
DEEPSEEK_API_KEYdeepseek_client.py从环境变量读取,不硬编码MOCK_SEEDtrue时,后端启动后从data/mock_seed.jsonl灌入 30 条预置记忆;不设则不灌种子5. 运行
5.1 启动后端 MCP 服务(端口 8000)
启动时 lifespan 依次:创建
MemoryEngine→ 启动Scheduler→ 加载 BGE-M3 →(若MOCK_SEED=true)灌入种子数据 → 挂载 DeepSeek 凝练器。 其中 BGE-M3 与凝练器加载失败会自动降级,不阻断服务启动。5.2 启动前端看板(端口 5173)
若后端未启动,看板会保持空状态并在控制台报
MCP request failed/stream error。 启动成功后的界面即上方「界面预览」中的图 1;点击任一节点可看到图 2 的详情浮层。5.3 运行端到端 Agent(可选)
需先启动后端,并配置
DEEPSEEK_API_KEY:agent_loop.py顶部的INPUTS列表可增删待处理碎片;取消注释{"kind": "image", "path": "..."}即可测试图片路径。6. MCP 接口
6.1 Tools
memory(action, ...)— 统一记忆操作入口storecontent,memory_type(必填),occurred_at,entities,attachment_urirecallqueryconsolidatedecayevolvedetailidevolution演化轨迹)set_speedspeed所有 action 由
memory/actions.py的_ACTIONS注册表分发,server.py中的memory()只做参数收集,不再含if/elif分支。两点返回契约:
detail外,其余 action 统一返回{ok, log:{time,method,status,detail}, affected:[id...]},store额外带affected_node。detail直接返回裸 node dict(含evolution数组,时间为 wall clock 毫秒),不带ok/log外壳 —— 与前端fetchMemory()的类型契约一致。read_pic(uri)— 读取blobs/下的图片,返回{ok, uri, mime, size, base64}。 URI 必须形如memory://blob/<name>,且<name>不允许包含/或..(防目录穿越)。6.2 Resources
memory://snapshotmemory://agent-loglogs/agent_calls.jsonl最近 50 条 tool 调用日志6.3 通知与订阅
快照不走 SSE 直接推数据,而是 「通知 + 拉取」:
前端按 MCP v2 modern 路由规范发送
MCP-Protocol-Version/mcp-method/mcp-name头。6.4 预置种子数据
data/mock_seed.jsonl是每行一个 JSON 对象的种子文件,由memory/seed.py::load_seed_nodes()读取并逐条走engine.store():MOCK_SEED=true时加载;不设置该变量则跳过(默认不灌数据)。MemoryEngine.store()对齐:type(必填) /content(必填) /entities/occurredAt/attachmentUri。type、空content的行会被跳过并记 warning,不影响其余行。episodic足够多时可被 20s 蒸馏任务自动凝练出新的semantic/procedural。7. 关键设计说明
server.py只负责 MCP 暴露与参数收集,action 的执行逻辑集中在memory/actions.py的注册表中, 新增 action 只需加一个 handler 并注册,不必改动工具签名(SDK 依赖签名生成 JSON schema,因此签名保持稳定)。memory://snapshot与memory://agent-log,避免双份状态漂移。unicode61会把整段中文当作一个 token,memory/store.py在查询侧改写为连续 bigram 短语以支持中文子串检索。read_pic看原图后做最终判断;base64 不塞进 tool 消息,而是以image_url追加 user 消息(对齐官方 agent 做法)。8. 模块速查表
server.pymemory/read_pic工具声明、两个资源、订阅总线、lifespan 装配dashboard/scheduler.pymemory/engine.pymemory/actions.pydispatch(),含recall等 7 个 handler 与统一响应壳memory/seed.pydata/mock_seed.jsonl灌入预置记忆(MOCK_SEED=true触发)memory/snapshot.pymemory/embedder.pymemory/distiller.pymemory/store.pymemory/models.pyMemoryRecord/LinkRecordPydantic 模型llm/deepseek_client.pyllm/mlx_brain.pyllm/mlx_vlm_brain.pyllm/tools.pymemory工具的 function-calling schema(与server.py签名同步)scripts/agent_loop.pyweb/src/api/web/src/store/ClientStore(快照 / 视觉插值 / 日志)+useSyncExternalStore绑定web/src/renderer/web/src/components/data/mock_seed.jsonlMOCK_SEED=true加载)assets/web_*.png