Skip to main content

Data, memory & assistant (four layers)

The product promise is a per-client operations brain: collect each client's data, accrete their memory from day one, and later offer an in-business chat over that data — not a generic CRUD admin. The architecture is four layers, decided in ADR-007 (see Decisions).

One headline: a per-client operations brain in four layers — structured SoR, event-driven ingestion, per-tenant memory (activity log now + pgvector next), and an assistant that is retrieval over SoR + memory through the existing engine.

The four layers

  1. System of Record — the structured "what is": per-client domain data (person/identity now; offering, enrollment, payment, certificate next), modeled in @tedos/domain. Nouns and state. See Product SoR.
  2. Ingestion — connectors + tenant inbound webhooks → upsert the SoR and append the activity log, event-driven: external black box → webhook → SoR upsert → enqueue objective. No new state-machine engine — state lives in the SoR, transitions are short engine objectives.
  3. Memory — two stores per tenant:
    • a per-tenant activity log — an append-only event table, written on every SoR write. Built from day one, so memory accretes from the first write.
    • pgvector semantic memory — embeddings of the client's content/events, in the same product Postgres (no separate vector DB). Built in a later effort.
  4. Assistant — a per-client chat over SoR + memory, implemented as retrieval + the existing orchestrator/evals/human-gate (no new engine). Staff-facing first (the client's back-office); end-user chat later.

Locked decisions (ADR-007)

#DecisionWhat it sets
D1Schema-per-client isolation — one cluster, a schema per project, a control/registry schema for the project listA tenant-scoped DB accessor selects the schema; the person/identity tables become the per-client template
D2pgvector in the product Postgres — no separate vector DBMemory embeddings live beside the SoR; one operational surface, one backup, one export
D3Activity log from day oneThe assistant is later just retrieval, not a backfill project
D4Chat = retrieval + the existing engineReuses the orchestrator + evals + the human gate; adds a retrieval step, nothing more
D5Staff chat before end-user chatThe client's back-office is the safer first consumer

Why schema-per-client

Schema-per-client is the deliberate middle option in the isolation spectrum:

ModelIsolationExportVerdict
Shared DB, tenant_id columnweakesthard (filtered dumps)rejected — fails the clean-export promise
Schema-per-client (one cluster)strong (schema boundary)clean (per-schema dump)chosen
Database-per-clientstrongestcleanestescalate later (data-residency / scale ceiling)

The tenant-scoped accessor (D1) is the seam that makes escalating to database-per-client a mechanical move, not a rewrite.

Hard rules

  • Tenant/client data never enters the dev-workflow graph — the product SoR + memory are a separate database from the Plan Engine.
  • Per-client isolation is enforced by the schema boundary, not query discipline — all SoR/memory access goes through the tenant-scoped accessor.
  • Secrets/PII never leak across tenants and never enter the dev corpus; fiscal PII stays masked.

Sequencing

  • Layers 1–2 first: schema-per-client SoR + ingestion contract + activity log.
  • pgvector semantic memory next.
  • Assistant later (staff chat first).
  • Real connectors (Moodle, store, events, payments/bank, CFDI, WhatsApp) land in later efforts; the ingestion contract ships a connector seam now. See Engine.