The structured input to /v1/experience. One shape covers messages, observations, tool results, documents, and triples.
The Experience Envelope
A single, structured payload for everything CortexDB ingests. The envelope replaces what older designs split across messages, add_memory, add_event, ingest_document, and link_triple.
Minimal shape
{
"scope": "org:acme/dept:eng/user:alice",
"modality": "conversation",
"content": { "kind": "message", "role": "user", "text": "Just got off a call with Priya at Acme." },
"context": { "observed_at": "2026-05-15T10:42:00Z" },
"idempotency_key": "alice-chat-001"
}
The three identity slots
| Slot | Default | Cap needed when set explicitly |
|---|---|---|
| Caller | Implicit (from token) | always |
| Observed actor | = caller | scope.write.on_behalf_of when ≠ caller |
| Subject | = observed_actor | scope.write.about_other when ≠ observed_actor |
Why three slots: support bots writing on behalf of a user (observed_actor), and writing memories about entities or other actors (subject). The three default neatly to each other for the common case where you're just narrating yourself.
Content kinds
The envelope's content field is a discriminated union:
| Kind | Shape | Use for |
|---|---|---|
| message | { role, text, media[] } | Conversational turns (user / assistant / tool / system) |
| text | { text } | Free-form text observations |
| json | { data } | Structured tool outputs, sensor readings |
| blob_ref | { blob_id } | Reference an already-uploaded blob |
| triple | { triple: {subject, predicate, object} } | Direct fact insertion (the old /v1/link) |
The role enum: user, assistant, tool, system.
Modality
| Modality | Triggers extraction? | Notes |
|---|---|---|
| conversation | yes | Fact + episode extraction |
| document | yes | Document chunking + entity extraction |
| tool_result | yes | JSON inspection |
| observation | yes | Generic |
| feedback | no | Marked as supporting evidence only |
| imported | yes (with importer-specific mapping) | Used by /v1/import/* |
Unknown modality values are stored verbatim but don't trigger structured extraction.
Context
{
"observed_at": "2026-05-15T10:42:00Z",
"source_recorded_at": "2026-05-15T10:41:58Z",
"location": { "city": "Bangalore" },
"preceded_by": ["evt_01HX..."],
"intent": "deal_status_update",
"labels": ["acme", "renewal"]
}
observed_at is the only required field. preceded_by lets you chain experiences across asynchronous tool calls so the episode builder can recognise the sequence.
Idempotency
idempotency_key is mandatory (≤ 64 chars). Same key + same body = no-op. Same key + different body = 409 idempotency_conflict. Use a deterministic key per source-system message (e.g., slack:C123:T456:1747293720) so retries are safe.
Directives (optional)
Override per-call behaviour:
{
"directives": {
"extract": ["facts", "beliefs"],
"consolidate_into": "org:acme/dept:eng",
"confidence_floor": 0.7,
"ttl_for_belief_layer": "P30D",
"embed": "eager"
}
}
| Field | Notes |
|---|---|
| extract | Subset of facts, entities, beliefs, episodes, understanding |
| consolidate_into | Merge derived layers into a different scope (requires scope.write on both) |
| confidence_floor | Drop derived records below this confidence |
| ttl_for_belief_layer | ISO 8601 duration (e.g. P30D) |
| embed | eager / lazy / none |