Skip to content

Database Layer

Construct uses SQLite for all persistent storage, accessed through Kysely (a type-safe SQL query builder). The database connection and custom Kysely dialect are provided by @repo/db. Construct’s schema extends CairnDatabase from @repo/cairn with app-specific tables.

FileRole
src/db/schema.tsConstruct-specific table types (extends CairnDatabase)
src/db/queries.tsAll database query functions
src/db/migrate.tsMigration runner
src/db/migrations/Individual migration files (001-010)

The createDb() function and custom Kysely dialect live in @repo/db. See DB package docs for details on the dialect and pragma configuration.

Construct’s database includes all Cairn tables plus four app-specific tables:

TablePurpose
memoriesLong-term facts, preferences, notes with FTS5 + embeddings
memories_ftsFTS5 virtual table synced via triggers
conversationsGroups messages by source + external ID. Includes observation watermark columns.
messagesIndividual messages (Construct extends with telegram_message_id)
observationsCompressed conversation summaries (Construct adds expires_at)
graph_nodesEntities extracted from memories
graph_edgesRelationships between entities
ai_usageLLM token/cost tracking

See Cairn docs for full Cairn schema details.

Construct’s messages table extends Cairn’s base with:

ColumnTypeNotes
telegram_message_idinteger (nullable)Telegram message ID for cross-referencing (migration 004)

Construct-Specific: observations (extended)

Section titled “Construct-Specific: observations (extended)”

Construct adds to Cairn’s observations table:

ColumnTypeNotes
expires_attext (nullable)ISO datetime; expired observations filtered from context (migration 010)

Reminders and recurring tasks.

ColumnTypeNotes
idtext (PK)nanoid
descriptiontextHuman-readable description
cron_expressiontext (nullable)Cron string for recurring schedules
run_attext (nullable)ISO 8601 datetime for one-shot schedules
messagetextNOT NULL; stores description as placeholder when using instruction mode
prompttext (nullable)Agent instruction to execute when fired (migration 008)
chat_idtextTelegram chat ID to deliver to
activeinteger1 = active, 0 = cancelled. Default 1
last_run_attext (nullable)Last execution timestamp
created_attextAuto-set

Index: idx_schedules_active

Key-value store for application settings.

ColumnTypeNotes
keytext (PK)Setting name
valuetextSetting value
updated_attextAuto-set

Stores API keys and tokens for extensions.

ColumnTypeNotes
keytext (PK)Secret name
valuetextSecret value
sourcetext'agent' or 'env'. Default 'agent'
created_attextAuto-set
updated_attextAuto-set

Tracks interactive questions sent to users via Telegram (used by telegram_ask tool).

ColumnTypeNotes
idtext (PK)nanoid
conversation_idtextReferences conversations
chat_idtextTelegram chat ID
questiontextThe question text
optionstext (nullable)JSON array of option strings
telegram_message_idinteger (nullable)Telegram message ID of the ask
created_attextAuto-set
resolved_attext (nullable)When the user responded
responsetext (nullable)The user’s response

Migrations use @repo/db’s migration runner. Each migration exports up() and optionally down().

MigrationDescription
001-initial.tsBase tables: memories, conversations, messages, schedules, ai_usage, settings
002-fts5-and-embeddings.tsFTS5 virtual table, sync triggers, embedding column on memories
003-secrets.tsCreates the secrets table
004-telegram-message-ids.tsAdds telegram_message_id column and index to messages
005-graph-memory.tsCreates graph_nodes and graph_edges tables
006-observational-memory.tsCreates observations table, adds watermark columns to conversations
007-observation-promoted-at.tsAdds promoted_at column to observations (for promoter tracking)
008-schedule-prompt.tsAdds prompt column to schedules (agent instruction mode)
009-pending-asks.tsCreates pending_asks table
010-observation-expires-at.tsAdds expires_at column to observations

Convention: additive only — never drop tables or columns.

All database queries are in src/db/queries.ts. Key functions:

Provided by @repo/cairn/db/queries — see Cairn docs.

  • getOrCreateConversation(db, source, externalId) — Finds or creates conversation. Updates updated_at.
  • getRecentMessages(db, conversationId, limit) — Last N messages (chronological)
  • saveMessage(db, message) — Inserts a message with optional telegram_message_id
  • updateTelegramMessageId(db, internalId, telegramMsgId) — Associates Telegram message ID
  • getMessageByTelegramId(db, conversationId, telegramMsgId) — Lookup by Telegram ID
  • createSchedule(db, schedule) — Inserts a schedule
  • listSchedules(db, activeOnly) — Lists schedules
  • cancelSchedule(db, id) — Sets active = 0
  • markScheduleRun(db, id) — Updates last_run_at
  • getLastResolvedAsk(db, chatId) — Returns the most recently resolved ask within the past 5 minutes (used by self-edit rejection detection)
  • trackUsage(db, usage) — Insert usage record
  • getUsageStats(db, opts?) — Aggregated usage stats
  • getSetting(db, key) / setSetting(db, key, value) — Settings CRUD

All entity IDs use nanoid() (21-character URL-safe string).