知道是谁说的记忆。
人的记忆以人为单位: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,不保存任何内容,也不附加说话者。仅在某个细节上不同于既有记忆的新事实("花生过敏"之后的"甲壳类过敏")同样会被丢弃,因此请读取 status,不要假定写入已生效。
我们用最严格的方式测试它:存入正文里完全没有名字的记忆,再按人回忆。归属来自说话者记录而不是文字匹配,所以在任何语言里表现一致。
如何使用
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 }列表显示存储库认识的人及各自的记忆数与上限。移除只删除注册:那个人的记忆保留,只有名字标签消失。
读取某个人的记忆
by_speaker 无需查询词即可返回某个人说过的内容,按时间由新到旧。"me" 返回助手自己的话。分页方式与图像相同,采用游标:把 next_before 和 next_skip_ids 回传。
page = mem.by_speaker("Bob", limit=50)
page["memories"], page["chunks"]| 字段 | 作用 |
|---|---|
| memories | 记忆列表,按时间由新到旧。结构与检索返回的相同。 |
| chunks | 这些记忆背后的句子级片段,即一次删除实际会移除的内容。通常大于记忆的条数,建议在确认删除前先展示。同时以 points_to_delete 返回。 |
| next_before | 下一页的游标,与 next_skip_ids 配合使用。两者都必须传,因为记忆可能共用同一时间戳。 |
speaker 是写入时记录的标签,而不是对文本的检索。存储时未指定说话人的记忆可以通过检索找到,但永远无法通过 by_speaker 取得,包括在 "me" 下。列出、浏览与移除说话人
mem.list_speakers() # who is registered
mem.by_speaker("Bob") # what Bob said, newest first
mem.remove_speaker("Bob") # unregister; the memories stay