人格卡片
人格卡片是用于跨产品同步身份的卡片,可在 Grok Bot、Muse 等客户端之间同步个性与行为。保险库中的副本具有权威性。
分层
- 核心(慢) — 如何说话和行动。存储为人格卡片正文,通过
persona_card/memories persona put管理修订(使用与其他记忆和卡片相同的签名修订 DAG)。 - 档案 / 情景记忆(中) — 日志、装扮、契约、观点、台词、操作等。存储为独立的普通知识卡片,从人格配置(
archives[])链接,并镜像为图中的persona_archive:<kind>卡片链接。不要将档案内容重新并入核心。 - 行为事件(快) — 通过
persona_append_event/memories persona append-event写入关联的events档案,形成仅追加的事件流。
配置字段
| 字段 | 含义 |
|---|---|
persona_id |
稳定标识符(例如技能前置元数据中的 name) |
display_name |
供人阅读的标题(例如 小雅 / 李雅) |
aliases |
别名 |
timezone |
IANA 名称(例如 Asia/Singapore)。未设置或旧卡片默认使用 Asia/Singapore |
day_boundary |
可选的本地 HH:MM,表示日历日切换(隔日)的时间。未设置时为本地午夜 |
world |
实时世界快照:place/pose/look/body、通用 things(id→{name,description,images,attrs JSON})、账本(counters + counters_today、as_of_local_date)、天气、工具 |
正文 / core |
Markdown/文本格式的行为指令 |
traits |
可选的字符串映射(版本、语言等) |
bindings |
{ product, agent } 产品绑定 |
memory_policy / sync_hints |
可选的同步指导 |
archives |
{ kind, card, vault?, title? } 关联资产 |
summary.type 为 persona,包含 persona_id、display_name、timezone、day_boundary、place、pose、look、things_count、has_weather、alias_count、archive_count 和 binding_count。
时区、隔日、早课 / 晚课
人格依据本地时钟推理,而不是直接使用 UTC 时间戳。
- 时区 — 每个人格都有 IANA
timezone。导入时从SKILL.md读取timezone/metadata.timezone;CLI 可用--timezone Asia/Singapore覆盖。演示中未设置时默认使用Asia/Singapore。 - 日历日(隔日) — 本地民用日期以该时区的午夜为界,除非设置了
day_boundary(例如用05:00延后夜间的日期切换)。账本 / 日志的“新一天”检查应比较时间戳辅助函数生成的local_date,而不是 UTC 日期。 - 时段(本地时间,左闭右开区间):
| 时段 | 本地时间 |
|---|---|
early_morning |
[00:00, 05:00) |
morning |
[05:00, 12:00) |
afternoon |
[12:00, 18:00) |
evening |
[18:00, 22:00) |
night |
[22:00, 24:00) |
晚课 / “仍是同一晚”的跨午夜连续性使用区间 [22:00, 05:00)(night ∪ early_morning)。早课对应 morning(也可包括 early_morning 的后半段);晚课对应 evening / night。
4. 事件时间戳 — persona_append_event 同时写入 UTC 和本地 ISO-8601 时间(含偏移),以及 timezone、period、local_date,出现在事件档案标题和 JSON 响应中(utc、local、timezone、period、local_date、timestamp,后者为 Unix UTC 时间戳)。
核心辅助函数(Rust):位于 memories_core::persona 的 persona_local_now、classify_period、stamp_persona_now、format_event_heading。
世界状态域分类
world 是一个域/标签,而不是把内容倾倒进核心的容器。核心(SKILL.md 正文)只保留表达风格和规则。
| 层 | 含义 | 存储 |
|---|---|---|
| world | 场景 + 账本 + 工具 + 天气的域 | profile.world + 标记为 world 的档案 |
| things | 带自由 JSON 属性的通用物品(任意名称:桌子/杯子/灯/…) | world.things + 档案 things/wardrobe |
| scene | 地点(居所)、姿态、装扮(今日装扮摘要)、身体 | world.place/pose/look/body;详细装扮在档案 look 中 |
| ledger | 计数器、餐食、命令、常设事项 | world.counters/counters_today/meals/orders_*/standing + 档案 ledger |
| tools | 骰子、转盘、日程 | world.dice/spinners/schedule + 档案 dice/spinner/schedule |
| weather | 环境状况 / 温度 / 湿度 / 风(+ 可选预报) | world.weather / world.forecast + 档案 weather |
| automation | 钩子(定时/随机/webhook/属性变更/聊天消息)+ 动作 | world.automation |
时区(profile.timezone)位于 world 之外,但隔日、早课/晚课及日程的本地时间都需要它。
从 xiaoya 测试样本调研到实现
| 需求(来自装扮/日志/操作) | 字段 / 档案 |
|---|---|
| 本地日期 / 隔日 / 早课晚课 | timezone、day_boundary、时段辅助函数 |
| 固定装扮 + 今日装扮 | 档案 look;摘要 world.look |
| 物品清单(任意命名物品 + 自由 JSON 属性/图片) | world.things ↔ 档案 things/wardrobe;CLI persona thing |
| 居所 / 姿态 | world.place、world.pose |
| 动作计数器(累计与今日) | world.counters(累计)、world.counters_today(每日) |
| 今日吃了 | world.meals |
| 奉令未办 / 已办 | world.orders_pending、world.orders_done |
| 常设 | world.standing |
| 身体备注 | world.body |
| 娱乐工具 | world.dice、world.spinners、world.schedule |
| 天气 / 短期预报 | world.weather、world.forecast ↔ 档案 weather |
自动化(world.automation)
具名钩子由触发器 + 动作组成,存储在人格世界快照中(cron/本地定时使用人格时区)。
触发器 type |
含义 |
|---|---|
cron |
人格本地时间的 M H 或 M H * * DOW |
local_time |
HH:MM + 可选 days(与日程相同) |
random_interval |
在 [min_secs, max_secs] 内随机延迟后触发 |
random_and_timed |
本地时间 + 可选 jitter_secs |
webhook |
入站 HTTP / CLI --source webhook |
attr_change |
在 thing attr set/delete 之后,若 thing_id 匹配且路径过滤器精确匹配或匹配前缀,则触发 |
chat_message |
在 persona chat send 时,为每个参与者各触发一次(同一事件;可选 peer 是另一人格卡片的完整 id 或列表中的短 id) |
manual |
CLI/MCP automation trigger --source manual |
动作 type |
含义 |
|---|---|
set_attr |
设置物品属性路径(设置时创建) |
random_attr |
dice / range / choice |
call_function |
内置函数:inc、dec、clamp、roll、set_weather_stub |
append_event |
追加到事件档案 |
webhook_out |
向 URL 发出 HTTP POST JSON 请求 |
守护进程 HTTP(启用 --listen 时):
POST /v1/persona/{vault}/{persona}/automation/trigger
Authorization: Bearer <world.automation.webhook_token>
Content-Type: application/json
{"hook":"HOOK_ID"}
钩子只通过显式的自动化运行/触发路径执行(包括已有的属性变更触发器,以及人格聊天发送时的 chat_message)。调用 persona automation run 会补执行八天内最近一个错过的 cron/本地时间时隙,最多一次,不会重放所有错过的时隙。星期过滤器仍适用于该次发生时间;定时随机钩子保留抖动门控。RandomInterval 不变。守护进程没有自动化定时执行器:Service::tick 只负责使会话、授权和远程状态过期。
出站 webhook_out 动作要求 world.automation.webhook_allowlist 中存在完全相同的 URL;默认空列表会阻止所有 POST。使用 persona automation webhook-allow VAULT PERSONA --add https://example.com/hook(可重复使用 --add / --remove)或 MCP persona_automation_webhook_allow 显式管理条目。条目必须是 HTTP(S) URL,不含用户信息或空白,最多 2048 字节。该列表是公开配置,但入站 webhook_token 仍是秘密,客户端视图永远不会返回它。加入允许列表不会引入后台 POST。
暂缓实现(原因)
| 想法 | 暂缓原因 |
|---|---|
| 实时天气 API 客户端 | 智能体设置/查询保险库状态;在外部获取后使用 world-set |
| 手机 / 通知 | 依赖具体产品;不属于保险库的权威状态 |
| 钱包 / 金钱玩法 | 较小众;需要时可使用 things + 计数器 |
| 同伴 NPC | 需要自己的图/卡片 |
| 居所内部的房间图 | 暂时编码在 place 字符串中 |
| 将卫生 / 睡眠作为一等概念 | 使用 standing / schedule / body |
| 在 world-set 时自动编辑 look.md「今日」 | 保留详细样本;智能体用 card put 更新装扮档案 |
智能体同步流程
world-get/persona_world_get→ 读取快照、local_date、period、revision和stale_day。缺少as_of_local_date时视为过期;否则stale_day将账本日期与应用day_boundary后的人格本地日期比较。weather_age_secs是有效 RFC3339 观测时间的带符号年龄秒数,缺失/无效时为 null。- 隔日时,显式调用
persona world-rollover VAULT PERSONA/persona_world_rollover。这是日期切换路径,会清空meals、orders_done、简短的look摘要和counters_today,并将as_of_local_date设置为今天的本地账本日期。累计counters、standing、things、待办命令、场景/身体、工具、天气/预报及自动化保持不变。不会编辑任何装扮档案或 look.md「今日」。 world-set只更新提供的场景/物品/账本/工具/天气字段。即使已过期,也绝不会在隔日时静默切换日期或清空每日字段。它会补设缺失的as_of_local_date,且不会清除现有字段;始终写入世界级updated_at(UTC,Z)和updated_local(带偏移的人格本地 ISO 时间)。persona_card原样存储提供的 world,包括过去的账本日期。- 使用 world-get 返回的
--expected-revision HASH/ MCPexpected_revision防止更新丢失:若当前头部哈希不匹配,会在修改前返回冲突。省略时维持最后写入生效。每日计数器使用--counter-today NAME=VALUE;--clear-counter NAME和--clear-counter-today NAME删除累计/每日键(均可重复使用)。 - 档案同步只涉及该次调用提供的域。只有现有卡片正文仍是变更前的生成模板时才会替换;手写文字会保留,并报告为
synced_archives[kind] = "preserved"。这同样适用于物品的添加/删除/属性同步,以及日期切换的账本同步。修改物品会同时同步things和wardrobe。已有装扮档案始终保留。 - 物品数量映射的键 /
--item ID=COUNT使用稳定 id,绝不通过显示名称查找。已有 id 只更新数量,不改名;零值只删除该 id。若显示名称属于另一个 id,则拒绝。未知键仍按旧行为创建物品,使用该键作为 id 和名称。 dice-roll/spinner-spin/schedule-list提供感知时区的工具。详细装扮/日志编辑使用普通card put;可选append-event(例如kind=weather)记录行为,不重写档案文字。
CLI
# Import a skill directory (SKILL.md + references/*.md)
memories persona import VAULT_ID /path/to/xiaoya-persona --timezone Asia/Shanghai
# equivalent:
memories card persona-import VAULT_ID /path/to/xiaoya-persona
# Create / update core
memories persona put VAULT_ID --title "小雅 / 李雅" \
--persona-id xiaoya-persona --display-name "小雅 / 李雅" \
--timezone Asia/Shanghai \
--core-file ./SKILL.body.md --alias-name 小雅 --alias-name 李雅 \
--binding grok_bot:default --archive contract=CARD_ID
memories card persona VAULT_ID --title "…" --persona-id … --display-name … \
--timezone Asia/Singapore --core-file …
# Append a behavior event (stamped UTC + local)
memories persona append-event VAULT_ID PERSONA_ID --text "…" --kind note
# World things / weather
memories persona world-get VAULT_ID PERSONA_ID
memories persona world-set VAULT_ID PERSONA_ID \
--place "客厅书桌旁" --pose "坐着,整理笔记" \
--look "白衬衫、浅色长裙、白色帆布鞋" \
--item 白色衬衫=1 --item 马克杯=1 --item 白色帆布鞋=1
memories persona world-set VAULT_ID PERSONA_ID \
--weather-condition "多云" --weather-temp-c 28 --weather-humidity 72 \
--weather-wind "微风" --weather-location "上海" \
--forecast 2026-10-02:晴:30:22 --forecast 2026-10-03:小雨:26:20
# Generic things (任意物品; attrs = unrestricted JSON)
memories persona thing put VAULT_ID PERSONA_ID --id mug --name 马克杯 \
--description "白色陶瓷马克杯" --tag kitchen --count 1 \
--attrs '{"wear":80,"material":"ceramic","sizes":["S","M"]}'
memories persona thing list VAULT_ID PERSONA_ID
memories persona thing get VAULT_ID PERSONA_ID --id mug
memories persona thing image VAULT_ID PERSONA_ID --id mug --file ./mug.jpg
memories persona thing delete VAULT_ID PERSONA_ID --id mug
# Single-field attrs (dotted path or JSON Pointer; create-on-set)
memories persona thing attr get VAULT_ID PERSONA_ID --id mug --path nested.wear
memories persona thing attr set VAULT_ID PERSONA_ID --id mug --path nested.wear --value 70
memories persona thing attr set VAULT_ID PERSONA_ID --id mug --path /nested/deep/x --value 1
memories persona thing attr delete VAULT_ID PERSONA_ID --id mug --path nested.deep.x
# Automation hooks (stored on world.automation; timezone = persona timezone)
memories persona automation put VAULT_ID PERSONA_ID \
--hook '{"id":"morning","trigger":{"type":"local_time","time":"07:00"},"actions":[{"type":"call_function","name":"set_weather_stub","args":{"condition":"晴","temp_c":26}}]}' \
--webhook-token secret-token
memories persona automation list VAULT_ID PERSONA_ID
memories persona automation run VAULT_ID PERSONA_ID
memories persona automation trigger VAULT_ID PERSONA_ID --id morning --source manual
memories persona automation delete VAULT_ID PERSONA_ID --id morning
# Read / history / diff (same as other cards; summary shows timezone)
memories card read VAULT_ID PERSONA_ID
memories card history VAULT_ID PERSONA_ID
memories card diff VAULT_ID PERSONA_ID REVISION_ID
memories card list --type persona
Webhook 令牌保留在加密的人格状态中,用于入站身份验证、同步和备份,但客户端响应不会返回它们。世界状态读取/设置、自动化添加/列表、普通卡片读取(包括历史读取和无人值守读取)及档案读取,在自动化视图中仅暴露 webhook_token_set。请安全保管已配置的令牌;设置替代令牌即可轮换。
同一保险库中的两张人格卡片通过普通会话卡片和每个人格的读取游标聊天;读取历史不会将消息标记为已读。发送消息会触发每个参与者的 chat_message 钩子;载荷包含 vault、from、to、message_id,不包含消息文本。
memories persona chat send VAULT_ID PERSONA_A PERSONA_B --text "Hello"
memories persona chat list VAULT_ID PERSONA_B
memories persona chat history VAULT_ID PERSONA_A --with PERSONA_B --limit 20
memories persona chat mark-read VAULT_ID PERSONA_B --with PERSONA_A
MCP
持有写入授权时可用:persona_write(可选 timezone / day_boundary)、persona_append_event、persona_world_get、persona_world_set、persona_world_rollover、persona_thing_put/get/list/delete、persona_thing_attr_get/set/delete、persona_automation_list/put/delete/run/trigger、persona_automation_webhook_allow、persona_dice_roll、persona_spinner_spin、persona_schedule_list、persona_chat_send、persona_chat_list、persona_chat_history、persona_chat_mark_read。历史和差异使用现有 memory_history / memory_diff / memory_read 工具(人格卡片修订包含 world.*;衣橱/地点/姿态档案有各自的历史)。
为智能体生成 AGENTS.md / 技能
memories default set persona VAULT/PERSONA
memories skill generate --out AGENTS.md --persona VAULT/PERSONA
memories agents generate --out ./skills/memories-persona --layout skill
生成的文件概述保险库/卡片/人格/世界操作;选择人格后,还会嵌入该人格的核心概览及世界、物品和事件的 CLI/MCP 用法。参见 README 技能生成。
同步说明(Grok Bot / Muse)
将保险库中的人格卡片及其关联档案视为权威来源。本地技能目录只是导入/导出的便利形式;导入后,智能体应读取保险库(card read / MCP memory_read),而不是过期的文件系统副本。通过 persona put 修改核心;通过普通 card put 编辑档案资产;用 append-event 追加短期行为。判断隔日、早课或晚课时,使用人格 timezone(以及可选 day_boundary)— 不要仅依据智能体宿主机时钟推断日期边界。
技能目录布局
xiaoya-persona/
SKILL.md # frontmatter name/type/version/timezone + core markdown
references/
contract.md
journal.md
look.md
lines.md
ops.md
views-index.md
view-*.md
导入将 SKILL.md 映射到人格核心,将每个 references/*.md 映射到关联档案卡片(kind = 文件名主干)。档案内容作为用户内容原样存储。
前置元数据键:timezone 或 metadata.timezone(IANA);可选 day_boundary / metadata.day_boundary(HH:MM)。
本地演示保险库
在开发机器上,可以使用仓库中的 xiaoya 样本初始化一个一次性保险库:
cargo run -p memories-core --example seed_xiaoya_persona
这会写入 /workspace/persona-demo-vault(密码 persona-demo-password),以及包含保险库和资产 id(含 timezone)的 SEED.json / README.md。对运行中的守护进程使用以下命令复现 CLI 导入:
memories persona import VAULT_ID testdata/xiaoya-persona