人生时间线的数据
人生时间线是两张表、十种记录、三种范围。这一篇讲谁往里写、按什么编号不重复、怎么读、什么时候删,以及加一种记录要动哪几处。
行为层面的讲法见 2.15。
两张表
life_timeline (
entry_id TEXT PRIMARY KEY, -- 按来源编号:activity:<计划>、sleep:<会话>、chat:<频道>:<开始毫秒>、commitment:<消息>:<第几条>……
kind, started_at, ended_at, -- 十种之一;结束时间空着 = 还在进行
scope, -- self | guild:<服务器> | dm:<对方>
guild_id, channel_id,
title, -- 最长 120 字:活动标题、承诺的一句概括
detail_json, -- 各说了几轮、睡了几小时、状态……
fact_weight, -- 0 到 1:梦是 0,说不准做没做完的活动是 0.5
source_ref, updated_at
)
life_timeline_people (entry_id, user_id) -- 这一条牵涉到谁(按人找、按人删都走它)
十种记录(timeline.KINDS):ACTIVITY、SLEEP、NAP、DREAM、DAYDREAM、CONVERSATION、OUTREACH、COMMITMENT、MILESTONE、CONDITION。TimelineEntry 构造时就查:种类不在表里、事实权重出了 0 到 1,直接抛。
谁写
- 生活引擎的记录(活动、睡眠、打盹、梦、发呆、持续几天的状态):维护循环的「时间线」那一步(
bot._timeline_sync_tick)把引擎手上 31 天以内的记录转成时间线(entries_from_life),算每一条的内容指纹,只写变了的。编号就是来源编号,同一条反复同步只会更新。 - 对话:它说出去的那一轮由
bot._note_timeline_sent写,对方接话由bot._note_timeline_answer写,落到storage.note_timeline_turn。同一个频道隔 30 分钟没人说话(CONVERSATION_GAP),下一句就开新的一段。对话那一条的detail里还记着规划器判过的场景、言语动作(scenes、intents),里程碑靠它们算。 - 主动联系:发出、被接时写,记隔多久被接。
- 承诺、里程碑:
cognition/commitments.py、cognition/milestones.py是纯函数,只产出行,由bot.py的_note_commitments、_commitment_milestone_tick写(行为见 2.21、2.14)。
写都走 storage.upsert_timeline_entries:按 entry_id 幂等,牵涉到的人整组换掉。
谁读
- 「此刻」:
bot._now_snapshot读 36 小时内、这里看得到的范围(timeline.visible_scopes:群里是self+ 这个服务器,私聊是self+ 和他的私聊),用facts_for_prompt变成给模型的最多 8 条事实:只要事实权重大于 0 的,不带任何编号,时间换成当地时间,和人有关的只标with_speaker。梦和承诺不在这里:梦有自己的回忆规则,欠着的承诺单独给(verified_commitments)。 - 主动联系:到期的承诺、最近三天的里程碑(
bot._owed_and_milestones)。 - 后台:
GET /api/life/timeline(默认 2 天、最多 30 天;self+ 选中的服务器;私聊的只给所有者);人物档案的关系时间线按人读(user_id)。
什么时候删
- 过期:六小时一次的重清理里,和人有关的(不是
self范围的,以及对话、主动联系、承诺、里程碑)跟聊天记录一起过期([conversation].message_retention_days,默认 30 天);人格自己的事留 180 天(SELF_RETENTION)。 - 删一个人的数据:
storage._forget_timeline:整个人删时,和他的私聊整条删,别的记录里去掉他;只在一个服务器里删时,只动那个服务器范围的;去掉他以后没人了的对话、主动联系整条删。之后再同步,生活引擎里还挂着他的梦也不会被写回来(upsert_timeline_entries先查被删过的人)。
加一种记录
cognition/timeline.py:KINDS和KIND_LABELS各加一项(给模型看的说法);需要的话写一个<种类>_entry构造函数。- 决定范围:人格自己的事用
SELF_SCOPE;和人有关的用scope_of(guild_id=…, dm_user_id=…),并把人写进people,删人时才删得到。 - 决定给不给模型:
facts_for_prompt默认会带上(事实权重大于 0 的);不该带的在那里排除。 - 决定留多久:和人有关的种类加进
cleanup_messages里那条过期查询。 - 后台要显示:
webui_frontend/src/routes/Life.tsx、Person.tsx里各有一张种类到中文的表。
对应的代码
src/aeria/cognition/timeline.py(TimelineEntry、KINDS、visible_scopes、entries_from_life、facts_for_prompt;纯函数,CONVERSATION_GAP= 30 分钟、SELF_RETENTION= 180 天、LIFE_SYNC_HORIZON= 31 天)。src/aeria/cognition/now.py(RECENT_HOURS= 36、RECENT_LIMIT= 8)。src/aeria/storage.py:建表、upsert_timeline_entries、timeline_entries、_forget_timeline、cleanup_messages。- 测试:
tests/test_life_timeline.py、tests/test_life_timeline_e2e.py。
没做到的
- 对话的边界只看安静了多久,聊到一半离开四十分钟会断成两段。
- 后台只能看、不能改、不能单独删一条。
- 同步按 31 天的窗口:引擎里更早的记录不会补进来(引擎自己也只留这么久)。