CortexDB Docs
Core Concepts

The Experience Envelope

The single structured payload for everything CortexDB ingests — messages, observations, tool results, documents, and triples.

The Experience Envelope is one structured shape used to ingest all forms of agent experience — messages, observations, tool results, documents, and raw triples — into lossless event-sourced memory. One shape covers every data type, so ingestion isn't fragmented across formats.

CortexDB separates the payload into functional slots: identity (who), content (the data), context (temporal/spatial grounding), and operational directives (how to process it).

Minimal envelope

{
  "scope": "org:acme/dept:eng/user:alice",
  "modality": "conversation",
  "content": { "kind": "message", "role": "user", "text": "Just got off a call with Priya at Acme." }
}

Required fields

Only scope, modality, and content are required. context (and context.observed_at) and idempotency_key are optional — a write without them returns 202. (Older docs marked observed_at and idempotency_key as required; they are not.)

Identity slots

Up to three identities; each defaults to the previous for the common "narrating yourself" case.

SlotDefaultCapability needed when set explicitly
CallerImplicit (from token)always
Observed actor= callerscope.write.on_behalf_of when ≠ caller
Subject= observed_actorscope.write.about_other when ≠ observed_actor

This supports bots writing on behalf of a user (observed_actor) and writing memories about other entities (subject).

Content kinds

content is a discriminated union — pick the shape that matches your data:

KindShapeUse for
message{ role, text, media[] }Conversational turns
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 (object needs type+datatype+value)

The role enum is enforced: user, assistant, tool, system (anything else → 422).

Modalities

ModalityTypical use
conversationChat turns, meeting transcripts
documentDocuments and long text
tool_resultStructured tool output
observationSensor readings, generic observations
feedbackRatings, corrections, thumbs up/down
importedRecords written by /v1/import/*

The value is an open-set label: it is stored verbatim and echoed by reads, and values outside the list above are accepted too (banana writes 202 and reads back unchanged). Modality does not gate extraction: with enrichment configured, every event is enriched the same way, feedback and other values included. Extraction into Facts/Beliefs requires enrichment to be configured at all — see Self-hosting defaults.

Per-event opt-out: send directives.extract: [] through bulk

To skip enrichment for one event, send directives: { "extract": [] } via POST /v1/experience/bulk (a one-item bulk request works). The single POST /v1/experience route runs enrichment regardless of the directive.

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"]
}

All context fields are optional. preceded_by chains experiences by CortexDB event id (evt_…) so the episode builder recognizes a sequence.

Language, script and time zone (v0.10.1)

Three optional hints, each gated on a capability in GET /v1/admin/version → capabilities[] (a server that doesn't list it refuses the field with 422 INVALID_BODY):

FieldWhereExampleCapability
languagetop level"hi-Latn" (BCP-47)capture_language_v1
scriptstop level["Deva"] (ISO 15924, at most 8)capture_language_v1
context.timezonecontext"Asia/Kolkata" (exact IANA name)capture_language_v1
context.refers_tocontext[{ "from": "2026-09-26", "precision": "day" }]refers_to_v1

Without hints the server still tags non-English text by detection (und-Deva, hi-Latn); English stores no tag. None of these is part of the write identity. The full rules are on Languages & Multilingual Memory.

Source descriptor (optional; added after v0.9.9)

source is an optional, provenance-bound description of the authoritative source this envelope represents and the exact fragments of it: which document, which page or turn, and a strong digest of the bytes. It is stored verbatim on the event and returned by GET /v1/events and recall (the key is absent on events written without one), so a citation can point past the event to the original artifact. Available since v0.9.13.

{
  "source": {
    "schema_version": 2,
    "source_id": "doc:policies/refunds.md",
    "modality": "text",
    "content_kind": "markdown",
    "language": "en",
    "source_fingerprint": "sha256:b493bf8a…",
    "fragments": [
      { "fragment_id": "f1",
        "locator": { "kind": "document", "page": 1, "section_path": ["Refunds"] },
        "content_fingerprint": "sha256:b493bf8a…" }
    ]
  }
}
  • Required: schema_version (always 2), source_id, modality, content_kind, source_fingerprint, fragments (1 to 1024). language is optional.
  • Fingerprints are algorithm-qualified digests of exact bytes: sha256: or blake3: plus 64 hex chars.
  • Each fragment needs fragment_id, content_fingerprint and a locator whose kind is one of conversation (session_id, turn_index, optional speaker, message_id), document (page, section_path, block_index), json (pointer), table (table, sheet, row, column, field), code (file, optional symbol, lines), log, media (page, frame_index, time_range, region, track) or generic (path, ordinal). An unknown locator field or a malformed fingerprint is 422 INVALID_BODY with the offending field_path.
  • source.modality must equal the envelope modality; a mismatch is 422 INVALID_ENVELOPE (source modality "image" does not match envelope modality "text").

CortexDB does not fetch the source. It does check one thing: a fragment's content_fingerprint must be the digest of the envelope's own text bytes, or the write is 422 INVALID_ENVELOPE (source fragment "f1" fingerprint does not match envelope text bytes). source_fingerprint, the digest of the whole source, is not checked. Otherwise the descriptor is a claim you make about where the content came from, kept immutable alongside the event.

Idempotency

idempotency_key (≤ 64 chars) is optional but recommended for safe retries. 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). Idempotency records persist across restarts, so a retry after a crash replays the same event_id (replayed_from_idempotency: true) rather than duplicating. Keys are per scope: the same key in two different scopes creates two events. They expire after ~24 h.

Operational directives

{
  "directives": {
    "extract": ["facts", "beliefs"],
    "consolidate_into": "org:acme/dept:eng",
    "confidence_floor": 0.7,
    "ttl_for_belief_layer": "P30D",
    "embed": "eager"
  }
}
FieldNotes
extractSubset of facts, entities, beliefs, episodes, understanding. [] opts the event out of enrichment — honored by /v1/experience/bulk only (see Modalities)
consolidate_intoMerge derived layers into a different scope (requires scope.write on both)
confidence_floorDrop derived records below this confidence
ttl_for_belief_layerISO-8601 duration, stored with the event; derived records don't expire from it (use retention for expiry)
embedeager / lazy / none

FAQ

Does the envelope block the write path? No — it returns 202 after the capture phase; extraction and consolidation run asynchronously.

Can it handle tool outputs? Yes — the json content kind ingests structured tool outputs / sensor readings.

How does it handle identity? Three slots (caller, observed actor, subject) let bots act on behalf of a user or write about other entities.

See also

On this page