Persistence¶
What is stored, when it is recomputed, and what that costs.
Embedding lifecycle¶
flowchart TB
C["POST /api/memory"] --> C1["embed(content)"] --> C2["INSERT row + embedding_json"]
P["PATCH /api/context/{id}"] --> P1{"did content change?"}
P1 -->|no| P2["reuse stored embedding"]
P1 -->|yes| P3["recompute embedding"] --> P4["UPDATE embedding_json + version entry"]
R["GET .../relevant"] --> R1{"stored embedding<br/>present and right dimension?"}
R1 -->|yes| R2["use it"]
R1 -->|no| R3["recompute for this request only<br/><i>not written back</i>"]
Three rules, all observable:
- Every write persists an embedding. Computed inline, stored as a JSON
float array in
contexts.embedding_json. - A content edit recomputes it. Without this, an edited memory would keep ranking on its old text — the classic silent-staleness bug. It is fixed and verified.
- A missing or wrong-dimension embedding is recomputed on read, for that request only. The memory is never dropped from results for lack of a vector, but the recomputation is not persisted, so it repeats on every query until backfilled.
Storage layout¶
| Column | contexts.embedding_json (TEXT) |
| Format | JSON array of floats |
| Dimension | 128 (hashing) or 384 (semantic) |
| Index | none |
There is no pgvector, no native vector column and no ANN index. Cosine
similarity is computed in Python over the loaded candidate set.
This is a deliberate, documented choice, not an oversight: the schema is a 1:1
match for a pgvector Vector column with identical cosine semantics, so the
migration is a column type change plus a backfill, on the day the scale
justifies it. Today it does not.
Practical implications¶
Performance¶
- Write: one embedding computation on the request path. Negligible with
hashing; a MiniLM forward pass withsemantic. - Read:
O(candidates)cosine computations, plus one query embedding, plus a recomputation for every candidate whose embedding is stale. - The read cost is linear in project size. Watch it as projects grow past a few thousand memories.
Storage¶
Roughly: 128 floats × ~20 chars ≈ 2.5 KB per memory with hashing, and
384 floats ≈ 7.5 KB with semantic — JSON text, so several times what a
packed binary vector would cost. At 100 000 memories that is ~750 MB for the
semantic provider. Budget for it, or plan the pgvector migration.
Changing providers¶
Switching VENKAI_EMBEDDING_PROVIDER changes the dimension, which makes every
stored embedding incompatible. Nothing breaks and nothing is lost — but every
retrieval re-embeds every candidate, on every request, forever.
flowchart LR
A["switch provider"] --> B["all stored embeddings<br/>wrong dimension"]
B --> C["recomputed per read"]
C --> D["correct results,<br/>degraded latency"]
D --> E["backfill to recover"]
A backfill is a PATCH of each memory with its own content, which recomputes
and stores the new vector. Planned — a
dedicated backfill command; there is no built-in one today.
Decide the provider before loading data if you can.
Versioning¶
Editing a memory records a version entry.
curl -s https://api.venkai.fr/api/context/ctx_cc55e4a7bbc5/versions \
-H "Authorization: Bearer $VENKAI_API_KEY"
Restore:
curl -sX POST https://api.venkai.fr/api/context/ctx_cc55e4a7bbc5/restore \
-H "Authorization: Bearer $VENKAI_API_KEY" \
-H 'Content-Type: application/json' -d '{"version": 1}'
A restore creates a new version entry rather than rewinding history, so the trail is append-only.
Durability¶
| Aspect | Reality |
|---|---|
| Development store | SQLite file (default VENKAI_DATABASE_URL) |
| Production store | PostgreSQL |
| Data volume (Docker) | venkai-data named volume, mounted at /data |
| Backups | Not provided by Venkai. Your database, your backup policy. |
| Retention / TTL | None. Memories persist until edited or the org is deleted. |
| Bulk export | GET /api/export |
| Bulk delete | DELETE /api/organization — removes the organization and its data |
There is no per-memory delete endpoint. Supersede with a PATCH, or export and
re-import into a fresh organization.
Related¶
- Embeddings · Storage
- Production — backups and operational posture