Metadata-Version: 2.4
Name: memsave
Version: 0.4.0
Summary: Read AI memories without spending credits — local, free hybrid retrieval
Author: memsave contributors
License-Expression: Apache-2.0
Project-URL: Homepage, https://memsave.app.workbuddy.host/?sharecode=5V43SCBI5Q
Project-URL: Documentation, https://memsave.app.workbuddy.host/guide.html?sharecode=5V43SCBI5Q
Project-URL: Funding, https://afdian.com/item/6f246044d49611f1b0a4525400c8052f
Keywords: memory,mcp,rag,bm25,embeddings,llm,context,token
Classifier: Development Status :: 4 - Beta
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Provides-Extra: local
Requires-Dist: onnxruntime>=1.16; extra == "local"
Requires-Dist: numpy>=1.24; extra == "local"
Requires-Dist: tokenizers>=0.15; extra == "local"
Provides-Extra: desktop
Requires-Dist: winrt-runtime>=2.3; sys_platform == "win32" and extra == "desktop"
Requires-Dist: winrt-Windows.Foundation>=2.3; sys_platform == "win32" and extra == "desktop"
Requires-Dist: winrt-Windows.UI.Notifications>=2.3; sys_platform == "win32" and extra == "desktop"
Requires-Dist: winrt-Windows.Data.Xml.Dom>=2.3; sys_platform == "win32" and extra == "desktop"
Provides-Extra: dev
Requires-Dist: pytest>=7.0; extra == "dev"
Dynamic: license-file

# memsave

读回忆不花积分。

大多数"记忆系统"贵在一个地方：它们把整座记忆库灌进上下文，然后让大模型自己去判断哪条相关。上下文是唯一稀缺资源，于是每想一次都要重新付一遍钱。

memsave 反过来做：**检索全程跑在本地 CPU 上，一次模型调用都不发，只把真正命中的那一小段送进上下文。**

## 核心方法

**1. 检索不要过模型**
- `SQLite FTS5` + BM25 做关键词召回。记忆这种命名实体密集的文本（人名、项目名、术语），关键词命中率高得离谱。
- 中文没有空格，所以 CJK 串切成重叠 bigram 再索引 —— 零依赖，不需要分词库。
- 可选 24MB 的 `bge-small-zh-v1.5` ONNX 模型做语义重排，CPU 单条 5–15ms。没装就退化成哈希向量，照样免费。

**2. 三层分级，只加载最薄的一层**

| 层 | 内容 | 体积 | 时机 |
|---|---|---|---|
| L0 | 标题 + 自动关键词 | ~17 token/条 | 常驻系统提示 |
| L1 | 摘要 | ~80 token | 命中后加载 |
| L2 | 原文 | 实际长度 | 显式请求才取 |

L0 大小**恒定**：不管库里 300 条还是 5000 条，索引都是约 1000 token，因为只取 Top-N。

**3. 成本从读取侧搬到写入侧**
原始对话流水账不要存。存蒸馏后的事实：谁 / 做了什么 / 何时 / 决策 / 偏好。写一次花一次钱，之后读一万次都是零。

**4. 文档只在写入时被处理一次**
分词、关键词、向量、token 计数，全部在写入时算好存进表里。检索时唯一的计算是点积。

## 实测数据

300 条记忆库：

| 查询 | 注入 | 全库基线 | 省下 | 耗时 |
|---|---|---|---|---|
| 老陈负责哪个项目 | 140 | 33,442 | 33,302 | 11.8ms |
| Alpha 排期 | 145 | 33,442 | 32,297 | 13.3ms |
| 预算上限 | 115 | 33,442 | 33,327 | 9.0ms |

5000 条记忆库：压缩比 **0.02%**，检索 35–70ms，L0 索引仍是 1051 token。

## 安装

给**用户**装：

```bash
pip install memsave
python -m memsave stats          # 看到统计输出就装好了
```

给**开发者**装（从源码）：

```bash
pip install -e .                 # 零依赖核心，BM25 检索开箱可用
pip install -e ".[local]"        # 可选：加上本地语义模型
python -m memsave embedder install
```

完整上手步骤见 **[`site/guide.html`](site/guide.html)**（使用教程，已上线）。

## 用法

```bash
# 手写摘要（推荐：你比启发式更清楚什么重要）
python -m memsave add "Alpha 项目延期" \
  --summary "Alpha 因 GPU 到货延迟推到 11 月 15 日" \
  --body "2026-10-12 会议决定：上线时间从 10/30 推迟到 11/15…" \
  --kind decision --tags "alpha,排期" --salience 0.9 --pinned

# 丢原始文本进去自动蒸馏，--llm 用你自己的 key 蒸馏得更好
python -m memsave add "会议决定：Alpha 上线时间推迟到 11/15，因 H800 到货延迟" --raw
python -m memsave add "会议决定：Alpha 上线时间推迟到 11/15" --raw --llm

python -m memsave search "Alpha 排期"
python -m memsave index            # 输出 L0，粘进系统提示
python -m memsave stats            # 省了多少 token / 多少钱

# --- 0.3.0 起新增 ---
memsave savings                    # 今日 / 近 N 天 / 累计省了多少
memsave savings --install          # 挂每日定时任务，每天自动播报（含桌面通知）
memsave rules --write              # 写 CLAUDE.md / .cursorrules，让每个新对话自动先查记忆
```

### 项目之间记忆不串味

```bash
export MEMSAVE_SCOPE=alpha项目      # 也可以在 MCP 配置的 env 里设
memsave search "排期"               # 只搜当前空间 + global 空间
memsave add "老陈拍板预算 80 万" --scope global   # 跨项目都能查到
```

### 团队共享记忆（不需要服务器）

```bash
memsave team sync --dir ~/团队共享文件夹 --name 你的名字
memsave team peers --dir ~/团队共享文件夹    # 看看有谁同步过
```

共享文件夹可以是网盘同步目录、NAS 或共享盘。`sync` 先把自己的记忆推上去，
再把别人的按 id 合并进来 —— 重复跑不会重复，也不会覆盖你自己的记忆。
**注意：共享文件夹里是明文记忆，别放在公开可访问的位置。**

## 写入侧蒸馏：唯一该花钱的地方

一条记忆写一次，读无数次。所以蒸馏是**唯一值得调用 LLM 的地方** —— 一次调用，永久摊销。

```bash
export OPENAI_API_KEY=sk-...          # 也支持任何 /chat/completions 兼容端点
export OPENAI_BASE_URL=https://api.deepseek.com/v1
export MEMSAVE_LLM_MODEL=deepseek-chat

python -m memsave distill "会议开了两小时，最后老陈拍板说预算就卡在 80 万" --llm
```

不配 key 也能跑，走启发式：按句切分取首句、按提示词分类（`决定`→decision、`偏好`→preference）、按重要度给权重（偏好 0.85 > 决策 0.80 > 一次性事件 0.40）。

**LLM 挂了就自动退回启发式，绝不丢记忆。**

## 导出（免费版也有）

```bash
python -m memsave export ~/memories.md
python -m memsave export ~/memories.json --format json
python -m memsave import ~/memories.json        # 合并
python -m memsave import ~/memories.json --replace
```

记忆是用户的资产，不是 lock-in 的筹码。**给不了干净导出的产品，用户就不敢往里放真正重要的东西。**

## 授权

```bash
python -m memsave license status
python -m memsave license activate MSV1-XXXX.XXXXXXXX
```

签发给客户：

```bash
export MEMSAVE_LICENSE_SECRET="<换成你自己的长随机串>"
python -m memsave license issue --email a@b.com --tier pro        # 永久
python -m memsave license issue --email a@b.com --tier team --days 365 --seats 20
```

看清楚这一点：**客户端授权是减速带，不是锁。** 任何客户端校验都能被提取密钥绕过。它的作用是让"付费"比"破解"更省事。真正的闸门在服务端 —— 同步、团队共享、更新，这些本来就要联网，校验应该放在那里。别在客户端 DRM 上烧工程时间。

## 收款与自动发码

从"有产品"到"有钱进账"，差的只是一个 HTTP 端点。`billing` 就是那个端点，标准库实现，不需要任何 web 框架。

```bash
export MEMSAVE_LICENSE_SECRET="<你自己的长随机串>"
export STRIPE_WEBHOOK_SECRET="whsec_..."

# SMTP，不配就落盘等待人工发送，绝不丢码
export SMTP_HOST=smtp.resend.com SMTP_PORT=587
export SMTP_USER=apikey SMTP_PASS=re_xxx MAIL_FROM=billing@yourdomain.com

python -m memsave billing test              # 不发网络请求的自检
python -m memsave billing serve --provider stripe --port 8787
python -m memsave billing stats             # 订单 / 已发送 / 待发送
python -m memsave billing pending           # 列出待人工发送的授权码
python -m memsave billing retry             # 邮件失败后补发
python -m memsave billing reconcile --provider afdian --days 3   # 对账补漏单
```

`reconcile` 是"不买服务器"这条路的兜底：webhook 是推送，你关机时收不到。
它反过来主动去平台拉订单，只补发本地账本里没有的（走同一套三道闸和幂等账本，
重复跑不会重复发码）。详见 **[`docs/hosting.md`](docs/hosting.md)**。

Stripe 后台配 `checkout.session.completed`，Lemon Squeezy 配 `order_created`。创建 Checkout Session 时带上 `metadata: {"tier": "pro", "seats": 1}`（`days` 留空即永久）。

放 Caddy 后面：

```
pay.yourdomain.com { reverse_proxy 127.0.0.1:8787 }
```

**默认只监听 127.0.0.1。** `--public` 是给容器用的，不是给笔记本用的。

### 三条不可妥协的规则

1. **验签必须开。** 不验签的 webhook 就是一台任何人都能 POST 的免费发码机。签名密钥每次请求都重新读取，轮换不用重启。
2. **幂等由本地账本保证，不信 provider。** Stripe 收到任何非 2xx 都会重试，而且重试不止一次。没有幂等，客户会收到三把不同的钥匙，然后不知道该用哪把。
3. **授权码绝不丢。** 邮件发不出去就落盘到 `~/.memsave/pending-delivery/`，然后 `billing pending` 人工补发。静默丢码是一张无法挽回的工单。

## 运维

```bash
memsave ops doctor          # 体检：密钥 / 数据库完整性 / 磁盘 / 邮件积压
memsave ops backup          # 备份（自动校验可读性）
memsave ops restore <file>  # 恢复（先把当前状态另存，绝不直接覆盖）
memsave ops prune --keep 14 # 清理旧备份
memsave ops new-secret      # 生成密钥
memsave ops rotate-key <新密钥>   # 轮换并重新签发全部已售授权码
memsave ops audit .         # 扫描硬编码密钥，发布前必做
```

服务器部署：

```bash
sudo ./deploy/install.sh    # 建用户、装服务、配 cron 备份、给 Caddy 提示
```

完整运维流程、故障排查表、应急预案见 **[`docs/operations.md`](docs/operations.md)**。

一句话说清运维的本质：**每天能回答"它健康吗"，以及随时能把数据拿回来。** 其余都是细节。

## 文档

| 文档 | 内容 |
|---|---|
| [`README.md`](README.md) | 安装、用法、原理、收款、运维 |
| [`site/guide.html`](site/guide.html) | **用户使用教程**（已上线，含各客户端配置路径与排错） |
| [`docs/pricing.md`](docs/pricing.md) | 付费模式设计、上线清单、护城河与风险 |
| [`docs/operations.md`](docs/operations.md) | 运维手册、故障排查、应急预案 |
| [`docs/marketing.md`](docs/marketing.md) | 推广骨架与口径 |
| [`promo/README.md`](promo/README.md) | **推广执行包**：口径卡 + 发布追踪表（先看这个） |
| [`promo/06-video.md`](promo/06-video.md) | 推广视频：分镜、口播稿、各平台配文 |
| [`docs/hosting.md`](docs/hosting.md) | **托管方案**：为什么现在不用买服务器 |
| [`docs/pypi-release.md`](docs/pypi-release.md) | **PyPI 发布**：注册 → token → 上传（工程已就绪，只差你 3 步） |
| [`CHANGELOG.md`](CHANGELOG.md) | 版本记录 |

## 接进任意 AI（MCP）

MCP 是"让所有 AI 都能调用"的答案：Claude Desktop、Cursor、Windsurf、Cline、WorkBuddy 都支持。

```json
{
  "mcpServers": {
    "memsave": {
      "command": "python",
      "args": ["-m", "memsave", "serve"],
      "env": { "MEMSAVE_HOME": "C:/Users/you/.memsave" }
    }
  }
}
```

暴露 9 个工具：`memory_index` / `memory_search` / `memory_get` / `memory_add`（支持 `raw` 自动蒸馏）/ `memory_update` / `memory_forget` / `memory_export` / `memory_license` / `memory_stats`。

每次 `memory_search` 的返回都在末尾附一行统计：

```
本轮注入 140 token，全库基线 33442 token，省下 33302 token。
累计 6 次调用共省下 199,858 token (≈ $0.60)
```

**这个数字就是产品本身。** 用户不需要你解释技术原理，他只需要看到"本次对话省了 3 万 token"。当省下的钱超过定价时，付费没有任何心理阻力。

## 建议的系统提示

```
每次会话开始时调用 memory_index 一次。
需要细节时调用 memory_search，默认只取摘要。
禁止一次性加载全部记忆原文。
新学到的用户偏好、决策、人物关系，用 memory_add 写入，
写入前先蒸馏成事实，不要存原始对话。
```

## 商业化设计

见 `docs/pricing.md`。要点：**BYOK 路径边际成本≈0，所以终身买断是净赚**；纯订阅在这个人群里转化率很差，因为 AI 重度用户已经订阅疲劳了。

## 已知边界

- 语义检索只在装了 `onnxruntime` 后启用，否则退化为 BM25 + 哈希向量。功能不残，语义质量下降。
- 自动关键词用的是位置 + 词频启发式，不是真 IDF。要真 IDF 就得每次写入全量重算，不值得。
- **标签里会出现 `库最`、`终选` 这种跨词碎片** —— 这是 bigram 索引无分词的固有痕迹。检索走完整 bigram 索引不受影响，标签只是给人扫一眼用的。要干净的中文标签就得引 jieba，为了这个不值当。
- 单机单文件。多机同步和团队共享是商业版的事，也是服务端校验应该待的地方。
- `estimate_tokens` 是启发式估算，误差约 15%。它用于展示节省量，**不用于计费**。
- 客户端授权可被绕过，这是设计上的取舍，不是 bug。见上文。
