面向 AI 智能体的长期记忆。
WOS 是一个记忆 API。您只需存储一次用户的记忆,之后每次查询只召回其中相关的部分,并将其传入模型的提示词。
检索完全基于语义,不使用关键词或 BM25 匹配,因此各语言的召回质量完全一致。无论您存储了多少内容,每次查询都只返回一小段大小受限的上下文,并且绝不会在您存储的记忆上运行任何模型。
核心操作
store- 为用户保存一条记忆。recall- 获取与查询相关的记忆。这是最主要的调用。search- 对已存储记忆进行原始语义搜索。supersede- 更新或替换已过时的记忆。forget- 删除单条记忆或整个用户的数据(GDPR)。
三个模型,一脉相承。
WOS 的模型以人类历史上保存知识的载体命名 - Tablet(石板)、Scroll(卷轴)、Codex(册页)。石板、卷轴、装订成册的书:每一代都能为您的智能体做得更多。
Tablet
已上线一种精简、快速、低成本的记忆写入与召回方式,是所有模型共同的基础。
Scroll
已上线加入一个语言模型来更仔细地解读您的问题,并带回更完整的上下文,让分散的线索汇聚成整体,而不是总缺一块。
Codex
即将推出自动翻到正确的一页 - 在每个时刻自行选择所需的记忆与工具,并且越用越敏锐。
Tablet 1 的完整基准测试报告见基准测试页面。
付给我们 $2,在您的 LLM 上省下数倍的开销。
WOS 每次查询只向您的 LLM 提供约 1,200 个 token(一段大小受限且高度相关的切片),而不是把完整历史塞进每个提示词。两者差距巨大,且随历史增长而不断扩大。
在 WOS 上每花 $1,就能在 LLM 上节省 ~$98。历史越大、模型越贵 → 回报越高。
节省从何而来
- 不使用 WOS 时,您需要把完整历史塞进每个提示词:按 GPT-4o 输入价格计算,每次查询
100K tokens × $2.50/1M = $0.25(Opus 级模型约为其 2 倍)。 - 使用 WOS 时,您只需一次性写入(
$2/1M),之后每次查询只是一次极小的检索($3/1M × 1,200),加上您的 LLM 只需读取约 1,200 个 token。 - 您的 LLM 读取的 token 越少,您付的钱就越少,而 WOS 会让这个数字在记忆增长时保持平稳。
每种语言,同样的准确率。
检索是纯语义的 - 只用向量嵌入,完全不做关键词或 BM25 匹配。因此无论您的用户使用日语、中文、西班牙语还是英语,召回质量都完全相同。
BM25 这类词法匹配是针对特定语言的形态、分词和书写系统调校的。在多语言存储中,这意味着检索质量会因语言而异。WOS 完全不使用词法匹配,因此每种语言都走同一条路径。
一个存储库,同时容纳三种语言
您无需为存储库指定语言 - 可以自由混用。下例中,同一位用户的记忆同时包含日语、英语和西班牙语,而无论提问用什么语言,每个问题都能找到正确的记忆。这是对线上 API 的一次真实交互:
# one user, three languages stored together mem.add("彼女はコーヒーより紅茶が好き", user_id="alice") # Japanese mem.add("she works at a design studio in Brooklyn", user_id="alice") # English mem.add("A ella le encanta hacer senderismo los sábados", user_id="alice") # Spanish
"¿Qué bebe ella?" -> 彼女はコーヒーより紅茶が好き "what does she do on weekends?" -> A ella le encanta hacer senderismo los sábados "彼女の仕事は?" -> she works at a design studio in Brooklyn
没有翻译步骤,没有语言检测,没有按语言的配置。记忆与问题按语义而非语言归位 - 只要语义匹配,语言无关紧要。
我们为什么刻意禁用关键词
BM25 之类的词法打分会让某些语言的检索得到比其他语言更多的强化,当一个存储库容纳多种语言时就会造成偏差。因此我们把它从引擎中彻底移除,并在代码评审中强制执行这一规则:只要路径中存在任何词法打分,召回质量就会因语言而异。
没有模型会读取您的记忆。
存储是逐字原样的,引擎通过向量嵌入进行搜索 - 便宜、快速、确定性。我们绝不会在您存储的记忆上运行模型。Tablet 完全不使用模型;Scroll 和 Codex 在引擎之外加入了一个模型以获得更强的效果,但它只会看到您的查询,绝不会看到您存储的内容。
- 确定性引擎。同一查询每次都返回相同的记忆 - 这正是我们基准测试的方差只来自阅读模型的原因。
- 规模化下依然便宜。存储和检索没有生成成本,因此随着记忆增长,您的账单只与存储量相关,而不是模型用量。
您的原话,一字不改
一种常见设计是在写入时运行语言模型,从文本中提取并改写“事实”。这种设计要付出三重代价:每次写入的生成成本、额外的延迟,以及存下来的是模型的转述而非原话。WOS 做了相反的取舍 - 它原样存储所说的话,让您的 LLM 在读取时拿着原文进行解读。
90.7%,实测且可复现。
在 LongMemEval-S 上取得 90.7%,为 5 次独立运行的平均值(σ 0.5%,无任何挑选),由该基准的官方 GPT-4o 评判模型打分。
在同一基准上,分数会随评分协议大幅波动 - 评判模型、提示词,以及检索层被允许做什么。我们按公开发布的原样使用该基准的官方第三方 GPT-4o 评判模型,不为迎合测试做任何改动,并公开评分代码和阅读器提示词,任何人都可以精确复现这个 90.7%。
评测协议,一表看全
| 项目 | 我们的做法 |
|---|---|
| 数据集 | LongMemEval-S(清洗版),每个问题约 240K token 的历史 |
| 评判 | 该基准的官方 GPT-4o 评判模型 - 第三方,而非我们自己 |
| 运行次数 | 5 次独立运行,公布每一次的分数,报告均值(σ 0.5%) |
| 阅读器 | 固定的阅读模型与提示词,原文公开 |
诚实的保障:第三方评判、原样公开的阅读器提示词、纯语义检索,以及公布每一次运行的结果 - 而不是只报最好的一次。检索引擎是确定性的 - 再跑一次,返回的记忆完全相同。
我们攀登更难的基准
我们只在尚未攻克的最难的标准基准上测试 - 这个数字是所有 WOS 模型的最高水位线,每当更强的模型发布就会刷新。突破 94%,我们就毕业,转向更难的基准。
每个模型两档 token 费率,
外加每次请求 $0.0001。
按每百万 token 计费,外加每次请求固定 $0.0001,按用量付费。没有订阅,没有存储租金,没有记忆上限。只有当您的智能体写入或读取时才付费 - 绝不为它记住的内容付费。
| 模型 | 输入 / 1M | 输出 / 1M | |
|---|---|---|---|
| Tablet 1 | $2 | $3 | 已上线 |
| Scroll 1 | $4 | $8 | 已上线 |
| Codex 1 | - | - | 待定 |
- 每次请求 $0.0001。在 token 用量之外,每次 API 调用收取固定费用。
- 存储免费。写入只付一次费,保存不花一分钱。没有条数限制,没有保留期限。
- 我们负责存储,但绝不用它训练、使用或查看。您智能体的记忆属于您 - 我们只负责组织它,让您能够检索。
- Tablet 为什么这么便宜:它的引擎不运行任何模型,所以我们的成本只有向量嵌入和磁盘 - 而不是 GPU。Scroll 和 Codex 加入了模型,这正是它们更高价格所覆盖的部分。
三次调用:存储、召回、回答。
一个 API。recall() 调用在一次往返中返回短期记忆、长期记忆和周边上下文,可直接放入您的提示词。
存储
add() 保存事实与对话:用户的话、助手自己的话(speaker "me"),或某个具名者的话。写入时完成嵌入,不调用 LLM。
召回
recall() 在一次调用中返回短期 + 长期 + 上下文 - 一段大小固定、有界的上下文。
回答
把这段有界的上下文交给您的 LLM - 任何提供商,用您自己的密钥。
from wontopos import Client mem = Client(api_key="wos-...") mem.add("she prefers tea over coffee", user_id="alice") mem.add("I suggested the jasmine tea", user_id="alice", speaker="me") # its own words # one call: short + long + context ctx = mem.recall("what does alice drink?", user_id="alice")
记忆带有说话者。默认是用户的话,speaker "me" 存储助手自己说的话,而像 "Bob" 这样的名字会记住是用户身边的谁说的,让回忆可以按人来回答。
5 分钟完成您的第一次召回。
一个密钥、一行安装命令、三次调用 - 您的智能体就有了记忆。本页每个代码片段都实际运行过;响应原样展示。
获取 API 密钥
在控制台创建。以 wos-live- 开头的 155 字符密钥只显示一次。请保存在环境变量中 - 绝不要写进代码。
安装
pip install wontopos # Python npm install wontopos # TypeScript / JavaScript cargo add wontopos # Rust # curl - nothing to install, just set WOS_API_KEY
创建存储库,然后存储与召回
存储库(store)就是您读写所依据的 user_id。存储库是显式的:先创建(下面的调用),再在其下存储和召回。存储 - 写入时即完成向量嵌入,不调用 LLM。召回 - 一次往返返回短期 + 长期 + 上下文。
from wontopos import Client
mem = Client(api_key="wos-live-...", user_id="alice") # set the store once
mem.create_store() # create it (stores are explicit)
mem.add("she prefers tea over coffee") # no user_id needed
# one call → short-term + long-term + context
ctx = mem.recall("what does alice drink?"){"user_id": "alice", "status": "created"}{"id": "576700aa-f0e0-4c26-99a0-10e2d5b0d624", "status": "stored (1 chunks)"}user_id 传给客户端后,每个调用都会使用它 - 无需重复;给单个调用传入 user_id 即可覆盖。存储库是显式的:向不存在的存储库写入或从中召回会返回 404 - 请先创建。每个账户自带一个 default 存储库,因此完全不传 user_id 的零配置路径也能直接工作。列出与管理存储库见存储库。recall() 返回四个块 - short_term(最近的对话轮次)、long_term(相关记忆)、context(最佳匹配的前后文),以及一条告诉 LLM 如何使用它们的 instruction。整体放入您的提示词即可。
存储库 - 创建、列出、删除。
存储库(store)就是您读写所依据的 user_id - 每个最终用户、智能体或主题一个隔离的记忆空间。存储库是显式的:必须先创建,再向其中存储或从中召回,否则调用返回 404。每个账户自带一个 default 存储库,因此无需创建即可开始。
mem.create_store("alice") # create (idempotent)
mem.list_stores() # [{"user_id","created_at"}, ...]
mem.delete_store("alice") # delete the store + all its memories{ "user_id": "alice", "status": "created" } // "exists" if it already did{ "collections": [
{ "user_id": "default", "created_at": "2026-06-26T02:23:14Z" },
{ "user_id": "alice", "created_at": "2026-06-26T02:24:01Z" }
], "count": 2 }{ "error": { "type": "not_found_error",
"message": "Store 'ghost' does not exist. Create it first with
POST /api/v1/memory/collection {\"user_id\":\"ghost\"}, then store or recall." } }"alice"、"user_42"),使每个人的记忆彼此独立;个人智能体则用单个 default 存储库即可。您也可以在控制台(Memory ids → Issue)中创建和浏览存储库,无需写代码。删除存储库是永久性的 - 其下所有记忆都会被删除。重复的召回,只需十分之一的价格。
按请求选择开启后,WOS 会以查询文本为键缓存检索结果,规则与 LLM 提示词缓存相同的前缀方式。缓存有效期间,重复或续写的查询会复用上一次的结果,缓存部分按正常 token 单价的 10% 计费。
一段对话,三个回合
当代理持续与记忆对话时,实际发生的事情如下。每个回合都把到目前为止的对话作为查询发送,并开启 cache_control。
整个查询被检索并缓存:输入按 2 倍计费(TTL 5 分钟)。
只有 Bob 的句子会被嵌入和检索。旧的部分按 0.1 倍,新句子按 2 倍,缓存现在以它结尾。
完全不调用引擎。全部按 0.1 倍:这就是 90% 的折扣。
费率
| 操作 | Token 计费 | 含义 |
|---|---|---|
| 缓存写入 - TTL 5 分钟 | 2× | 第一次请求。结果保留 5 分钟,每次读取都会顺延有效期。 |
| 缓存写入 - TTL 1 小时 | 3× | 第一次请求,保留整整一小时。 |
| 缓存读取 - 命中或前缀命中 | 0.1× | 写入之后的每次请求:缓存部分按正常 token 单价的十分之一计费。 |
能省多少
一个具体的例子:你的代理把一段 3,000 token 的对话作为查询发送,并在五分钟内重复或续写 10 次。没有缓存时,是按全价计费的 30,000 输入 token。开启 5 分钟缓存后,第一次写入 6,000(2 倍)加上九次缓存读取约 2,700,共计 8,700 计费 token,节省 71%。对话越长,省得越多。
前缀规则
匹配以查询的开头为准。如果开头保持不变、只是在末尾追加了新文本,缓存部分会被复用,只检索新的部分。如果缓存文本结束之前的任何内容发生变化,则什么都无法复用。
cached [ A B C D E F G ] ○ [ A B C D E F G ] E ✗ [ B C D E F G ] E
○ 命中 - 开头不变,只有 E 是新的部分
✗ 未命中 - 开头变了,整个查询将重新检索并重新缓存
要记住的三条规则
- 续写会连同新尾部一起重新缓存。 在 [A B C D E F G] + E 之后,缓存以 E 结尾:尾部按写入费率计费一次,下一轮可以把 A 到 E 的全部内容再次作为前缀匹配。
- 每个请求只有一个连续前缀。 一个查询不能拆成两段缓存,只有开头才能匹配。
- 写入会立即失效缓存。 任何 store、store-turn、bulk-store、forget、supersede 或删除存储的操作都会丢弃该存储的缓存,缓存的答案绝不会返回过时的记忆。
如何开启
hits = mem.search(
"...the conversation so far...", user_id="alice",
cache_control={"ttl": "5m"}, # or "1h"
){ "memories": [ ... ],
"cache": { "status": "hit", // "write" | "hit" | "extend"
"ttl": "5m",
"cache_read_input_tokens": 412,
"cache_creation_input_tokens": 0 } }这些功能并不依赖 SDK。缓存只是一次 HTTP 调用上的一个字段,因此任何编程语言都能使用。curl 标签页就是通用配方,Python、TypeScript 和 Rust SDK 只是对同一个调用的便捷封装。
知道是谁说的记忆。
人的记忆以人为单位:Bob 承诺了什么,你说过要做什么。给每条记忆打上说话者标签,你的智能体也能做到,在所有 Tablet 和 Scroll 模型上。
一个团队,三条记忆
一个存储库能让多个声音互不混淆。先注册一个人,每句话按说话者保存,之后按人提问。
存储库现在认识 Bob 了。50 人上限只在注册这里计数,store 调用绝不会返回上限错误。
这条记忆现在属于 Bob:每次搜索返回它时都会这样标注。
自己说的话也会被记住,而且 "me" 永远不占说话者上限。
只返回 Bob 的话。一个人的话绝不会变成另一个人的。
只需记住三条规则
- "me" 是助手本身。不注册、不计数。保留字且仅小写有效:
speaker: "Me"或"ME"不会被隐式转换,而是返回400 invalid_request_error。 - 上限在注册时计数:每库起步 50 人。超限注册返回
400 invalid_request_error,错误体带speaker_limit: 50。用未注册的名字 store 同样返回400且不保存任何内容。用未注册的名字过滤搜索返回404 not_found_error。请按状态码和字段分支,不要解析消息文本;上限将逐步提高。 - 标签存在于每一次读取。搜索结果、recall 的长期上下文、engram 结果都带有说话者,模型始终知道手里的话是谁的。搜索时传 speaker 就只返回那个人的话;supersede 保留说话者,forget 一并删除。
- 名字是 Unicode,任何语言都可以。さくら、Иван、하늘 都是有效的说话者,归属行为在所有语言中一致。匹配在去空格和 Unicode 规范化后完全一致,因此
Bob和bob是两个人。名字上限 80 个字符。
# POST /speakers past the limit { "type": "error", "error": { "type": "invalid_request_error", "message": "This store already has 50 registered speakers, ...", "speaker_limit": 50 } } # store with an unregistered name → 400, nothing stored { "type": "error", "error": { "type": "invalid_request_error", "message": "speaker 'Bob' is not registered in this store. Register it first: ...", "speaker": "Bob" } } # search filtered by an unregistered name → 404 { "type": "error", "error": { "type": "not_found_error", "message": "speaker 'Bob' is not registered in this store.", "speaker": "Bob" } }
两点范围说明。speaker 附加在 add / store 上:add_turn 整体记住一次交流,按人标注和过滤来自显式带 speaker 保存的记忆。另外,会话段落(expand)是多条记忆的合成,因此不带标签;speaker 过滤总是返回原子级、带标签的记忆。 另外,完全相同的内容会去重到第一条记忆:这样的 store 返回 status "duplicate" 和明确的 note,且不附加说话者。
我们用最严格的方式测试它:存入正文里完全没有名字的记忆,再按人回忆。归属来自说话者记录而不是文字匹配,所以在任何语言里表现一致。
如何使用
mem.add_speaker("Bob", user_id="alice") # once per person; "me" needs no registration
mem.add("Bob said the deadline moved to Tuesday", user_id="alice", speaker="Bob")
mem.add("I promised the summary by Friday", user_id="alice", speaker="me")
hits = mem.search("what did Bob say about the deadline?", user_id="alice", speaker="Bob")
mem.list_speakers(user_id="alice")
mem.remove_speaker("Bob", user_id="alice") # memories stay, the tag goes{ "memories": [
{ "content": "Bob said the deadline moved to Tuesday",
"speaker": "Bob", ... } ] }{ "user_id": "alice",
"speakers": [ { "speaker": "Bob", "memories": 2, "created_at": "2026-07-10T04:20:39Z" } ],
"count": 1, "limit": 50 }列表显示存储库认识的人及各自的记忆数与上限。移除只删除注册:那个人的记忆保留,只有名字标签消失。
MCP - 给 AI 工具的记忆
WOS 的核心是 API 和 SDK。MCP 服务器是其上的附加功能:把同一份记忆,插进不是你构建的工具里 - Claude Code、Claude Desktop、Cursor。
一行安装,智能体就获得 9 个记忆工具并自行使用。记忆在你的账户里,一个工具写入的,其他所有工具都能召回 - 包括你用 SDK 构建的智能体。
用它能做什么
- 记得你项目的 Claude Code。决策、bug 修复、偏好 - 下个会话直接召回,无需重新解释。
- 在 ChatGPT 开始,在 Claude 继续。同一个存储、同一份记忆 - 对话跨工具延续,而不是从头再来。
- 你自己的智能体也在同一记忆里。Claude Code 学到的,SDK 智能体能召回;智能体存下的,Claude Code 也能召回。
可用于 Claude Code、Claude Desktop、Cursor、Windsurf 及任何 MCP 宿主。ChatGPT 通过 Actions 加 OpenAPI 规范接入同一份记忆。
安装
claude mcp add wontopos --env WONTOPOS_API_KEY=wos-live-... -- npx -y wontopos-mcp智能体获得九个工具 - recall · remember · search · update · forget · list_memories · engram · stats · create_store - 每个描述都写明何时使用,它自行判断。
OpenAPI 规范
API 的完整机器可读地图 - 所有端点、请求、响应和错误。
OpenAPI 是用机器可读文件描述 HTTP API 的行业标准格式。
https://api.wontopos.com/openapi.json用它能做什么
Postman: File → Import → 粘贴 URL,16 个端点变成可点击的集合。ChatGPT: 创建 GPT,添加 Action,粘贴同一 URL。代码生成: openapi-generator -i .../openapi.json -g go 生成我们未提供语言的客户端。
导入 Postman、为我们未提供的语言生成客户端、接入 ChatGPT Actions、或在 CI 里做契约校验。测试将它钉在真实路由上,不会漂移。
llms.txt
把整个 API 装进 AI 能读的一页文本。
llms.txt 是一种网络惯例:放在站点根目录的一页纯文本,把 AI 需要了解的产品信息全部讲清。
https://wontopos.com/llms.txt放进 IDE 或编码智能体,它就知道怎么在 WOS 上构建 - 认证、端点、模式、错误。每次发布同步更新。
与 OpenAPI 规范同样的事实,不同的读者:规范是给工具的精确结构,这个文件是 AI(或人)一口气读完的散文。两者都随每次发布更新。
在每个 AI 工具里的记忆
一条命令,Claude Code、Claude Desktop、Cursor 等任何 MCP 宿主就拥有由你的 WOS 账户支撑的长期记忆。无需集成代码,智能体获得 9 个记忆工具并自行决定何时使用。
安装
Claude Code 只需一行(先在控制台创建密钥):
claude mcp add wontopos --env WONTOPOS_API_KEY=wos-live-... -- npx -y wontopos-mcp
# pick which store it remembers into (optional): add --env WONTOPOS_USER_ID=my-projectAdd to Cursor → · Add to VS Code →
可选 env:WONTOPOS_USER_ID 指定默认存储,WONTOPOS_MODEL 指定引擎,WONTOPOS_BASE_URL 指定自托管部署。 WONTOPOS_READ_ONLY=1 切换为只读(仅召回/搜索/查看)。
共享同一个存储之前
- 使用专用密钥。密钥携带其工作区,为 MCP 单独创建的密钥从根上限定了所连工具能触及的范围 - 随时可在控制台轮换,不影响应用的密钥。
- 只读模式。
WONTOPOS_READ_ONLY=1时完全不注册写入工具:智能体只能召回、搜索、列出记忆、运行 engram、读取统计,无法存储、更新或删除。适合只应查阅记忆、而非拥有记忆的智能体。 - 保持工具执行确认开启。MCP 宿主默认在运行工具前询问 - 尤其是
forget,因为删除对该存储上的所有工具生效。 - 凡是存入的,持有密钥的每个工具都能召回。绝不要把机密 - API 密钥、密码 - 存成记忆。
- 召回的记忆是数据,不是指令。工具描述会明确告诉智能体这一点。即便如此,也不要把不可信的第三方文本存进自主智能体会遵循的存储里。
- 删除同样是共享的。一个工具里的 forget 或 delete_all,对所有工具都生效。
- "me" 指的是写入该存储的智能体自己。多个智能体共享一个存储时,"me" 的声音会混在一起。想分开身份,就给每个智能体单独的存储(
WONTOPOS_USER_ID)。 - 一个账户买单。所有接入的工具都消耗同一份余额和速率额度。
然后直接对话
智能体调用 remember 工具。持久保存在你的存储里,会话结束也不会丢失。
新会话没有任何聊天记录。智能体调用 recall,凭记忆回答:周五。
可以这样说
- "这个仓库用 pnpm,记住" →
remember存下来,下个会话就已经知道。 - "上周我们定的错误格式是什么?" →
recall把那个决定拉回上下文。 - "其实截止日期改到周五了" → 智能体发现这与它召回的记忆矛盾,调用
update就地更正那条记忆。 - "记错了,删掉" → 智能体找到记忆 id 并调用
forget,宿主会先要求确认。 - "你记得我哪些事?" →
list_memories翻遍所有已存记忆,智能体就能回答或整理。
不需要特殊句式 - 以上都是普通句子,不是命令。智能体读取各工具的描述,自行选择。
九个工具
recall- 一次调用取得上下文:最近对话 + 相关长期记忆。工具描述里写明:涉及过去上下文时先调用它。remember- 保存持久的事实或决定。speaker: "me"标记智能体自己的话;已注册的名字标记说话人。search- 语义搜索,可选按人过滤(speaker)。forget- 按 id 删除一条记忆。speakers- 注册、查看或注销存储记住的人。create_store- 存储是显式的:每个最终用户、项目或智能体一个。
SDK 还是 MCP?
- SDK 放进你自己写的应用里。什么时候存、取什么,由你的代码精确决定 - 确定性、有类型、有版本。做产品就用 SDK。
- MCP 插进不是你写的 AI 工具里。何时使用记忆由智能体根据工具描述判断 - 零代码。适合 Claude Code、Claude Desktop、Cursor,或给现成的助手装上记忆。
底层是同一个 API、同一批存储 - 用 SDK 构建的应用和用 MCP 连接的 Claude Code 会话共享同一份记忆。按场景选择,不是二选一。
一份记忆,贯穿所有工具
记忆属于账户,而不是工具。从 ChatGPT(Actions + OpenAPI 规范)写入的存储,在 Claude Code 和你自己的智能体里同样能召回,反之亦然。在一个工具里开始的对话,在另一个工具里继续。
而且因为是同一个存储,你可以在 Claude Code 上用着,再回到自己的智能体继续对话:同一密钥、同一存储的 SDK 智能体能召回 Claude Code 刚学到的一切;你的智能体存下的,下个会话的 Claude Code 也能召回。
npx wontopos-mcp):密钥只留在你的环境里,不经过任何第三方服务器。它封装了 TypeScript SDK,自动重试、拒绝重定向、密钥脱敏原样生效。Claude Code
旗舰路径:终端里一条命令,每个会话都带着记忆开始。
- 在控制台创建 API 密钥。密钥携带其工作区,一把密钥 = 一个记忆空间。
- 注册服务器。加
--scope user所有项目可用;不加则只有当前项目可见。 - 验证:在 Claude Code 里运行
/mcp,应能看到wontopos和 9 个工具。 - 自动化技巧:在
CLAUDE.md里写一行"涉及过去上下文时先调用 wontopos recall",每个会话就会不用吩咐地带着记忆开始。
claude mcp add wontopos --scope user \
--env WONTOPOS_API_KEY=wos-live-... -- npx -y wontopos-mcp
# pick a store (optional): add --env WONTOPOS_USER_ID=my-projectClaude Desktop
把下面的块加进 claude_desktop_config.json(设置 → 开发者 → Edit Config),重启应用,九个工具就会出现。注意:网页版和移动版 claude.ai 需要远程 MCP 服务器,WOS 尚未提供 - 桌面应用是受支持的路径。
# claude_desktop_config.json
{ "mcpServers": {
"wontopos": {
"command": "npx",
"args": ["-y", "wontopos-mcp"],
"env": { "WONTOPOS_API_KEY": "wos-live-...",
"WONTOPOS_USER_ID": "my-project" }
} } }Cursor
把下面的块加进 ~/.cursor/mcp.json,或点一键按钮,然后重启 Cursor。智能体就会拿到九个工具。
# ~/.cursor/mcp.json
{ "mcpServers": {
"wontopos": {
"command": "npx",
"args": ["-y", "wontopos-mcp"],
"env": { "WONTOPOS_API_KEY": "wos-live-...",
"WONTOPOS_USER_ID": "my-project" }
} } }VS Code
VS Code(Copilot 智能体模式)从项目的 .vscode/mcp.json 读取 MCP 服务器:加入下面的块,或点一键按钮。
# .vscode/mcp.json
{ "servers": {
"wontopos": {
"command": "npx",
"args": ["-y", "wontopos-mcp"],
"env": { "WONTOPOS_API_KEY": "wos-live-...",
"WONTOPOS_USER_ID": "my-project" }
} } }Windsurf
Windsurf(Cascade)读取 ~/.codeium/windsurf/mcp_config.json:加入下面的块并重新加载,同样的九个工具就会出现。
# ~/.codeium/windsurf/mcp_config.json
{ "mcpServers": {
"wontopos": {
"command": "npx",
"args": ["-y", "wontopos-mcp"],
"env": { "WONTOPOS_API_KEY": "wos-live-...",
"WONTOPOS_USER_ID": "my-project" }
} } }ChatGPT
ChatGPT 的 MCP 连接器只接受远程服务器,所以目前受支持的路径是自定义 GPT 的 Action:创建 GPT,添加 Action,粘贴下面的 OpenAPI 规范 URL,并把 API 密钥设为认证头。这个 GPT 就会调用与其他工具相同的记忆。
# GPT → Configure → Actions → Import from URL
https://api.wontopos.com/openapi.json
# Authentication: API Key · Header name: X-API-Key同一个存储、同一份记忆:ChatGPT 通过 Action 存的,Claude Code 通过 MCP 召回,反之亦然。
Gemini CLI
Gemini CLI 从 ~/.gemini/settings.json 读取 MCP 服务器:加入下面的块并重启 CLI,同样的九个工具也会出现。
# ~/.gemini/settings.json
{ "mcpServers": {
"wontopos": {
"command": "npx",
"args": ["-y", "wontopos-mcp"],
"env": { "WONTOPOS_API_KEY": "wos-live-...",
"WONTOPOS_USER_ID": "my-project" }
} } }Python - 全部方法,三大类。
写入、读取、删除。以下每个示例都于 2026-07-10 针对线上 API 实际运行;响应为原样展示。
pip install wontopos
from wontopos import Client mem = Client(api_key="wos-live-...") # or read from an env var
选择模型
API 密钥决定用哪份记忆(您的账户);模型决定用哪个引擎来读取。所有模型共享同一份记忆,因此可以用一个模型存储、用另一个召回。在客户端上设置默认值;给单个调用传 model= 即可覆盖。
mem = Client(api_key="wos-live-...", model="tablet-1") # default engine mem.recall("...", user_id="alice") # tablet-1 mem.recall("...", user_id="alice", model="scroll-1") # or pick a model per call
list_models ✓ live-tested
模型目录 - 可传给 model 的 id 以及各自是否已上线。memory: "shared" 的模型读取同一存储;"isolated" 则各自独立。无需 API 密钥。
mem.list_models()[{"id": "tablet-1", "name": "Tablet 1", "available": true, "memory": "shared"},
{"id": "scroll-1", "name": "Scroll 1", "available": true, "memory": "shared"},
{"id": "scroll-1.2", "name": "Scroll 1.2", "available": true, "memory": "shared"}]ping ✓ live-tested
用一行确认连接以及 API 密钥是否有效。
mem.ping() # True, or raises AuthenticationError / PaymentRequiredError
上面的目录始终反映当前可用的模型 - 传入其他任何 id 都会得到明确的错误。新模型发布后会自动出现在其中。
写入
add ✓ live-tested
存储一条记忆。写入时即完成向量嵌入 - 不调用 LLM,您只为嵌入付费。
mem.add("she prefers tea over coffee", user_id="alice") mem.add("I promised the summary by Friday", user_id="alice", speaker="me") # its own words - no registration needed
{"id": "576700aa-f0e0-4c26-99a0-10e2d5b0d624", "status": "stored (1 chunks)"}add_turn ✓ live-tested
把一轮对话(用户 + 助手)同时写入短期与长期记忆。
mem.add_turn("hi", "hello!", user_id="alice")
{"status": "ok"}speaker ✓ live-tested
每条记忆都可以记录说话者。先注册一个人,之后把名字作为 speaker 传入;"me"(助手自己的话)无需注册。搜索也接受 speaker,可以只取某个人说过的话。
mem.add_speaker("Bob", user_id="alice") # once per person; "me" needs no registration mem.add("I promised to send the report on Friday", user_id="alice", speaker="me") mem.add("Bob said the deadline moved to Tuesday", user_id="alice", speaker="Bob") mem.search("what did Bob say about deadlines?", user_id="alice", speaker="Bob")
{"memories": [{"content": "Bob said the deadline moved to Tuesday", "speaker": "Bob", ...}]}add_bulk ✓ live-tested
回填一大段文本。在服务端分块并嵌入 - 非常适合导入既有历史。
mem.add_bulk("Alice moved to Brooklyn in March. She works at a design studio downtown.", user_id="alice")
{"elapsed_secs": 0.154154944, "status": "ok", "stored": 1, "total_chunks": 1}update ✓ live-tested
某个事实变了。旧记忆被标记为已取代(保留供追溯);新记忆在召回中取而代之。
mem.update("576700aa-...", "she switched to coffee this year", user_id="alice")
{"new_memory_id": "07e94433-b7cc-4e49-8d8f-f37fc1a392b7",
"old_memory_id": "576700aa-f0e0-4c26-99a0-10e2d5b0d624", "status": "superseded"}读取
search ✓ live-tested
语义搜索,最相关的排在最前。纯向量嵌入 - 没有关键词匹配,因此任何语言都能找到任何记忆。SDK 直接返回 memories 数组;下方展示的是原始 HTTP 响应体。
r = mem.search("what does she drink?", user_id="alice", limit=1)
{"memories": [{
"id": "576700aa-f0e0-4c26-99a0-10e2d5b0d624",
"content": "she prefers tea over coffee",
"category": "general",
"time_bucket": "2026-06",
"importance": 0.3,
"similarity": 0.6316057443618774,
"is_superseded": false,
"superseded_by": null,
"created_at": "2026-07-10T04:20:39.688276876Z"
}], "search_ms": 315, "total_found": 1}| 字段 | 含义 |
|---|---|
| similarity | 与您查询的原始嵌入相似度(0–1)。 |
| is_superseded | 若该事实已被 update() 取代则为 true。 |
| search_ms | 服务端检索耗时。 |
recall ✓ live-tested
一次往返返回您的 LLM 所需的一切 - 结果可直接粘贴进提示词:无论已存储多少内容,上下文大小固定且有界。
ctx = mem.recall("what does she drink?", user_id="alice")
{"short_term": {"count": 2, "turns": [{"role": "user", "content": "hi", ...}]},
"long_term": {"count": 4, "memories": [{"content": "she prefers tea over coffee",
"similarity": 0.63, ...}]},
"context": {"count": 4, "around_top_memory": [
"[match] she prefers tea over coffee",
"[after] Alice moved to Brooklyn in March. ..."]},
"instruction": "Use short_term for recent context, long_term for relevant
past memories, context for surrounding conversation of the
most relevant memory."}history ✓ live-tested
最近的对话轮次(短期记忆),最早的在前。
turns = mem.history("alice")
{"count": 2, "turns": [
{"role": "user", "content": "hi", "timestamp": "2026-07-10T04:20:40.989011337Z"},
{"role": "assistant", "content": "hello!", "timestamp": "2026-07-10T04:20:40.989013416Z"}
], "user_id": "alice"}stats ✓ live-tested
单个用户的记忆条数统计。
mem.stats("alice")
{"short_term_turns": 2, "total_memories": 4, "user_id": "alice"}get ✓ live-tested
按 id 获取单条记忆 - 即 add 或 list_memories 返回的 id。只返回存储的原文和元数据,绝不返回向量。其他存储空间的 id,或已删除/失效的记忆,返回 404。
m = mem.get("alice", memory_id="576700aa-...")
{"id": "576700aa-...", "content": "she prefers tea over coffee",
"category": "general", "source_type": "user_said",
"created_at": "2026-07-10T04:20:39Z", "event_date": null, "is_superseded": false}list_memories ✓ live-tested
列出存储中的记忆——只返回你保存的原文与元数据,不含向量。按游标翻页:把返回的 next_cursor 传回以获取下一页。
page = mem.list_memories("alice", limit=100)
{"count": 2, "next_cursor": null, "memories": [
{"id": "576700aa-...", "content": "she prefers tea over coffee",
"category": "general", "source_type": "user_said",
"created_at": "2026-07-10T04:20:39Z", "event_date": null, "is_superseded": false}
]}iter_memories · export_memories
无需管理游标即可遍历全部记忆,或一次性取回整个存储。
for m in mem.iter_memories("alice"): # every page, no cursor bookkeeping print(m["id"], m["content"]) everything = mem.export_memories("alice") # the whole store as a list
删除
delete ✓ live-tested
按 id 删除单条记忆。
mem.delete("alice", memory_id="576700aa-...")
{"memory_id": "576700aa-f0e0-4c26-99a0-10e2d5b0d624", "status": "deleted"}delete_all ✓ live-tested
抹除单个用户的全部数据 - 一次调用,符合 GDPR。
mem.delete_all("alice")
{"memories_deleted": 4, "status": "deleted", "user_id": "alice"}错误与可靠性
每个失败都是带类型的错误——可按具体情况(限流、认证、付费)分别捕获,或用基类 WosError 统一捕获。
from wontopos import PaymentRequiredError, NotFoundError try: mem.add("...", user_id="alice") except NotFoundError: mem.create_store("alice") # store didn't exist yet except PaymentRequiredError: top_up() # out of credit - don't retry
rate_limit
在任意调用后读取剩余额度,在触达上限前主动放慢。
mem.search("...", user_id="alice") rl = mem.rate_limit # {"limit": 150, "remaining": 3, "reset": ...}
TypeScript - 全部方法,三大类。
写入、读取、删除。以下每个示例都于 2026-07-10 针对线上 API 实际运行;响应为原样展示。
npm install wontopos
import { Client } from "wontopos"; const mem = new Client({ apiKey: "wos-live-..." });
选择模型
API 密钥决定用哪份记忆(您的账户);模型决定用哪个引擎来读取。所有模型共享同一份记忆,因此可以用一个模型存储、用另一个召回。在构造函数中设置默认值;用 withModel() 覆盖单个调用。
const mem = new Client({ apiKey: "wos-live-...", model: "tablet-1" }); // default mem.recall("...", "alice"); // tablet-1 mem.withModel("scroll-1").recall("...", "alice"); // or pick a model per call
listModels ✓ live-tested
模型目录 - 可传给 model 的 id 以及各自是否已上线。memory: "shared" 的模型读取同一存储;"isolated" 则各自独立。无需 API 密钥。
await mem.listModels();
[{"id": "tablet-1", "name": "Tablet 1", "available": true, "memory": "shared"},
{"id": "scroll-1", "name": "Scroll 1", "available": true, "memory": "shared"},
{"id": "scroll-1.2", "name": "Scroll 1.2", "available": true, "memory": "shared"}]ping ✓ live-tested
用一行确认连接以及 API 密钥是否有效。
await mem.ping(); // true, or throws AuthenticationError / PaymentRequiredError
上面的目录始终反映当前可用的模型 - 传入其他任何 id 都会得到明确的错误。新模型发布后会自动出现在其中。
写入
add ✓ live-tested
存储一条记忆。写入时即完成向量嵌入 - 不调用 LLM,您只为嵌入付费。
await mem.add("she prefers tea over coffee", "alice"); await mem.add("I promised the summary by Friday", "alice", { speaker: "me" }); // its own words - no registration needed
{"id": "576700aa-f0e0-4c26-99a0-10e2d5b0d624", "status": "stored (1 chunks)"}addTurn ✓ live-tested
把一轮对话(用户 + 助手)同时写入短期与长期记忆。
await mem.addTurn("hi", "hello!", "alice");
{"status": "ok"}speaker ✓ live-tested
每条记忆都可以记录说话者。先注册一个人,之后把名字作为 speaker 传入;"me"(助手自己的话)无需注册。搜索也接受 speaker,可以只取某个人说过的话。
await mem.addSpeaker("Bob", "alice"); # once per person; "me" needs no registration await mem.add("I promised to send the report on Friday", "alice", { speaker: "me" }); await mem.add("Bob said the deadline moved to Tuesday", "alice", { speaker: "Bob" }); await mem.search("what did Bob say about deadlines?", "alice", 10, { speaker: "Bob" });
addBulk ✓ live-tested
回填一大段文本。在服务端分块并嵌入 - 非常适合导入既有历史。
await mem.addBulk("Alice moved to Brooklyn in March. She works at a design studio downtown.", "alice");
{"elapsed_secs": 0.154154944, "status": "ok", "stored": 1, "total_chunks": 1}update ✓ live-tested
某个事实变了。旧记忆被标记为已取代(保留供追溯);新记忆在召回中取而代之。
await mem.update("576700aa-...", "she switched to coffee this year", "alice");
{"new_memory_id": "07e94433-b7cc-4e49-8d8f-f37fc1a392b7",
"old_memory_id": "576700aa-f0e0-4c26-99a0-10e2d5b0d624", "status": "superseded"}读取
search ✓ live-tested
语义搜索,最相关的排在最前。纯向量嵌入 - 没有关键词匹配,因此任何语言都能找到任何记忆。SDK 直接返回 memories 数组;下方展示的是原始 HTTP 响应体。
const r = await mem.search("what does she drink?", "alice", 1);
{"memories": [{
"id": "576700aa-f0e0-4c26-99a0-10e2d5b0d624",
"content": "she prefers tea over coffee",
"category": "general",
"time_bucket": "2026-06",
"importance": 0.3,
"similarity": 0.6316057443618774,
"is_superseded": false,
"superseded_by": null,
"created_at": "2026-07-10T04:20:39.688276876Z"
}], "search_ms": 315, "total_found": 1}| 字段 | 含义 |
|---|---|
| similarity | 与您查询的原始嵌入相似度(0–1)。 |
| is_superseded | 若该事实已被 update() 取代则为 true。 |
| search_ms | 服务端检索耗时。 |
recall ✓ live-tested
一次往返返回您的 LLM 所需的一切 - 结果可直接粘贴进提示词:无论已存储多少内容,上下文大小固定且有界。
const ctx = await mem.recall("what does she drink?", "alice");
{"short_term": {"count": 2, "turns": [{"role": "user", "content": "hi", ...}]},
"long_term": {"count": 4, "memories": [{"content": "she prefers tea over coffee",
"similarity": 0.63, ...}]},
"context": {"count": 4, "around_top_memory": [
"[match] she prefers tea over coffee",
"[after] Alice moved to Brooklyn in March. ..."]},
"instruction": "Use short_term for recent context, long_term for relevant
past memories, context for surrounding conversation of the
most relevant memory."}history ✓ live-tested
最近的对话轮次(短期记忆),最早的在前。
const turns = await mem.history("alice");
{"count": 2, "turns": [
{"role": "user", "content": "hi", "timestamp": "2026-07-10T04:20:40.989011337Z"},
{"role": "assistant", "content": "hello!", "timestamp": "2026-07-10T04:20:40.989013416Z"}
], "user_id": "alice"}stats ✓ live-tested
单个用户的记忆条数统计。
await mem.stats("alice");
{"short_term_turns": 2, "total_memories": 4, "user_id": "alice"}get ✓ live-tested
按 id 获取单条记忆 - 即 add 或 list_memories 返回的 id。只返回存储的原文和元数据,绝不返回向量。其他存储空间的 id,或已删除/失效的记忆,返回 404。
const m = await mem.get("alice", "576700aa-...");
{"id": "576700aa-...", "content": "she prefers tea over coffee",
"category": "general", "source_type": "user_said",
"created_at": "2026-07-10T04:20:39Z", "event_date": null, "is_superseded": false}listMemories ✓ live-tested
列出存储中的记忆——只返回你保存的原文与元数据,不含向量。按游标翻页:把返回的 next_cursor 传回以获取下一页。
const page = await mem.listMemories("alice", { limit: 100 });
{"count": 2, "next_cursor": null, "memories": [
{"id": "576700aa-...", "content": "she prefers tea over coffee",
"category": "general", "source_type": "user_said",
"created_at": "2026-07-10T04:20:39Z", "event_date": null, "is_superseded": false}
]}iterMemories · exportMemories
无需管理游标即可遍历全部记忆,或一次性取回整个存储。
for await (const m of mem.iterMemories("alice")) console.log(m.id, m.content); const everything = await mem.exportMemories("alice");
删除
delete ✓ live-tested
按 id 删除单条记忆。
await mem.delete("alice", "576700aa-...");
{"memory_id": "576700aa-f0e0-4c26-99a0-10e2d5b0d624", "status": "deleted"}deleteAll ✓ live-tested
抹除单个用户的全部数据 - 一次调用,符合 GDPR。
await mem.deleteAll("alice");
{"memories_deleted": 4, "status": "deleted", "user_id": "alice"}错误与可靠性
每个失败都是带类型的错误——可按具体情况(限流、认证、付费)分别捕获,或用基类 WosError 统一捕获。
import { NotFoundError, PaymentRequiredError } from "wontopos"; try { await mem.add("...", "alice"); } catch (e) { if (e instanceof NotFoundError) await mem.createStore("alice"); else if (e instanceof PaymentRequiredError) topUp(); // out of credit else throw e; }
rateLimit
在任意调用后读取剩余额度,在触达上限前主动放慢。
await mem.search("...", "alice"); const rl = mem.rateLimit; // { limit: 150, remaining: 3, reset: ... }
Rust - 全部方法,三大类。
写入、读取、删除。以下每个示例都于 2026-07-10 针对线上 API 实际运行;响应为原样展示。
cargo add wontopos
use wontopos::Client; let mem = Client::new("wos-live-...");
选择模型
API 密钥决定用哪份记忆(您的账户);模型决定用哪个引擎来读取。所有模型共享同一份记忆,因此可以用一个模型存储、用另一个召回。用 with_model() 设置默认值;再次链式调用即可覆盖单个调用。
let mem = Client::new("wos-live-...").with_model("tablet-1"); // default mem.recall("...", "alice").await?; // tablet-1 mem.with_model("scroll-1").recall("...", "alice").await?; // or pick a model per call
list_models ✓ live-tested
模型目录 - 可传给 with_model 的 id 以及各自是否已上线。memory: "shared" 的模型读取同一存储;"isolated" 则各自独立。无需 API 密钥。
mem.list_models().await?;
[{"id": "tablet-1", "name": "Tablet 1", "available": true, "memory": "shared"},
{"id": "scroll-1", "name": "Scroll 1", "available": true, "memory": "shared"},
{"id": "scroll-1.2", "name": "Scroll 1.2", "available": true, "memory": "shared"}]ping ✓ live-tested
用一行确认连接以及 API 密钥是否有效。
mem.ping().await?; // Ok(true), or Err whose .kind() is Auth / PaymentRequired
上面的目录始终反映当前可用的模型 - 传入其他任何 id 都会得到明确的错误。新模型发布后会自动出现在其中。
写入
add ✓ live-tested
存储一条记忆。写入时即完成向量嵌入 - 不调用 LLM,您只为嵌入付费。
mem.add("she prefers tea over coffee", "alice", json!({})).await?; mem.add("I promised the summary by Friday", "alice", json!({"speaker": "me"})).await?; // its own words - no registration needed
{"id": "576700aa-f0e0-4c26-99a0-10e2d5b0d624", "status": "stored (1 chunks)"}add_turn ✓ live-tested
把一轮对话(用户 + 助手)同时写入短期与长期记忆。
mem.add_turn("hi", "hello!", "alice").await?;
{"status": "ok"}speaker ✓ live-tested
每条记忆都可以记录说话者。先注册一个人,之后把名字作为 speaker 传入;"me"(助手自己的话)无需注册。搜索也接受 speaker,可以只取某个人说过的话。
mem.add_speaker("Bob", "alice").await?; # once per person; "me" needs no registration mem.add("I promised to send the report on Friday", "alice", json!({"speaker": "me"})).await?; mem.add("Bob said the deadline moved to Tuesday", "alice", json!({"speaker": "Bob"})).await?; mem.search_with("what did Bob say about deadlines?", "alice", 10, json!({"speaker": "Bob"})).await?;
add_bulk ✓ live-tested
回填一大段文本。在服务端分块并嵌入 - 非常适合导入既有历史。
mem.add_bulk("Alice moved to Brooklyn in March...", "alice", "general").await?;
{"elapsed_secs": 0.154154944, "status": "ok", "stored": 1, "total_chunks": 1}update ✓ live-tested
某个事实变了。旧记忆被标记为已取代(保留供追溯);新记忆在召回中取而代之。
mem.update("576700aa-...", "she switched to coffee this year", "alice").await?;
{"new_memory_id": "07e94433-b7cc-4e49-8d8f-f37fc1a392b7",
"old_memory_id": "576700aa-f0e0-4c26-99a0-10e2d5b0d624", "status": "superseded"}读取
search ✓ live-tested
语义搜索,最相关的排在最前。纯向量嵌入 - 没有关键词匹配,因此任何语言都能找到任何记忆。SDK 直接返回 memories 数组;下方展示的是原始 HTTP 响应体。
let r = mem.search("what does she drink?", "alice", 1).await?;
{"memories": [{
"id": "576700aa-f0e0-4c26-99a0-10e2d5b0d624",
"content": "she prefers tea over coffee",
"category": "general",
"time_bucket": "2026-06",
"importance": 0.3,
"similarity": 0.6316057443618774,
"is_superseded": false,
"superseded_by": null,
"created_at": "2026-07-10T04:20:39.688276876Z"
}], "search_ms": 315, "total_found": 1}| 字段 | 含义 |
|---|---|
| similarity | 与您查询的原始嵌入相似度(0–1)。 |
| is_superseded | 若该事实已被 update() 取代则为 true。 |
| search_ms | 服务端检索耗时。 |
recall ✓ live-tested
一次往返返回您的 LLM 所需的一切 - 结果可直接粘贴进提示词:无论已存储多少内容,上下文大小固定且有界。
let ctx = mem.recall("what does she drink?", "alice").await?;
{"short_term": {"count": 2, "turns": [{"role": "user", "content": "hi", ...}]},
"long_term": {"count": 4, "memories": [{"content": "she prefers tea over coffee",
"similarity": 0.63, ...}]},
"context": {"count": 4, "around_top_memory": [
"[match] she prefers tea over coffee",
"[after] Alice moved to Brooklyn in March. ..."]},
"instruction": "Use short_term for recent context, long_term for relevant
past memories, context for surrounding conversation of the
most relevant memory."}history ✓ live-tested
最近的对话轮次(短期记忆),最早的在前。
let turns = mem.history("alice").await?;
{"count": 2, "turns": [
{"role": "user", "content": "hi", "timestamp": "2026-07-10T04:20:40.989011337Z"},
{"role": "assistant", "content": "hello!", "timestamp": "2026-07-10T04:20:40.989013416Z"}
], "user_id": "alice"}stats ✓ live-tested
单个用户的记忆条数统计。
mem.stats("alice").await?;
{"short_term_turns": 2, "total_memories": 4, "user_id": "alice"}get ✓ live-tested
按 id 获取单条记忆 - 即 add 或 list_memories 返回的 id。只返回存储的原文和元数据,绝不返回向量。其他存储空间的 id,或已删除/失效的记忆,返回 404。
let m = mem.get("alice", "576700aa-...").await?;
{"id": "576700aa-...", "content": "she prefers tea over coffee",
"category": "general", "source_type": "user_said",
"created_at": "2026-07-10T04:20:39Z", "event_date": null, "is_superseded": false}list_memories ✓ live-tested
列出存储中的记忆——只返回你保存的原文与元数据,不含向量。按游标翻页:把返回的 next_cursor 传回以获取下一页。
mem.list_memories("alice", 100, None).await?;
{"count": 2, "next_cursor": null, "memories": [
{"id": "576700aa-...", "content": "she prefers tea over coffee",
"category": "general", "source_type": "user_said",
"created_at": "2026-07-10T04:20:39Z", "event_date": null, "is_superseded": false}
]}list_all_memories
无需管理游标即可遍历全部记忆,或一次性取回整个存储。
let all = mem.list_all_memories("alice").await?; // every page, collected
删除
delete ✓ live-tested
按 id 删除单条记忆。
mem.delete("alice", "576700aa-...").await?;
{"memory_id": "576700aa-f0e0-4c26-99a0-10e2d5b0d624", "status": "deleted"}delete_all ✓ live-tested
抹除单个用户的全部数据 - 一次调用,符合 GDPR。
mem.delete_all("alice").await?;
{"memories_deleted": 4, "status": "deleted", "user_id": "alice"}错误与可靠性
每个失败都是带类型的错误——可按具体情况(限流、认证、付费)分别捕获,或用基类 WosError 统一捕获。
use wontopos::ErrorKind; match mem.search("...", "alice", 10).await { Ok(hits) => { /* use hits */ } Err(e) if e.kind() == ErrorKind::NotFound => { mem.create_store("alice").await?; } Err(e) if e.is_rate_limited() => { /* back off */ } Err(e) => return Err(e), }
rate_limit
在任意调用后读取剩余额度,在触达上限前主动放慢。
mem.search("...", "alice", 10).await?; let rl = mem.rate_limit(); // Some(RateLimit { remaining: Some(3), .. })
curl - 无需安装,同样的方法。
无需安装 SDK - 任何 HTTP 客户端都可用。设置一次密钥,即可调用 SDK 所封装的相同端点。基础 URL 为 https://api.wontopos.com,通过 X-API-Key 认证,请求与响应均为 JSON。
# set your key once (never hard-code it) export WOS_API_KEY="wos-live-..."
写入
store ✓ live-tested
存储一条记忆。写入时即完成向量嵌入 - 不调用 LLM。
curl -X POST https://api.wontopos.com/api/v1/memory/store \ -H "X-API-Key: $WOS_API_KEY" -H "Content-Type: application/json" \ -d '{"user_id":"alice","content":"she prefers tea over coffee"}'
{"id": "576700aa-f0e0-4c26-99a0-10e2d5b0d624", "status": "stored (1 chunks)"}store-turn ✓ live-tested
把一轮对话(用户 + 助手)同时写入短期与长期记忆。
curl -X POST https://api.wontopos.com/api/v1/memory/store-turn \ -H "X-API-Key: $WOS_API_KEY" -H "Content-Type: application/json" \ -d '{"user_id":"alice","user_msg":"hi","assistant_msg":"hello!"}'
{"status": "ok"}speaker ✓ live-tested
每条记忆都可以记录说话者。先注册一个人,之后把名字作为 speaker 传入;"me"(助手自己的话)无需注册。搜索也接受 speaker,可以只取某个人说过的话。
curl -X POST https://api.wontopos.com/api/v1/memory/speakers \ -H "X-API-Key: $WOS_API_KEY" -H "Content-Type: application/json" \ -d '{"user_id":"alice","speaker":"Bob"}' # once per person curl -X POST https://api.wontopos.com/api/v1/memory/store \ -H "X-API-Key: $WOS_API_KEY" -H "Content-Type: application/json" \ -d '{"user_id":"alice","content":"I promised to send the report on Friday","metadata":{"speaker":"me"}}' curl -X POST https://api.wontopos.com/api/v1/memory/store \ -H "X-API-Key: $WOS_API_KEY" -H "Content-Type: application/json" \ -d '{"user_id":"alice","content":"Bob said the deadline moved to Tuesday","metadata":{"speaker":"Bob"}}' curl -X POST https://api.wontopos.com/api/v1/memory/search \ -H "X-API-Key: $WOS_API_KEY" -H "Content-Type: application/json" \ -d '{"user_id":"alice","query":"what did Bob say about deadlines?","speaker":"Bob"}'
supersede ✓ live-tested
某个事实变了 - 旧记忆被标记为已取代,新记忆在召回中取而代之。
curl -X POST https://api.wontopos.com/api/v1/memory/supersede \ -H "X-API-Key: $WOS_API_KEY" -H "Content-Type: application/json" \ -d '{"user_id":"alice","old_memory_id":"576700aa-...","new_content":"she switched to coffee this year"}'
{"new_memory_id": "07e94433-...", "old_memory_id": "576700aa-...", "status": "superseded"}读取
search ✓ live-tested
语义搜索,最相关的排在最前。纯向量嵌入 - 任何语言都能找到任何记忆。
curl -X POST https://api.wontopos.com/api/v1/memory/search \ -H "X-API-Key: $WOS_API_KEY" -H "Content-Type: application/json" \ -d '{"user_id":"alice","query":"what does she drink?","max_results":1}'
{"memories": [{"id": "576700aa-...", "content": "she prefers tea over coffee",
"similarity": 0.63, "is_superseded": false}], "search_ms": 315, "total_found": 1}recall ✓ live-tested
一次往返返回短期 + 长期 + 上下文 + 一条 instruction。直接粘贴进您的提示词即可。
curl -X POST https://api.wontopos.com/api/v1/memory/recall \ -H "X-API-Key: $WOS_API_KEY" -H "Content-Type: application/json" \ -d '{"user_id":"alice","query":"what does she drink?"}'
{"short_term": {"count": 2, "turns": [...]},
"long_term": {"count": 4, "memories": [{"content": "she prefers tea over coffee", "similarity": 0.63}]},
"context": {"count": 4, "around_top_memory": ["[match] she prefers tea over coffee"]},
"instruction": "Use short_term for recent context, long_term for relevant past memories..."}删除
forget ✓ live-tested
按 id 删除单条记忆,或省略 id 以删除该用户的全部数据(GDPR)。
curl -X POST https://api.wontopos.com/api/v1/memory/forget \ -H "X-API-Key: $WOS_API_KEY" -H "Content-Type: application/json" \ -d '{"user_id":"alice"}' # omit memory_id = delete all
{"memories_deleted": 1, "status": "deleted", "user_id": "alice"}Engrams
可供您的模型调用的召回工具 - 每一个都是作用于同一份记忆的不同检索策略。可单独使用,也可同时运行多个。
新的 engram 会持续发布 - 这份列表会不断增长。
Memoir 与 Archive Scroll 1.2+
这是一种交付形式,而不是可调用的工具。在 Scroll 1.2 及以上,按调用选择 form: "memoir" 或 form: "archive",那次召回 - 包括普通搜索 - 返回的时间就会以该方式书写。
Time_awareness Scroll 1.2+
按调用选择的交付形式。在支持形式的模型(Scroll 1.2 及以上)的任意调用中传入 form - memoir 或 archive - 该次响应就会以对应方式呈现:普通搜索、recall 或任何 engram。SDK 中是与 tz 一样的 form 字段;HTTP 中是 X-WOS-Form 请求头。Memoir 读起来像人的回忆;Archive 保持精确的记录 - 两者的差异最明显地体现在时间的写法上。
Memoir
form: "memoir"讲述发生了什么、一个瞬间如何引向下一个,带着人回忆时那种柔和的时间感 - 读起来是经历,而不是清单。
Archive
form: "archive"以精确记录的形式返回匹配 - 精确的经过时间和绝对时间锚点,结构化到模型可以直接读取。
user_id 下的一次 store / add 调用(那个 user_id 就是那个人的存储库)。先存储;之后任何召回 - 包括下面的普通搜索 - 都会带上时间标注。存储方法见快速上手。# plain search - no engram - the memoir form on Scroll 1.2
r = mem.search("what does Alice drink?", user_id="alice", model="scroll-1.2", form="memoir", tz=9)
# every hit gets a .time field → "a couple weeks ago" (form="archive" → "2 weeks ago (Jun 09)")tz 是调用方的 UTC 偏移小时数 - 这样“今天早上”和凌晨 4 点的日界线都落在用户的本地时间。省略则为 UTC;HTTP 下对应 X-WOS-Timezone 请求头。按地区粗略对照:美国东部 -5、美国中部 -6、美国西部 -8 · 英国 / 里斯本 0 · 中欧 +1 · 东欧 +2 · 印度 +5.5 · 中国 / 新加坡 +8 · 韩国 / 日本 +9 · 悉尼 +10。(均为标准时间 - 夏令时会让部分地区 +1;请传用户实际所处的偏移。)
同一次搜索,两种形式 - 记忆完全相同,只有 time 不同:
{ "count": 3, "memories": [
{ "content": "Alice prefers tea over coffee", "time": "a couple weeks ago" },
{ "content": "met Alice at the cafe downtown", "time": "yesterday afternoon" },
{ "content": "Alice moved to Brooklyn", "time": "about half a year ago" }
] }{ "count": 3, "memories": [
{ "content": "Alice prefers tea over coffee", "time": "2 weeks ago (Jun 09)" },
{ "content": "met Alice at the cafe downtown", "time": "yesterday at 14:00" },
{ "content": "Alice moved to Brooklyn", "time": "6 months ago (Dec 2025)" }
] }| 经过时间 | Memoir | Archive |
|---|---|---|
| 3 分钟 | a few minutes ago | 3 minutes ago |
| 14 分钟 | about 15 minutes ago | 14 minutes ago |
| 30 分钟 | half an hour ago | 30 minutes ago |
| 50 分钟 | about an hour ago | 50 minutes ago |
| 2 小时 | a couple hours ago | 2 hours ago, at 13:10 |
| 8 小时 | this morning | 8 hours ago, at 07:10 |
| 昨天下午 | yesterday afternoon | yesterday at 14:00 |
| 昨晚 | last night | 17 hours ago, at 22:00 |
| 2 天 | a couple days ago | 2 days ago (Tue 15:10) |
| 6 天 | several days ago | 6 days ago (Fri 15:10) |
| 9 天 | about a week ago | last week (Jun 16) |
| 16 天 | a couple weeks ago | 2 weeks ago (Jun 09) |
| 35 天 | about a month ago | last month (May 21) |
| 60 天 | a couple months ago | 2 months ago (Apr 2026) |
| 180 天 | about half a year ago | 6 months ago (Dec 2025) |
| 380 天 | about a year ago | last year (Jun 2025) |
| 800 天 | a couple years ago | 2 years ago (Apr 2024) |
| 1500 天 | about 4 years ago | 4 years ago (May 2022) |
上表每个值都是渲染器的真实输出。看看两行“昨天”:Memoir 把昨天下午和昨晚分开 - 一天以一次睡眠为界 - 而 Archive 只写一个钟点,不划分白天与黑夜。
两种模式如何解读时间
Memoir - 人们实际的说法。近期时刻保持相当清晰(大约 15 分钟、半小时),越往前措辞越宽泛 - 两周左右、大约半年、两三年前 - 就像记忆本身随着距离而松弛。在一天之内,它舍弃钟点而使用时间地标:今天早上、昨晚、昨天下午。而且一天以一次睡眠为界,不按日历跳变:日界线大约在本地时间凌晨 4 点,因此深夜仍算同一个晚上,而不是已经到了明天。
Archive - 精确,且始终带锚点。每一行都带有精确的经过时间和可供模型计算的绝对参照,而且越近锚点越细:今天用钟点(8 小时前,07:10),本周用星期加钟点(2 天前(周二 15:10)),本月用日期(上周(6 月 16 日)),更早则用年月(6 个月前(2025 年 12 月))。绝不含糊,绝不出错。
deep_recall
多跳召回。先搜索您的查询,再取最佳匹配并以它的内容再次搜索 - 带回单次搜索会遗漏的关联上下文。当记忆相互引用时效果最佳(一个人 → 其项目 → 具体细节)。最多返回约 12 条。
out = mem.engram("deep_recall", "what should I know about Alice?", user_id="alice"){ "engram": "deep_recall", "hops": 2, "count": 12,
"memories": [ ... ],
"usage": { "input_tokens": 5, "output_tokens": 61 } }usage(输入 + 输出),使用与 API 其他部分相同的分词器统计;没有隐藏的按 engram 收费。需要同时使用多个?并发调用即可 - 每个 engram 都是独立请求。timeline
按时间排序的召回。按事件发生时间由新到旧返回记忆,而非按相关性。适合“X 是什么时候”、历史与顺序类问题。最多返回 15 条。
events = mem.engram("timeline", "project milestones", user_id="alice"){ "engram": "timeline", "hops": 1, "count": 15,
"memories": [ ... ],
"usage": { "input_tokens": 4, "output_tokens": 88 } }usage(输入 + 输出),使用与 API 其他部分相同的分词器统计;没有隐藏的按 engram 收费。需要同时使用多个?并发调用即可 - 每个 engram 都是独立请求。gather
广域收集。先搜索,再围绕前三个最佳匹配展开 - 比 deep_recall 撒得更宽。用它把与某个人、项目或主题相关的一切一次调用全部带回。最多返回约 18 条。
related = mem.engram("gather", "everything about Project Atlas", user_id="alice"){ "engram": "gather", "hops": 4, "count": 18,
"memories": [ ... ],
"usage": { "input_tokens": 6, "output_tokens": 142 } }usage(输入 + 输出),使用与 API 其他部分相同的分词器统计;没有隐藏的按 engram 收费。需要同时使用多个?并发调用即可 - 每个 engram 都是独立请求。所有端点,一个基础 URL。
无需 SDK - 任何 HTTP 客户端都可用。基础 URL 为 https://api.wontopos.com,通过 X-API-Key 请求头认证,请求与响应均为 JSON。记忆操作均为 POST;存储库管理使用 /collection 上的 POST / GET / DELETE。存储库必须先存在(见存储库),否则库内操作返回 404。
| 端点 | 用途 | 请求体字段 |
|---|---|---|
| POST /api/v1/memory/collection | 创建存储库 | user_id |
| GET /api/v1/memory/collections | 列出您的存储库 | (无) |
| DELETE /api/v1/memory/collection | 删除存储库及其全部记忆 | user_id |
| /api/v1/memory/store | 存储一条记忆 | user_id · content · metadata? (speaker) |
| /api/v1/memory/store-turn | 存储一轮对话 | user_id · user_msg · assistant_msg |
| POST /api/v1/memory/speakers | 注册说话者(显式,最多 50 人) | user_id · speaker |
| GET /api/v1/memory/speakers | 列出已注册说话者 + 记忆数 | user_id |
| DELETE /api/v1/memory/speakers | 注销说话者(记忆保留) | user_id · speaker |
| /api/v1/memory/bulk-store | 回填一段文本 | user_id · content · category? |
| /api/v1/memory/search | 语义搜索 | user_id · query · max_results? · speaker? |
| /api/v1/memory/recall | 短期 + 长期 + 上下文 | user_id · query |
| /api/v1/memory/history | 最近的对话轮次 | user_id |
| /api/v1/memory/stats | 记忆条数统计 | user_id |
| /api/v1/memory/supersede | 替换已变更的事实 | user_id · old_memory_id · new_content |
| /api/v1/memory/forget | 删除一条(或全部) | user_id · memory_id? (省略 = 全部删除) |
# create the store once (stores are explicit) curl -X POST https://api.wontopos.com/api/v1/memory/collection \ -H "X-API-Key: $WOS_API_KEY" -H "Content-Type: application/json" \ -d '{"user_id":"alice"}' # store a memory curl -X POST https://api.wontopos.com/api/v1/memory/store \ -H "X-API-Key: $WOS_API_KEY" -H "Content-Type: application/json" \ -d '{"user_id":"alice","content":"she prefers tea over coffee"}' # recall - one call, ready for your prompt curl -X POST https://api.wontopos.com/api/v1/memory/recall \ -H "X-API-Key: $WOS_API_KEY" -H "Content-Type: application/json" \ -d '{"user_id":"alice","query":"what does alice drink?"}'
{"id": "576700aa-f0e0-4c26-99a0-10e2d5b0d624", "status": "stored (1 chunks)"}人人功能相同。
等级只提升您的限额。
每个等级都运行完整引擎 - 相同的召回质量、相同的语言支持、全部方法。随着累计信用额度购买的增长,等级会自动提升至 Tier 5,无需申请或联系销售。企业版(Tier 6)是唯一的例外。
消费限额
每个等级限定您每个日历月的消费上限。累计信用额度购买达到下一档门槛时立即升级。
| 用量等级 | 信用额度购买 | 每月消费限额 |
|---|---|---|
| Tier 1 | $5 | $100 |
| Tier 2 | $40 | $500 |
| Tier 3 | $200 | $1,000 |
| Tier 4 | $400 | $5,000 |
| Tier 5 | $1,000 | $25,000 |
| Tier 6 - Enterprise | 联系我们 | 无限制 |
速率限制
速率限制按账户计 - 账户下的所有 API 密钥共享同一限额,并随等级提升。超限会返回 429 和 retry-after 响应头;请退避(1s → 2s → 4s)后重试。所有端点都是幂等友好的,重试是安全的。
| 等级 | 每分钟请求数 |
|---|---|
| Tier 1 | 150 |
| Tier 2 | 300 |
| Tier 3 | 600 |
| Tier 4 | 1,500 |
| Tier 5 | 3,000 |
| Tier 6 - Enterprise | 自定义 |
企业版(Tier 6)可获得自定义速率限制、SLA、专属支持以及可选的自托管许可 - 联系我们。
出错时会发生什么。
错误以 JSON 信封返回,包含稳定的 type、面向人的消息,以及报告问题时可提供给我们的 request_id。
{"type": "error", "error": {
"type": "authentication_error",
"message": "Invalid or revoked API key.",
"request_id": "063f8b83-eee2-4383-a5cf-11e4bcd29d7c"
}}| HTTP | 含义 | 处理方式 |
|---|---|---|
| 401 | API 密钥无效或已吊销 | 检查密钥;在控制台签发新密钥。 |
| 422 | 请求体格式错误(字段缺失或类型错误) | 错误消息会指明具体字段 - 修正后重试。 |
| 429 | 触发速率限制 | 指数退避(1s → 2s → 4s)后重试。安全:所有端点都是幂等友好的。 |
| 5xx | 服务端问题 | 退避后重试;联系我们时请附上 request_id。 |
# SDK error handling (Python) from wontopos import Client, WosError try: mem.search("...", user_id="alice") except WosError as e: if e.status == 401: ... # bad key elif e.status == 429: ... # back off and retry
您的服务器,您的数据。
引擎可以运行在您自己的基础设施内 - 相同的 API、相同的 SDK。把客户端指向您的主机,其余一切不变。
mem = Client(api_key="...", base_url="https://wos.your-host.com")- 数据驻留。记忆永不离开您的网络。
- 相同的接口。相同的方法与端点,行为完全一致。
- 许可。自托管方案按部署单独商定 - 联系我们。
超越上下文窗口。
WOS 可从 1.4M token 的历史中召回记忆 - 这远大于任何 LLM 的上下文窗口 - 却依然只交回约 1,470 个 token 的紧凑切片。
您智能体的记忆不受提示词容量的限制。它保留一切,只检索真正重要的部分,无论历史增长到多大。
私密,且属于您。
您的数据只属于您的存储库。我们绝不用它训练、查看或复用 - 只负责组织它,让您能够检索。
- BYOK。您的 LLM 密钥随每次请求传递,绝不存储。
- 隔离。记忆先按账户、再按
user_id隔离。 - GDPR 删除与自托管。一次调用即可抹除一个用户;如有需要,也可在您自己的环境中运行引擎。