Metadata-Version: 2.1
Name: n1mem
Version: 0.2.0
Summary: N1Mem memory API client + local memory importers (BYOK, zero hard dependencies)
Author: N1Mem (powered by T1Mem engine)
License: Proprietary
Project-URL: Homepage, https://www.n1mem.com
Keywords: memory,llm,rag,agent,n1mem,t1mem,import,migration
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: >=3.9
Description-Content-Type: text/markdown
Provides-Extra: async
Requires-Dist: httpx>=0.24; extra == "async"

# n1mem · N1Mem 记忆 API Python SDK

> powered by T1Mem engine · BYOK（自带 Key）· 同步**零硬依赖**

给 Agent 一个长期记忆后端：**写入即可召回**，召回基于命中记忆接地；
并且能把你**已有的 Agent 记忆**（身份文件 / 技能库 / 关键文档）一次性迁进来。

## 安装

```bash
pip install n1mem            # 同步版，零硬依赖（仅标准库）
pip install n1mem[async]     # 需要异步客户端时（依赖 httpx）
```

## 10 行代码跑通

```python
from n1mem import N1Mem

m = N1Mem(api_key="tk_xxx")        # 或设环境变量 N1MEM_API_KEY 后不传参

print(m.health())                  # 服务健康 + provider 可用性
m.ingest("我今天换了新工位，在 3 楼靠窗")
print(m.recall("我坐哪儿"))
print(m.list_memories(limit=5))    # 看看存进去了什么
```

## 迁移：把已有 Agent 记忆导进来

`n1mem.importers` 在**你本机**把记忆资产枚举成条目（不上传原始目录结构），
再由 `ingest_batch()` 分批提交。全程离线可复现，不依赖服务端读你的磁盘。

```python
from n1mem import N1Mem
from n1mem.importers import WorkBuddyAdapter

m = N1Mem(api_key="tk_xxx")
ad = WorkBuddyAdapter()                    # 默认导入身份层 + 技能层

plan = ad.plan(ad.enumerate())             # 先看要导什么，再决定导不导
r = m.ingest_batch(plan, source_id=ad.source_id)
print(r["inserted"], r["source_id"])

# 反悔：按批次整体撤销（被删内容会进 tombstone，防止同内容被后续导入"复活"）
m.import_rollback(source_id=r["source_id"])
```

已支持的适配器：

| 适配器 | 来源 | 导入内容 |
|---|---|---|
| `WorkBuddyAdapter` | `~/.workbuddy` | L1 身份（常驻生效）· L2 技能 · L4 项目笔记 |
| `DocAdapter` | 指定目录 | L3 关键文档（PRD / 架构 / 计划 / 复盘 / ADR / 规范），按章节切分 |

> ⚠️ **导入必须走 `ingest_batch()`，不要循环调用 `ingest()`。**
> `ingest()` 只发送 `text`，会丢掉 `tier` / `mtype` / `source_id` / `blob`：
> 结果是身份层从 `resident` 静默降级成普通事实（**常驻身份失效且没有任何报错**），
> 并且因为没有批次 id 而**无法回滚**。

安全：导入前会在**客户端**先扫一遍疑似凭据（阿里云 AK / PyPI token / PEM 私钥 /
带密码的 DSN / 高熵串），命中条目交给服务端后会被**拒绝落库**并回报 `blocked_secrets`
（只给序号与类型，不回显值）。

## API

| 方法 | 对应端点 | 说明 |
|---|---|---|
| `health()` | `GET /health` | 健康与 provider 状态，无需鉴权 |
| `metrics()` | `GET /metrics` | Prometheus 文本指标 |
| `ingest(text, purpose="recall")` | `POST /v1/ingest` | 写入一段记忆（持久化 + 建向量，写入即可召回） |
| `recall(prompt, purpose="recall", mode=None)` | `POST /v1/recall` | 按提示召回；返回 `answer`（已基于命中记忆接地）与 `retrieved`（命中明文） |
| `ingest_batch(items, source_id=…)` | `POST /v1/ingest/batch` | 批量导入，自动切块并汇总；幂等（同批重跑 `inserted=0`） |
| `list_memories(limit=50, …, with_content=False)` | `GET /v1/memories` | 列出本租户记忆；默认只给预览，要全文须显式开 |
| `forget(memory_id)` | `POST /v1/forget` | 删除指定记忆（仅本租户可见，删除后无法召回） |
| `import_rollback(source_id=…)` | `POST /v1/import/rollback` | 按批次 / 来源目录撤销导入 |
| `forget_all(confirm=True)` | `POST /v1/memories/all` | 清空本租户**全部**记忆（须显式 `confirm=True`） |
| `ask / update` | — | **尚未提供**，调用会明确抛 `NotImplementedError` |

异步版 `AsyncN1Mem` 接口完全一致：

```python
from n1mem import AsyncN1Mem

async with AsyncN1Mem(api_key="tk_xxx") as m:
    await m.ingest("…")
    await m.ingest_batch(items)
```

## 错误处理

上游 4xx/5xx 统一抛 `N1MemError`，带 `status` / `message` / `endpoint`：

```python
from n1mem import N1MemError
try:
    m.ingest("x")
except N1MemError as e:
    print(e.status, e.endpoint, e.message)   # 401 /v1/ingest {"detail":"invalid api key"}
```

参数用错（如 `forget_all()` 未确认、`import_rollback()` 两个参数都没给）会抛
`ValueError` / `TypeError` —— **在本地就失败**，不浪费一次注定被拒的网络请求。

## 当前能力边界（诚实清单）

- `ingest()` 持久化到 N1Mem 存储层并生成向量，返回 `{stored, embedded, memory_id}`；写入后即可被召回。
- `recall()` 走关键词 + 向量混合检索（hybrid）；`answer` 基于命中记忆接地生成，未命中会**诚实说明未命中**，不编造。
- 租户隔离由服务端按 API Key 反查 org 保证，**不存在传参越权读取的路径**。
- `ask` / `update` 尚未提供，SDK 会明确抛 `NotImplementedError`，而不是静默返回空。

### 历史勘误

| 版本 | 曾经的错误说法 | 现状 |
|---|---|---|
| ≤ 0.1.2 | README 写「Phase 0 不持久化、recall 取不回」 | 已随 C-2 存储层 + 召回接地修复而过时，勿再引用 |
| ≤ 0.1.5 | 包内 `__version__` 停在 `0.1.2`，与 pyproject 不一致 | 0.2.0 已对齐，并有测试钉住（防再漂移） |

## 与 `t1mem_sdk` 的区别

同目录下有两个包，**不要混用**：

| 包 | 对接对象 | 用途 |
|---|---|---|
| `n1mem` | 线上 C-2 API（`https://api.n1mem.com`） | **对外发布**，本 README 描述的对象 |
| `t1mem_sdk` | 本地 t1mem-core API（`http://127.0.0.1:8080`） | 内部使用，接口为 `/memories`、`/sessions`、`/stats` |

## 相关：MCP Server

想让 Claude / Cursor / OpenClaw 以**工具**方式调用记忆（而不是写代码），
装配套的 [`n1mem-mcp`](https://pypi.org/project/n1mem-mcp/)。
