Skip to main content

Dual-system layered memory SDK (Hy-Memory reproduction)

Project description

dual-mem

分层记忆内核 —— 从零复现 Hy-Memory 的演化链 / System1-System2 / 8 层记忆框架,封装成一套干净、可测、契约对齐的 Python 记忆 SDK。

dual_mem 是一个面向 LLM 应用 / Agent 的长期记忆组件。它把零散的对话沉淀成结构化、可演化、可检索的记忆,并通过 SDK / REST / MCP / CLI / Skill 五种方式对外提供能力。

核心能力:

  • 演化链:偏好/事实发生变化时(如「最爱 Java」→「转向 Python」、「住上海」→「搬北京」),写侧显式建立 supersedes/superseded_by 双向指针;旧版本软删保留,检索命中链头时自动展开整条历史(最新→最旧)。
  • System1(写侧同步认知):一次写入内集中所有 LLM 调用 —— 抽取 identity/facts、更新 L0 结构化画像、生成摘要、协调演化链。
  • System2(ultra 异步沉淀):把同领域事实聚类,升维为高阶认知结构(L6 行为 Schema / L7 意图),并支持跨域升维出「核心 Schema」。
  • 三路召回(零 LLM):profile(画像/身份/模式)/ proactive(推断意图)/ normal(普通事实知识),叠加演化链展开、BM25+RRF 重排、中文时间词解析、tag 桥接。

安装

需要 Python ≥ 3.11。

git clone <repo-url> dual-mem
cd dual-mem
pip install -e .          # 运行时依赖
pip install -e ".[dev]"   # 含测试依赖(pytest / pytest-asyncio / respx)

主要依赖:pydantic / pydantic-settings(配置),openai(LLM/Embed 兼容协议),chromadb(向量库),kuzu(图库),scikit-learn+numpy(System2 聚类),rank-bm25+jieba(中文重排),fastapi+uvicorn(REST),typer(CLI),mcp(MCP server)。


快速开始(SDK)

MemoryClient 是全异步门面。最小例子:

import asyncio
from dual_mem import MemoryClient


async def main():
    client = MemoryClient(mode="pro", storage_dir="./.dual_mem_data")

    await client.add(
        content="我最爱的编程语言是 Java,已经用了5年。",
        app_id="my_app",
        user_id="alice",
    )

    res = await client.search(
        query="用户的编程语言偏好",
        app_ids=["my_app"],
        user_id="alice",
    )
    # res["memories"] = {"profile": [...], "proactive": [...], "normal": [...]}
    for m in res["memories"]["profile"]:
        print(m["content"], m.get("evolution_chain"))


asyncio.run(main())

配置(YAML)

配置主源是 YAML 文件,默认读 ~/.dual_mem/config.yaml(可用环境变量 DUAL_MEM_CONFIG_FILE 指定其它路径)。复制仓库根的 config.example.yaml 即可:

mkdir -p ~/.dual_mem
cp config.example.yaml ~/.dual_mem/config.yaml
mode: pro                       # lite | pro | ultra
storage_dir: ./.dual_mem_data
llm_base_url: https://api.openai.com/v1
llm_api_key: sk-your-llm-key
llm_model: gpt-4o-mini
embed_base_url: https://api.openai.com/v1
embed_api_key: sk-your-embed-key
embed_model: text-embedding-3-small
embed_dim: 1536
auth_disabled: true             # REST 本地鉴权开关
app_whitelist:                  # REST app_id 白名单
  - default
system2_trigger_mode: per_write # per_write | manual | scheduled

优先级:显式传参 MemoryClient(mode=...) > DUAL_MEM_ 前缀环境变量(临时覆盖)> YAML 文件 > 默认值。

LLM/Embed 走 OpenAI 兼容协议,base_url 可指向任意兼容服务(OpenAI / Hunyuan / 本地推理)。


三档模式

mode 自动派生 agent_modeenable_graph,决定写入深度、LLM 开销与是否启用图库:

维度 lite pro ultra
一次 add 的 LLM 次数 0(仅 embed) 2~3 pro + 异步 1~N
写入层 仅 L1_RAW L0 / L1(SHADOW) / L2 / L3 / L4 + L5 / L6 / L7(图库)
System2 异步沉淀 (队列 + 跨域 sweeper)
图库(Kuzu)
Writer MemoryWriter MemoryWriter System2Writer(内部仍跑 System1)
适用 纯检索 / 离线写入 个性化助手 深度画像 / 认知沉淀

lite 与 pro 共用 MemoryWriter,差别仅在 agent_mode;ultra 用 System2Writer,写完 System1 后 fire-and-forget 入 System2 队列,由 client.digest() 或调度触发加工。


五种用法

1. SDK

见上「快速开始」。MemoryClient 提供 add / search / get / list / update / delete / delete_bulk / digest,全异步。

2. REST(dual-mem serve

dual-mem serve --host 0.0.0.0 --port 8000

契约对齐 hy-api:POST /v1/memories/POST /v1/memories/searchGET /v1/memories/GET|DELETE /v1/memories/{id}DELETE /v1/memories/(需 confirm=true),外加 /health /ping /info。开启鉴权时需 Authorization: Bearer <appkey>app_id 在白名单内。

curl -X POST http://localhost:8000/v1/memories/ \
  -H "Content-Type: application/json" \
  -d '{"content": "用户喜欢喝咖啡", "app_id": "default", "user_id": "u1"}'

3. MCP(dual-mem-mcp

dual-mem-mcp                                            # stdio(供 Cursor/Claude Desktop 经 uvx 拉起)
dual-mem-mcp --transport streamable-http --port 8765   # HTTP,暴露 /mcp 端点

暴露工具:memory_add / memory_search / memory_get / memory_list / memory_delete,可直接接入支持 MCP 的 Agent 客户端。memory_search 返回结果按 profile/proactive/normal 三路分组,演化过的记忆带 evolution_chain

Cursor mcp.json 最小配置:

{ "mcpServers": { "dual-mem": { "command": "uvx", "args": ["dual-mem-mcp"] } } }

更多传输模式、uvx、Cursor/Claude Desktop 配置见 docs/mcp_integration.md;整体分层见 docs/architecture.md

4. CLI

dual-mem add --content "用户喜欢喝咖啡" --app-id default --user-id u1
dual-mem search "用户的饮品偏好" --app-id default --user-id u1
dual-mem list --app-id default --user-id u1
dual-mem get <memory_id>
dual-mem delete <memory_id>
dual-mem digest          # 触发 System2 沉淀(ultra)

5. Skill

skills/dual-mem/SKILL.md 指导 Agent 何时、如何通过 CLI 或 MCP 读写记忆(调用范式、三路分组用法、演化链解读),可直接装入支持 Skill 的 Agent。


架构

8 层记忆框架(L0–L7)

名称 含义
L0 BASIC_INFO 结构化画像(name/age/location/occupation/employer),diff-only 演化链
L1 RAW 原始对话(pro/ultra 写后转 SHADOW,避免与 L2/L4 双重命中)
L2 FACT 客观事件、经历、计划
L3 SUMMARY 长内容(≥500 字)摘要
L4 IDENTITY 偏好、态度、价值观、人格特质
L5 KNOWLEDGE 领域知识
L6 SCHEMA System2 提炼的行为模式(场景+模式+洞察)
L7 INTENTION System2 推断的具体未来意图

写侧 System1(集中 LLM)

add → 写 L1_RAW → Extractor(identity/facts JSON + L0 tool)
                → Summarizer(≥500 字才出 L3)
                → Reconciler(search_query 改写 → 双路检索 → 判 ADD/SUPERSEDE/DELETE)
                → L1 转 SHADOW

异步 System2(仅 ultra)

digest() → 取 fresh facts → 两阶段 DBSCAN 聚类
        → System2Agent 真 ReAct 循环(OpenAI function-calling 8 工具,tool_choice=auto,
           上限 system2_max_iters=10 轮,无 tool_calls 即终止)
           读:search_vdb / search_graph / get_node / expand_node
           写:create_schema(L6) / create_intention(L7) / add_evidence / add_edge
        → CrossDomainSweeper:基础 Schema ≥5 → 升维出核心 Schema(CROSS_ABSTRACTS_TO)

演化链

Reconciler 在写侧显式建 supersedes/superseded_by 双向指针;DELETE 不真删(is_latest=False + SHADOW),信息无损。读侧命中链中任一节点 → 回溯到链头(is_latest)→ 附 evolution_chain(最新→最旧)。不依赖相似度聚类。

三路召回(零 LLM)

召回层 说明
profile L0 / L4 / L6 稳定画像;identity 40% / schema 40% / 自由 20% 配额,profile_limit=-1 全量
proactive L7 推断意图;intention_limit=0 时恒空(默认关)
normal L2 / L5 / L3 / L1 普通事实知识;受 limitmin_score 约束,BM25+RRF 重排

返回的 profile/proactive/normal 是路由分组,不是层;真实层看每条记忆的 category 字段。


与 Hy-Memory 的差异

本项目复现的是 Hy-Memory 的能力与行为,不是源码拷贝。主要差异:

  1. 8 层命名对齐代码而非官网:官网文档 L1–L6 命名相对源码有 ±1 位移,本项目弃用官网命名,采用源码语义的 L0_BASIC_INFOL7_INTENTION
  2. REST 契约对齐 hy-api 文档:源码 REST 是无鉴权的 stdlib HTTPServer + 自定义 /api/v1/add 内部协议;本项目用 FastAPI 重写为文档约定的 /v1/memories/ 契约 + Bearer/appkey 鉴权 + 统一错误码。
  3. 新增 MCP / CLI / Skill / 鉴权 / 测试:源码这五块完全没有,本项目全新构建(含 128 单测 + e2e smoke)。
  4. 时间查询解析增强:源码默认 legacy reader 不解析时间词(横评点名短板,「昨天聊了啥」类几乎全错);本项目默认给 reader 接入中文相对时间解析(昨天/上周/X月X日created_after 区间)。
  5. System2 工具集对齐:源码 System2 是 8 工具 ReAct 多轮循环;本项目同样实现为 chat_with_tools + tool_choice=auto 的真 ReAct 循环(system2_max_iters=10 轮上限,无 tool_calls 即终止),早期文档里的「单次 LLM 输出 ops JSON 数组」已废弃。
  6. 配置收敛:源码几十个分散的 MEMORY_* 环境变量收敛为 DUAL_MEM_ 前缀的 pydantic-settings,mode 自动派生其余开关。
  7. 砍掉死代码:源码 4 套 reader(legacy/hybrid/hybrid_tag/exhaustive)、rdb.py 桩、scorer(V1)emotion_analyzerintention_detector 等只留一套增强 reader。

已知短板 / 限制

  • supersedes 不跨层纠错:演化链是层内链路(L4↔L4、L2↔L2)。L0 若被误抽(如一句话误判用户名),不会被 L2/L4 纠正 —— 如实保留源码行为。
  • tag_index 图桥接为简化版:跨域升维(CrossDomainSweeper)用「基础 Schema 数 ≥5 触发 + 一次 LLM 归纳」,而非源码的行为升维 embed + 矩阵碰撞 + Union-Find。
  • L0 抽取依赖 LLM 质量:结构化画像的准确性取决于 extract 阶段 LLM 的判断,弱模型可能误抽/漏抽。
  • Kuzu 无原生向量索引:L6/L7 的向量召回在 Python 侧做 cosine 全量比对,数据量大时性能受限。
  • 摘要触发阈值:L3 仅在 content ≥ 500 字才生成;单条短文本 add 几乎不产摘要(messages 形态更易触发)。

测试与开发

# 全量单测(已全程 mock LLM/Embed,不真连网络)
python -m pytest tests/ -q

# 静态检查(__init__ 参数 / 方法名 / 模块路径 / 导出列表一致性)
python ~/.agents/rules/check_ast.py .

# 端到端 smoke(三档 + REST + MCP + CLI,无需 API key)
python tmp/m7_e2e_demo.py

测试遵循 TDD,tests/conftest.py 注入确定性 FakeEmbed + 脚本化 FakeLLMClient,绝不真连 LLM/Embed。

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

dual_mem-0.1.0.tar.gz (146.3 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

dual_mem-0.1.0-py3-none-any.whl (128.3 kB view details)

Uploaded Python 3

File details

Details for the file dual_mem-0.1.0.tar.gz.

File metadata

  • Download URL: dual_mem-0.1.0.tar.gz
  • Upload date:
  • Size: 146.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.12.5

File hashes

Hashes for dual_mem-0.1.0.tar.gz
Algorithm Hash digest
SHA256 eb2b10018c36a747afa544540bf53dfe13ef397b76870d8a98f2c7fe85d2a39e
MD5 b4ed3f13d78352608f575f55d74117dd
BLAKE2b-256 1e3eed45776bea203d0fa44b8798c69549a236032fddd628e81886584f6d97c5

See more details on using hashes here.

File details

Details for the file dual_mem-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: dual_mem-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 128.3 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.12.5

File hashes

Hashes for dual_mem-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 d7f929464b4e35fa7a994d26ce39a252bec136f2ffe804ab57a69e49333d60d2
MD5 3edcddec6b6486083a323f2f9d0cc395
BLAKE2b-256 75f2278e191c0651b76c8d955f2f835d277411993c3e44aa6e2f3e144a244547

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page