CortexDB Docs
Docs for CortexDB 0.14.0, the version running on CortexDB Cloud (api.cortexdb.ai).
Shared Memory

Shared Memory and the Access Model

One CortexDB for a tenant's sources, applications and org brain. Every memory has a home, every read is decided against it, and the access API says who may read what and why.

CortexDB 0.14.0 adds shared memory: one server holds a tenant's sources (Slack channels, Drive folders, tickets), its applications' memories (one home per person in each application) and its org brain (teams, groups, rollups), and one access model decides every read and write across all three.

The model fits in three sentences. Every memory has a home: the scope it was written to. Every request is made by a principal (a person, an agent acting for a person, a service), carried by a signed token. Every route that returns stored memories checks each one against that principal at its home, so a memory you may not read is never returned, whichever route you ask through.

When to use it

Use the shared-memory features when several people, agents and applications use one memory and they must not all see everything: a company brain, a support desk with per-agent notes, an application that keeps each customer's memory apart and still shares reference material. A single application that writes and reads its users' scopes with one key needs none of these features, but on cloud_shared_saas the operator key's default changed in 0.14.0: see Operator Modes and Break-Glass.

You want to…Read
Model teams, people and private spacesOrg Trees
Change who may read what, safely and in one stepChange Sets
Give each person and agent their own token, or use your identity providerIdentity and Tokens
Decide what the operator key may readOperator Modes and Break-Glass
Give applications per-person homes, sync sources with their permissions, handle agent writesApplications and Sources
Erase a person, an outside party or a tenant, or keep data under a legal holdErasure and Legal Holds
Connect ChatGPT, Claude, Cursor or another assistantConnecting AI Assistants
Upgrade an existing serverUpgrading to 0.14.0

Who may read and write what

A scope's record holds its roster: who holds which role there. There are four roles:

RoleCan
OwnerAdminister the record: its roster, its tree attributes, its children
WriterWrite memories there
ReaderRead the memories there
SummaryRead only rollup totals and items marked summary_safe (new in 0.14.0)

A roster entry can name a person, an agent, a service or a set (a named group of principals), and can carry an end date (until) or a condition (when), such as a purpose the read must declare. Sets, summary, until and when are written through change sets; the older registry routes (POST /v1/scopes, PUT /v1/scopes/members) take only owner, writer and reader entries for one principal each.

Capabilities still come first: the deployment preset and the token's caps and scopes decide what kind of request a credential may make at all, and the roster decides where. See Authorization for the capability stack.

Private regions

A record marked private starts a private region: a person's own memory, a confidential group, a private channel. A private region is read only by name, by the people its roster names. No read that starts elsewhere walks into it, whatever view it asks for, and nothing CortexDB synthesizes outside it draws on it. Under the deny operator mode even the operator key needs a break-glass header to read one, and the region's people are told first (Operator Modes).

The delivery gate

Since 0.14.0, every route that returns stored rows (recall, answer, compose, events, facts, beliefs, exports, the lifecycle stream) checks each row's home for the reader, and, for a memory an agent wrote or CortexDB derived, every source it was made from. A row the reader may not read is left out; a read of it by id answers 404, the same answer as an id that does not exist. A fact derived from a private conversation is therefore never served to someone who could not read that conversation.

Ask the server who may read what

The access API answers access questions and reads no memory:

RouteAnswers
POST /v1/access/checkMay this principal read, write or administer this scope, or read this one memory?
POST /v1/access/explainWhy: each link that decided, with the path of records and entries it used
POST /v1/access/lookup/scopesWhich scopes under a prefix may this principal read?
POST /v1/access/lookup/principalsWho may read this scope?
GET /v1/access/limits?scope=<root>The limits the server enforces for a tenant
POST /access/v1/evaluation, POST /access/v1/search/resourceThe same decisions over AuthZEN 1.0, for a policy enforcement point you already run; GET /.well-known/authzen-configuration describes them

A caller asks about its own token by default. An Owner of the scope, or the operator key, can ask about another principal by naming it in chain. A caller that holds no role at the scope gets one uniform denial (not_authorized), so the API never tells it whether the scope exists or is private.

# Alice's own token: may she write to the engineering team's scope?
curl -sS -X POST "$CORTEX_URL/v1/access/check" \
  -H "Authorization: Bearer $ALICE_TOKEN" -H "X-Cortex-Actor: user:alice" \
  -H "Content-Type: application/json" \
  -d '{"scope": "org:acme/team:eng", "role": "writer"}'
{
  "allowed": false,
  "denied_by": "person",
  "links": {
    "capability": {"allowed": true, "via": [{"kind": "capability", "field": "deployment.allow"}]},
    "confinement": {"allowed": true},
    "tenant": {"allowed": true, "via": [{"kind": "tenant", "record": "org:acme/team:eng", "field": "billing_tenant"}]},
    "person": {"allowed": false, "reason": "insufficient_role",
               "via": [{"kind": "entry", "record": "org:acme/team:eng", "field": "members", "index": 2,
                        "who": "user:alice", "role": "reader"}]}
  },
  "names": {"user:alice": "Alice Kim"},
  "role": "writer",
  "scope": "org:acme/team:eng",
  "until": null
}

Alice is a Reader of org:acme/team:eng, so a write there is refused by the person link, and the answer names the roster entry that decided it. The same question over AuthZEN, asked with the operator key:

curl -sS -X POST "$CORTEX_URL/access/v1/evaluation" \
  -H "Authorization: Bearer $CORTEX_API_KEY" -H "Content-Type: application/json" \
  -d '{"subject": {"type": "user", "id": "alice"},
       "action": {"name": "write"},
       "resource": {"type": "scope", "id": "org:acme/team:eng"}}'
# {"context":{"denied_by":"person"},"decision":false}

Reading several scopes at once

In an org tree a read does not walk the tree from a shared scope; it names what it reads. The four read routes (/v1/recall, /v1/recall/stream, /v1/answer, /v1/compose) take:

  • extra_scopes: up to 40 more scopes, searched in parallel in the same pass;
  • coverage: true: report which scopes were searched and any gap (a bound, a deadline, a private scope left out, a source whose last sync is old);
  • mounts: "default": add the scopes your application mounts (Applications and Sources);
  • beliefs: "merged": rebuild the pack's beliefs across the whole read set for this reader;
  • purpose: what the read is for (1 to 64 characters of [a-z0-9_-]). A roster entry whose when is {"purpose": "<id>"} admits a read only when it declares that purpose.
curl -sS -X POST "$CORTEX_URL/v1/recall" \
  -H "Authorization: Bearer $ALICE_TOKEN" -H "X-Cortex-Actor: user:alice" \
  -H "Content-Type: application/json" \
  -d '{"scope": "org:acme/user:alice", "extra_scopes": ["org:acme/team:eng"],
       "query": "payments migration", "coverage": true}'

The pack's coverage lists the scopes searched (org:acme/user:alice, org:acme/team:eng) and its gaps (none here).

Consistency tokens

A write can return a consistency token, and a later read can demand it, so the read sees the write on whichever node serves it. Add ?consistency=1 to a write (a change set's activation always returns one), then send "consistency": {"at_least": "<token>"} on a read, or ?at_least=<token> on a GET read. The read waits until the node has caught up, up to CORTEX_CONSISTENCY_WAIT_MS (10 s by default, 30 s at most), and answers 408 WAIT_TIMEOUT past it.

curl -sS -X POST "$CORTEX_URL/v1/experience?consistency=1" \
  -H "Authorization: Bearer $BOB_TOKEN" -H "X-Cortex-Actor: user:bob" \
  -H "Content-Type: application/json" \
  -d '{"scope": "org:acme/team:eng", "modality": "conversation",
       "content": {"kind": "message", "role": "user", "text": "The payments migration starts on 12 January."},
       "idempotency_key": "eng-decision-0001"}'
# {"event_id":"evt_…","status":"captured",…,"consistency_token":"ct1.eyJy…"}

How to turn it on

Shared memory is part of 0.14.0; there is nothing to switch on. The first start of 0.14.0 arms the data directory for the new record formats, one way: back up first (Upgrading). GET /v1/admin/version lists each feature under its capability (access_check_v1, access_changesets_v1, authzen_v1, org_tree_v1, directory_v1, scim_v1, …): gate a client on the capability, not on the version.

What a deployment that already ran 0.13.x sees change, and the switches that serve the old behaviour while you move clients, are in Upgrading.

See also

On this page