Skip to content

1.5 data

wiki[bot] edited this page Aug 23, 2026 · 3 revisions

1.5. Data & Storage

3F state lives in four stores, each chosen for its access shape:

Store Holds Accessed via
PostgreSQL 16 Conversations, dead-letter envelopes, per-session config Prisma 7
MinIO Raw image payloads (bytes) S3 API (modules/minio)
KeyDB Queue state, pub/sub, cancellation markers, ephemeral caches BullMQ / ioredis
Qdrant Long-term memory records + the AI's cognition document REST (modules/qdrant)

Prisma schema (server/prisma/schema.prisma)

The generated client lives at server/src/generated/prisma (checked-in build artifact of prisma generate, produced during image builds and the install step). Prisma 7's prisma-client generator is used; the datasource URL comes from environment (POSTGRES_URL via prisma.config.ts).

HarnessConversationharness_conversation

One row per exchange request: composite key (sessionId, conversationId, requestId), optional title, and JSON content holding the full exchange (prompt, messages, phases, structured results). Indexed by (sessionId, conversationId) for session-scoped loading — this is what lets the dashboard reopen a conversation exactly where it left off.

HarnessDlqharness_dlq

Persisted dead-letter envelopes keyed by requestId:

  • identity: queueName, jobId;
  • status lifecycle: DlqStatusFailedActiveClearedRemoved;
  • context: payload (JSON), retryConfig, failureHistory, failedReason;
  • bookkeeping: attemptsMade / totalAttempts, failedAt, nextRetryAt.

See 1.3 for how records arrive here and 1.1 for the DLQ API that manages them.

HarnessConfigharness_config

Per-session workbench configuration (key sessionId): selectedModel, preprocessing (JSON), providerOverrides (JSON), and the memory space ids memoryPartition / memoryCognition — the user-set memory overrides (see the memory section below). This is the server-side state behind the SysCtl area (see 2-dashboard): overrides set in the UI are persisted here and applied by the harness on the next request.

HarnessShownMediaharness_shown_media

Media the user has already been shown inside a conversation, keyed by deterministic identity: image entries carry a normalized content fingerprint (fp:) or storage hash (sh:), video entries carry canonical provider keys. Written at respond time from the guarded final data; read at sanitize time so media-list follow-ups never repeat already-shown content. Rows are purged when their conversation or session is deleted.

HarnessProviderOverrideharness_provider_override

Per-provider override rows (key provider): serper, brightData, youtube, sources, ollama, memory. values holds the provider config (API keys encrypted at rest — see the secrets cipher below). The memory row carries the memory system variables (cognitionLimit — the cognition profile character cap), written by PUT /memory-overrides and layered over the MEMORY_COGNITION_LIMIT env baseline.

HarnessPlaylistharness_playlist

A named playlist scoped to one conversation (composite key (sessionId, conversationId, name)). videos is a JSON array of VideoGalleryItem; the active playlist is the queue. Rows are purged when their conversation is deleted. Backed by the /api/v1/playlists controller (see 1.1).

StockMarketBarstock_market_bar

Cached end-of-day OHLCV bar for one ticker and trading date (composite key (ticker, date)): open/high/low/close, optional adjustedClose, volume, fetchedAt. Final bars are immutable; the repository re-fetches the most recent days so late updates (post-close uploads, restated adjusted closes) propagate.

StockMarketHistoryRangestock_market_history_range

Ledger of the [from, to) date windows already fetched per ticker (composite key (ticker, fromDate, toDate)). Trading days skip weekends/holidays, so coverage cannot be derived from the bars alone — requests only backfill intervals not in this ledger. Together the two tables back the /api/v1/stock-data endpoints (see 1.1).

MinIO — image payloads

Images uploaded with POST /harness are stored per conversation, addressed by content hash:

storage/:sessionId/:conversationId/:hash

Properties:

  • Dedup by construction — the dashboard sends known hashes in sessionMetadata; the storage API exposes an exists probe, so identical images are never re-uploaded or re-stored.
  • Stable addressing — hash-as-key makes payloads idempotent and replay-safe (DLQ reinstatement can re-reference the original bytes).
  • Lifecycle — whole-conversation deletion is one call (DELETE /storage/:sessionId/:conversationId).

MinioHealthIndicator participates in /health/ready; bucket bootstrap happens at startup.

KeyDB

Configuration (server/keydb.conf, mounted into the container): password-protected (requirepass redis for dev), 500 MB memory ceiling, 2 I/O threads, RDB persistence. It backs:

  1. BullMQ structures (waiting/active/failed sets, delayed jobs);
  2. Socket.IO adapter state for multi-instance room routing;
  3. Lightweight runtime markers (cancellation, overrides cache).

Everything on KeyDB is rebuildable — the durable truth is PostgreSQL + MinIO. Wiping the keydb_data volume mid-session costs, at most, in-flight queued jobs (and those get re-instated, see 1.3).

Qdrant — long-term memory

Feature-gated (MEMORY_ENABLED, default off). One collection per embedding model (name suffixed with the model; vector size probed from the live model at bootstrap, Cosine distance). Payload keyword indexes cover every filtered field; the two space identity keys carry Qdrant's is_tenant multitenancy marker.

One point = one record whose payload text IS the record (no chunk layer — the transcript already lives in HarnessConversation). Point ids are deterministic (sha256 of a seed), so re-stating a record overwrites it in place. Every point belongs to exactly one space, folded into the identity key:

  • memory_partition — the user's fact space: statements the user made or asked to remember (preferences, contact details, notable gathered facts). Written two ways: the agentic memoryRemember tool (sync store) and the background vectorize pipeline (extract → embed → store, one job per turn-side, roles user/assistant).
  • memory_cognition — the AI's cognition space: the assistant's derived, accumulated understanding of the user in two forms. (1) ONE structured profile document per cognition key (tags ['cognition','profile']): stable identity and durable traits — name, language, timezone, expertise[], goals[], communication{style,detailLevel,formality}, preferences{}, likes[], dislikes[], interests[] — rewritten wholesale by the memory-profile vectorize-queue job after every answered turn, serialized JSON capped by the memoryCognitionLimit system variable (env baseline MEMORY_COGNITION_LIMIT, default 5000, clamped 500–32000; runtime override via PUT /memory-overrides, persisted globally in the provider_overrides row memory). (2) Derived insight records (tags ['cognition','insight'], ≤500 chars each, id seeded on the text so repeats overwrite): the DEPTH behind profile topics — the profile doubles as the routing map: its lean topic values ("cars", "Linux") token-match against the prompt at respond time (word-boundary, deterministic — no extra model call) and sharpen the insight query for the hot facet (likes.cars style paths are stored on each insight for observability); probe fires only when insights exist. Both personalize every answer as marked private system context (never quoted as user-stated facts; disclosed plainly when asked); the user can also wipe the space (memoryDelete with cognition=true — purges profile + insights).

Reads: MemorySearchService embeds the query as the full text AND per-sentence variants, merges hits by best score — sentence-dense records are matched on their strongest segment. Records have no exposed ids: exact-text equality is the deletion identity.

Retention/deletion: sysctl prune (DELETE /qdrant/memory) wipes a partition (facts AND cognition) or one conversation (facts only — cognition transcends conversations by design); DELETE /qdrant/text deletes by filters (exact text / contains / tags / conversation / request, capped at 50 matches per call) or the cognition space (cognition=true — batched purge of profile + insights). Unscoped deletes are rejected.

Secrets cipher

Provider API keys stored in HarnessProviderOverride are encrypted at rest with AES-256-GCM (modules/secrets, SecretsCipherService, keyed by TRIPLEF_SECRETS_KEY). The provider-overrides API returns masked keys and decrypts only when a provider actually needs the value, so secrets never appear in plaintext in the database or the UI.

Image preprocessing (sharp)

modules/sharp generates image variants before model consumption — resized/normalized derivatives that improve vision-model throughput and OCR quality without touching the stored original. Effective settings are configurable per session (HarnessConfig.preprocessing), pushed from the dashboard's PProc/SysCtl controls; the server treats client overrides as the effective config on the next request.

Clone this wiki locally