Yoke

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, last n.
  • 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.

On this page