Package: store/. SQLite persistence for all runtime state.
The database opens in WAL mode with a 5-second busy timeout. WAL allows concurrent readers while a single writer holds the lock. Multiple daemons (gated, timed, dashd, onbod) open the same messages.db file.
Schema versioning uses PRAGMA user_version. On startup, store.Open reads the current version and applies any pending migration functions in order. Migrations are Go functions (not SQL files at this layer) registered in a slice; the slice index is the version number.
External daemons (timed) run their own migration runner keyed by service name in the shared migrations table, allowing each daemon to apply its own schema additions idempotently.
| Table | Key columns | Purpose |
|---|---|---|
messages |
id, chat_jid, sender, content, timestamp, verb, source, group_folder, is_from_me | All messages. Primary poll target. source and group_folder exist for outbound audit trail but are not yet populated. |
chats |
jid (PK), name, channel, is_group, errored | Known JIDs. errored flag set on agent failure; cleared on next successful run. |
routes |
id, jid, seq, type, match, target | Routing rules evaluated by the gateway. seq controls order within a JID. |
registered_groups |
jid (PK), folder, trigger_word, requires_trigger, container_config (JSON), parent, slink_token | Active groups. container_config carries per-group mounts, timeout, sidecars as JSON. |
sessions |
group_folder + topic (composite PK), session_id | Current Claude Code session ID. topic is empty string for the default session. |
session_log |
id, group_folder, session_id, started_at, ended_at, result, error | Historical session records for audit and debugging. |
system_messages |
id, group_id, origin, event, body | XML system events. Flushed and prepended to agent prompt at invocation time. |
scheduled_tasks |
id (PK), owner, chat_jid, prompt, cron, next_run, status, created_at | Cron and one-shot tasks. Polled by timed. cron null = one-shot. |
router_state |
key (PK), value | Persisted gateway state: lastTimestamp, lastAgentTimestamp. |
auth_users |
sub (unique), username (unique), hash | Web auth accounts. hash is argon2id. |
auth_sessions |
token_hash (PK), user_sub, expires_at | JWT refresh token hashes. Short-lived access tokens are stateless. |
user_groups |
user_sub + folder (composite PK) | Restricts a web user to specific group folders. Absent = operator (unrestricted). |
email_threads |
thread_id (PK), chat_jid, subject | Maps IMAP thread IDs to JIDs for reply threading. |
onboarding |
jid (PK), status, world_name, prompted_at | Per-JID onboarding state. Owned by onbod. |
Each daemon owns its own tables and applies its schema idempotently. gated owns the core tables via store.Open. timed creates scheduled_tasks via its own migration runner (idempotent; no-ops if the table already exists from gated). onbod owns onboarding. Cross-daemon reads are read-only.
messages has source, group_folder, and is_from_me columns for recording outbound messages. StoreOutbound() is not yet implemented โ the columns exist and are included in the schema but are not populated. Full spec: specs/7/22-audit-log.md.
| Function | Description |
|---|---|
PutMessage | Insert inbound message, upsert chat record |
NewMessages(since) | Return unprocessed messages after timestamp |
MessagesSince(jid, cursor) | Per-chat message slice for agent prompt |
FlushSysMsgs(groupID) | Return and delete pending system messages for group |
GetSession / SetSession / DeleteSession | Session ID by (folder, topic) |
ActiveWebJIDs(since) | Web JIDs with messages newer than timestamp |
GetRegisteredGroups | All active groups for routing |