Memory Model
Ownership and scope
Every memory has four authoritative scope keys. Retrieval and quotas only ever see memories matching all four:
| Field | Source | Notes |
|---|---|---|
| user_id | Resolved from auth token | Cannot be set in request body. |
| organization_id | Resolved from API key / membership | Tenant boundary. |
| environment | API key environment | development | staging | production. |
| project_id | X-Project-ID header | Optional but strongly recommended. |
Record schema (selected fields)
id BIGSERIAL PRIMARY KEYuser_id BIGINT NOT NULLorganization_id BIGINT NOT NULLproject_id VARCHAR(40) -- prj_xxxxenvironment VARCHAR(16) NOT NULLtext TEXT NOT NULLsummary TEXTsource VARCHAR(64) -- support, chat, web, api...event_type VARCHAR(64)tags TEXT[] -- jsonb in some plansimportance REAL -- 0..1quality_score REAL -- 0..1, asynctier VARCHAR(8) DEFAULT 'hot' -- hot | warm | coldmetadata_json JSONB -- caller metadata + semantic extractionparent_id BIGINT -- summary lineageis_summary BOOLEAN DEFAULT falseexpires_at TIMESTAMPTZcreated_at TIMESTAMPTZ DEFAULT now()updated_at TIMESTAMPTZ
Vector layout
Vector collections are named org_<org_id>_env_<environment>_proj_<project_id>. This makes every retrieval query physically scoped — a misconfigured query cannot accidentally hit the wrong tenant’s vectors. Each vector is keyed by the record’s id and carries a small payload of recall-time fields (importance, tier, tags) so the ranker avoids extra round-trips to the metadata store.
Semantic enrichment
These fields are populated asynchronously by the intelligence pipeline. Until they fill in (~1–5 seconds), retrieval works purely on vector similarity + recency.
key_points— concrete, atomic facts extracted fromtext.insights— higher-order observations (causal, comparative).entities— named entities (people, orgs, products, IDs).topics— semantic labels for filtering and clustering.memory_type— fact | event | decision | insight | narrative | analysis | reference.quality_score— 0–1 estimate of usefulness.
Summary lineage
When the system summarizes a batch of memories, the resulting record has is_summary = true and parent_id pointing to the canonical group. Lossy summaries replace originals; lossless: true keeps originals so they can be re-expanded by recall.
Worked examples
A fact
{"id": 9421,"text": "User Alice prefers concise replies and uses dark mode.","source": "api","tags": ["preference", "ui"],"importance": 0.7,"memory_type": "fact","tier": "hot"}
An incident event
{"id": 18420,"text": "Customer 8821 reported login failures after MFA reset…","source": "support_ticket","event_type": "incident_resolved","tags": ["mfa", "incident"],"importance": 0.6,"memory_type": "event","entities": ["Customer 8821", "MFA"],"tier": "hot"}