Store & data model
Pluggable storage with stable data structures.
The store provides persistence through a pluggable backend. One interface
(StorageBackend), many providers — in-memory, SQLite, and more to come.
Entities
The framework defines stable, generalized data structures for everything it touches:
- Principal — an abstract actor (human or otherwise).
- Platform — a distinct communication surface (Slack, Discord, SMS, ...).
- Persona — the "face" a Principal wears on a specific Platform.
- Chat — a conversation container on a Platform.
- ChatParticipant — a Persona's membership record in a Chat.
- Turn — a unit of "someone took a turn"; groups Messages produced together.
- Message — an immutable atomic communication with a categorical type and a
multi-part
content[]array. - MessageRelation — a directed edge (edit / reply / reaction) between Messages.
- ReadState — per-(participant, message) seen marker.
Using a backend
import { MemoryStorageBackend, SqliteStorageBackend } from "yoke";
const runtime = Runtime({
storage: new MemoryStorageBackend(), // in-process, no durability
// or:
storage: new SqliteStorageBackend("yoke.db"), // bun:sqlite, zero deps
});Message
The message is the heart of the model — and the same Message type used for
inference:
const Message = z.object({
id: MessageId,
chatId: ChatId.optional(), // unset = turn-private
turnId: TurnId,
personaId: PersonaId.optional(),
role: MessageRole, // system | user | assistant | tool
type: MessageType,
content: z.array(MessageContent).min(1),
receivedAt: z.coerce.date(),
createdAt: z.coerce.date(),
updatedAt: z.coerce.date(),
metadata: z.record(z.string(), z.unknown()).optional(),
});Content parts are a discriminated union: text, image, audio, video,
file, reaction, tool_call, and tool_response.
Key invariant: chatId set = chat-visible (shared history); unset =
turn-private (tool calls, internal reasoning, private scratch).
Key views
MessageStore.listForAgentView(chatId, chatParticipantId, n)returns the per-participant transcript: all messages from this participant's turns (including turn-private) plus chat-visible messages from other turns, merged chronologically, lastn.TurnStore.listOrphaned({ olderThanMs, now })supports orphan recovery.StorageBackend.transaction?is an optional atomic scope.
Errors & pagination
Backends throw StorageNotFoundError / StorageConflictError for predictable
failure cases. ListOptions / ListResult give cursor pagination.
Full contract
The per-entity store interfaces (ChatStore, PrincipalStore, PersonaStore,
ChatParticipantStore, TurnStore, MessageStore, MessageRelationStore,
ReadStateStore) and the composite StorageBackend live in
packages/core/src/store/storage.ts. New backends implement that interface —
never the other way around.