时间切片
大多数 AI 产品按「对话线程」整理你:你决定什么时候开新对话、给对话起名字、事后翻找该回哪一条。Previously 不做这个假设。它只认一个自然单位——时间。你开口,一个时间切片就打开;时间到了,它就合上。没有「新对话」按钮,因为你的生活本来就没有这个按钮。
**时间切片(slice)**是 Previously 记忆的最小完整单位:一段连续的对话爆发,原样保存在一个小目录里——纯 Markdown 加 YAML,没有数据库,没有二进制格式。
一段时间切片里有什么
你问 Previously:我的一段对话,是怎么被你记住的?
23:47
拿 7 月 16 日那一片来说:focus 是「半马完赛与个人成长」;情绪色调记的是 proud、reflective;open_loops 里留着一条「你接下来可能会拾起的健身目标」;从头到尾的完整对话在 timeline/core.md 里,一字未动。
切片关闭的那一刻,回合分析器统一标记:focus、summary、tags、情绪色调。就算模型那次没写好,也有确定性兜底——任何一片都不会「空着」关闭。
每个切片是 memory/episodic/slices/YYYY/MM/DD/HHMM/ 下的一个目录——路径本身就是它发生的时刻。HHMM 是 UTC,取自第一条消息的时间戳;你的本地时区单独存在 frontmatter 的 timezone 字段里,所以路径跨时区、跨夏令时都稳定。目录里的文件各有分工:
memory/episodic/slices/2026/08/10/2130/
timeline/
core.md -- 共同的记录:YAML 前置元数据 + 逐字对话轮次
agent.md -- 它的思考:提取的推理、工具调用(ok/error)、thinkDeep 报告
appendix.md -- 没能解析进轮次的原始行,原样保留
previously.md -- 这个切片冻结时的用户卡快照
timeline/core.md是共享的对话记录,也是唯一事实来源:你和 Previously 实际说过的话,一字不少。timeline/agent.md是 agent 自己的那条线——它每一轮背后的认知,机械提取,绝不作为对话上下文注入。timeline/appendix.md是诚实兜底:解析器吃不下的行不丢,进附录。previously.md是切片关闭那一刻冻结的用户卡快照——它当时「认识的你」。
切片只认时间
切片的生命周期里没有任何「话题检测」。这是刻意的:判断「你们换没换话题」是模型会犯错的地方,而时间从不说谎。
一个切片有三种结束方式,全部记录在 frontmatter 的 closed_by 字段里:
- 片龄上限(
time_cap)——从切片开始算起maxSliceMinutes分钟(默认 30,可在设置里调)后强制关闭。注意是从开始算,不是从最后一次活动算:切片是一段有起有止的时间,而不是一串沉默检测的结果。 - 上下文丢失(
context_lost)——你刷新了页面或换了设备,客户端历史里没有了 agent 的回复、而磁盘上的切片有,这段切片无法诚实地接续,于是合上。 - 轮次上限(
capacity)——maxTurnsPerSlice(默认 50)只是安全阀,防止异常会话无限增长。正常生活里你几乎碰不到它。
关闭是惰性的:信号在你的下一条消息到达时才被检查,切片的 end 盖上的是最后一轮的真实时间戳,而不是检测时刻。下一条消息会打开一个新切片——聊天界面的历史窗口因此也是切片对齐的:服务器把客户端历史裁到当前切片,而不是固定的「最近 N 条」。
关闭的那一刻
切片合上前,turn analyzer(每轮跑一次的分析同事)会为它写下标记:focus(一句话主题)、summary(百字内摘要)、精炼后的 tags、emotional_tone。
这里有一条硬纪律:切片绝不空着关闭。 如果模型这次没能给出标记,系统会从切片本身确定性地造一份——首轮话题加上标签——而不是留下空字段。模型负责提质,代码负责兜底。
更老的「干切片」(早于这套机制、没有 focus/summary 的切片)也不会永远匿名:每逢切片关闭的边界,一次顺手而为的回填最多为 3 个干切片补上标记。关闭边界是唯一允许回填的时机——它绝不在对话进行中打扰你。
前置元数据
每个 core.md 以 --- 分隔的 YAML 开头。focus、summary 和 emotional_tone 在关闭时写入(带确定性兜底);其余字段都是机械写入:
| 字段 | 含义 |
|---|---|
slice_id | YYYY-MM-DD-HHMM,第一条消息的 UTC 日期时间 |
status | "active" 或 "closed" |
start / end | 第一轮与最后一轮的 UTC ISO 8601 时间戳(活跃时没有 end) |
timezone | 交互时你的 IANA 时区,例如 "Asia/Shanghai" |
focus | 核心主题,一句话(关闭时写入) |
summary | 摘要,最多 100 个字符(关闭时写入) |
tags | 语义关键词——切片关闭时被织进 Strand 索引 |
open_loops | 悬而未决的问题 |
decisions | 这段对话里达成的结论 |
related_slices | 相关切片的相对路径,可为空 |
loops | 从该切片派生的后台循环 id,可为空 |
emotional_tone | "positive"、"neutral"、"negative" 或 "mixed",关闭时评估 |
closed_by | 关闭信号:"time_cap"、"context_lost"、"capacity" 或 "user_explicit";更老的切片可能带着遗留值 "time_silence" |
evolution_summary | 切片开始时那次卡片进化的一句话摘要——逐字重放进系统提示词,让提示词在切片整个生命周期内逐字节不变 |
空字符串和未定义的字段会从 YAML 中去除(所以活跃的切片没有 focus/summary);空数组按原样序列化。
轮次正文
前置元数据之后,每条消息成为一个二级标题:
## Turn mVhV2g — 2026-08-10T21:30:22.811Z (user)
你的消息正文……
## Turn mVhV2g — 2026-08-10T21:30:43.641Z (agent)
Previously 的回复……
同一轮的用户消息和 agent 回复共享一个 6 字符的 turnId,因此每一次交互都可寻址——工具可以按它精确读取单个轮次。
为什么是纯 Markdown
每一个切片都是带 YAML frontmatter 的 Markdown 文件,这个选择是深思熟虑的:在任何编辑器里可读,在 git 里每次变化都有清晰的 diff,任何懂 Markdown 的工具都能解析它。一轮之内的所有写入会合入一次提交落盘——你的消息在任何内容回流之前就已安全写进你的记忆文件夹。它在磁盘上的完整样子,见记忆是一个文件夹。