Memories / Documentation

On this page

Persona cards

Persona cards are cross-product identity cards for syncing personality and behavior across clients such as Grok Bot and Muse. The vault copy is authoritative.

Layers

  1. Core (slow) — how to speak and act. Stored as the persona card body and revisioned through persona_card / memories persona put (same signed revision DAG as other memories and cards).
  2. Archives / episodic (medium) — journal, look, contract, views, lines, ops, and similar. Stored as separate ordinary knowledge cards linked from the persona profile (archives[]) and mirrored as persona_archive:<kind> card links for the graph. Do not fold archive content back into core.
  3. Behavior events (fast) — append-only stream on a linked events archive via persona_append_event / memories persona append-event.

Profile fields

Field Meaning
persona_id Stable id (e.g. skill frontmatter name)
display_name Human title (e.g. 小雅 / 李雅)
aliases Alternate names
timezone IANA name (e.g. Asia/Singapore). Default Asia/Singapore when unset / on older cards
day_boundary Optional local HH:MM when the calendar day rolls (隔日). Unset = local midnight
world Live world snapshot: place/pose/look/body, generic things (id→{name,description,images,attrs JSON}), ledger (counters + counters_today, as_of_local_date), weather, tools
body / core Markdown/text behavior instructions
traits Optional string map (version, language, …)
bindings { product, agent } product bindings
memory_policy / sync_hints Optional sync guidance
archives { kind, card, vault?, title? } linked assets

summary.type is persona with persona_id, display_name, timezone, day_boundary, place, pose, look, things_count, has_weather, alias_count, archive_count, and binding_count.

Timezone, 隔日, 早课 / 晚课

Personas reason about local wall clock, not bare UTC epochs.

  1. Timezone — every persona has an IANA timezone. Import reads timezone / metadata.timezone from SKILL.md; CLI may override with --timezone Asia/Singapore. Demo default when unset: Asia/Singapore.
  2. Calendar day (隔日) — the local civil date uses midnight in that timezone unless day_boundary is set (e.g. 05:00 for a late-night rollover). Ledger / journal “new day” checks should compare local_date from the stamp helpers, not UTC date.
  3. Day periods (local hour, half-open intervals):
Period Local hours
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)

Overnight continuity for 晚课 / “still the same night” uses the span [22:00, 05:00) (night ∪ early_morning). 早课 maps to morning (and optionally late early_morning); 晚课 maps to evening / night. 4. Event stamps — persona_append_event writes both UTC and local ISO-8601 (with offset), plus timezone, period, and local_date in the events archive heading and in the JSON response (utc, local, timezone, period, local_date, timestamp unix UTC).

Core helpers (Rust): persona_local_now, classify_period, stamp_persona_now, format_event_heading in memories_core::persona.

World domain taxonomy (世界状态域)

world is a domain/tag, not a dump into core. Core (SKILL.md body) stays voice/rules only.

Layer Meaning Storage
world Domain for scene + ledger + tools + weather profile.world + archives tagged world
things Generic items (任意名称: table/cup/lamp/…) with free JSON attrs world.things + archive things/wardrobe
scene place (居所), pose (姿态), look (今日装扮摘要), body world.place/pose/look/body; detailed look in archive look
ledger counters, meals, orders, standing (常设) world.counters/counters_today/meals/orders_*/standing + archive ledger
tools dice, spinner/转盘, schedule/日程 world.dice/spinners/schedule + archives dice/spinner/schedule
weather Ambient condition / temp / humidity / wind (+ optional forecast) world.weather / world.forecast + archive weather
automation Hooks (schedule/random/webhook/attr_change/chat_message) + actions world.automation

Timezone (profile.timezone) is outside world but required for 隔日, 早课/晚课, and schedule local times.

Researched from xiaoya fixtures → implemented

Need (from look/journal/ops) Field / archive
Local day / 隔日 / 早课晚课 timezone, day_boundary, period helpers
Fixed + today’s outfit archive look; summary world.look
Inventory (any named item + free JSON attrs/images) world.things ↔ archive things/wardrobe; CLI persona thing
居所 / 姿态 world.place, world.pose
动作计数器(累计与今日) world.counters (cumulative), world.counters_today (daily)
今日吃了 world.meals
奉令未办 / 已办 world.orders_pending, world.orders_done
常设 world.standing
Body note world.body
Play utilities world.dice, world.spinners, world.schedule
Weather / short forecast world.weather, world.forecast ↔ archive weather

Automation (world.automation)

Named hooks with triggers + actions, stored on the persona world snapshot (persona timezone for cron/local schedules).

Trigger type Meaning
cron M H or M H * * DOW in persona local time
local_time HH:MM + optional days (same as schedule)
random_interval fire after random delay in [min_secs, max_secs]
random_and_timed local time + optional jitter_secs
webhook inbound HTTP / CLI --source webhook
attr_change after thing attr set/delete when thing_id matches and path filter is exact or a prefix
chat_message on persona chat send, once for each participant (same event; optional peer is the other persona card id, full or the short id from list)
manual CLI/MCP automation trigger --source manual
Action type Meaning
set_attr set thing attrs path (create-on-set)
random_attr dice / range / choice
call_function built-ins: inc, dec, clamp, roll, set_weather_stub
append_event append to events archive
webhook_out HTTP POST JSON to URL

Daemon HTTP (when --listen is on):

POST /v1/persona/{vault}/{persona}/automation/trigger
Authorization: Bearer <world.automation.webhook_token>
Content-Type: application/json

{"hook":"HOOK_ID"}

Hooks fire only through explicit automation run/trigger paths (including existing attribute-change triggers and chat_message on persona chat send). Invoking persona automation run catches up the latest missed cron/local-time slot within eight days, at most once rather than replaying all missed slots. Weekday filters still apply to the occurrence; timed-random hooks keep their jitter gate. RandomInterval is unchanged. There is no daemon automation ticker: Service::tick only expires sessions, grants, and remote state.

Outbound webhook_out actions require an exact URL in world.automation.webhook_allowlist; the default empty list blocks every POST. Manage entries explicitly with persona automation webhook-allow VAULT PERSONA --add https://example.com/hook (repeatable --add / --remove) or MCP persona_automation_webhook_allow. Entries must be HTTP(S) URLs without userinfo or whitespace, at most 2048 bytes. The list is public configuration, but the inbound webhook_token remains secret and is never returned by client views. Allowlisting does not introduce background POSTs.

Deferred (why)

Idea Why deferred
Live weather API client Agents set/query vault state; fetch outside and world-set
Phone / notifications Product-specific; not vault truth
Wallet / money play Niche; can be things + counters if needed
Companion NPCs Needs graph/cards of their own
Room graph inside residence Encode in place string for now
Hygiene / sleep as first-class Use standing / schedule / body
Auto-edit look.md 「今日」 on world-set Preserves detailed fixture; agents card put look archive

Agent sync flow

  1. world-get / persona_world_get → read the snapshot, local_date, period, revision, and stale_day. A missing as_of_local_date is stale; otherwise stale_day compares the ledger date with the persona-local date after day_boundary. weather_age_secs is the signed age of a valid RFC3339 observation, or null when missing/invalid.
  2. On 隔日, explicitly call persona world-rollover VAULT PERSONA / persona_world_rollover. This is the day-transition path that clears meals, orders_done, the short look summary, and counters_today and sets as_of_local_date to today's local ledger date. Cumulative counters, standing, things, pending orders, scene/body, tools, weather/forecast, and automation remain. No look archive or look.md 「今日」 is edited.
  3. world-set updates only supplied scene/things/ledger/tools/weather fields. It never silently rolls the day or wipes daily fields on 隔日, even when stale. It establishes a missing as_of_local_date without clearing existing fields, and always stamps world-level updated_at (UTC, Z) and updated_local (persona-local ISO with offset). persona_card stores a supplied world verbatim, including a past ledger date.
  4. Use --expected-revision HASH / MCP expected_revision from world-get to prevent lost updates: a mismatched current head hash returns a conflict before mutation. Omitting it keeps last-write-wins. Daily counters use --counter-today NAME=VALUE; --clear-counter NAME and --clear-counter-today NAME delete cumulative/daily keys (all repeatable).
  5. Archive sync touches only domains supplied by that call. Existing cards are replaced only when their body is still the generated pre-change template; handwritten prose is preserved and reported as synced_archives[kind] = "preserved". This also applies to thing put/delete/attribute sync and rollover's ledger sync. Changing things syncs both things and wardrobe. Existing look archives are always preserved.
  6. Thing-count map keys / --item ID=COUNT use stable ids, never a lookup by display name. Existing ids update count without renaming; zero removes that id only. A display name belonging to a different id is rejected. Unknown keys still legacy-create a thing with that id and name.
  7. dice-roll / spinner-spin / schedule-list provide timezone-aware tools. Detailed look/journal edits use ordinary card put; optional append-event (e.g. kind=weather) records behavior without rewriting archive prose.

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 tokens remain in the encrypted persona state for inbound authentication, sync and backup, but client responses do not return them. World get/set, automation put/list, ordinary card reads (including historical and unattended reads) and archived reads expose only webhook_token_set in automation views. Keep the provisioned token securely; set a replacement to rotate it.

Chat between two persona cards in the same vault uses an ordinary conversation card and per-persona read cursors; reading history does not mark messages read. Sending a message fires each participant's chat_message hooks; the payload includes vault, from, to, and message_id, and omits message text.

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

With a write grant: persona_write (optional 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. History and diffs use the existing memory_history / memory_diff / memory_read tools (persona card revisions include world.*; wardrobe/place/pose archives have their own history).

Generate AGENTS.md / skill for agents

memories default set persona VAULT/PERSONA
memories skill generate --out AGENTS.md --persona VAULT/PERSONA
memories agents generate --out ./skills/memories-persona --layout skill

The generated file summarizes vault/card/persona/world operations and, when a persona is selected, embeds that persona's core overview plus CLI/MCP recipes for world, things, and events. See README Skill generation.

Sync note (Grok Bot / Muse)

Treat the vault persona card and its linked archives as the source of truth. Local skill directories are import/export convenience; after import, agents should read the vault (card read / MCP memory_read) rather than a stale filesystem copy. Put core changes through persona put; put archive edits through ordinary card put on the archive asset; append short-lived behavior with append-event. When deciding 隔日, 早课, or 晚课, use the persona timezone (and optional day_boundary) — do not infer day boundaries from the agent host clock alone.

Skill directory layout

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

Import maps SKILL.md → persona core and each references/*.md → a linked archive card (kind = filename stem). Archive content is stored verbatim as user content.

Frontmatter keys: timezone or metadata.timezone (IANA); optional day_boundary / metadata.day_boundary (HH:MM).

Local demo vault

On a development box you can seed a disposable vault with the checked-in xiaoya fixture:

cargo run -p memories-core --example seed_xiaoya_persona

This writes /workspace/persona-demo-vault (password persona-demo-password) plus SEED.json / README.md with vault and asset ids (including timezone). Reproduce CLI import against a running daemon with:

memories persona import VAULT_ID testdata/xiaoya-persona