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

Org Trees

Model a company's memory as an org tree, with teams, groups, people, compartments and private regions, and know the rules a shared scope follows.

An org tree is a tenant whose root scope is registered with "tree": {"kind": "org"}. Its scopes carry a kind (a team, a group, a person), a parent (where it sits in the chart) and optionally private. The tree decides who reads what by default, and a few rules keep shared material from mixing with private material. A tenant without an org tree is unaffected by anything on this page.

When to use it

Use an org tree for a company or team brain: several people, their AI assistants and the company's sources in one memory, with team spaces, cross-team groups and each person's own private memory. For an application that only keeps each end user's memory apart, plain scopes under one root (org:<tenant>/user:<id>) are enough; see Scopes.

Kinds

tree.kindUse it forNotes
orgThe tenant rootNo parent
teamA team or a departmentUnits are flat siblings under the root; tree.parent gives the chart, so a reorg changes parent and moves no memories
groupA project, a committee, a meeting series, a 1:1 pairCross-team, read by its roster
personOne person's own memoryUsually private
employeeAn AI employee's own working memoryUsually private
compartmentAn audience compartment of a unit or a person: aud:pub, aud:lead, aud:roll, aud:workPublished material, the unit's leads, rollups, and the part of a person's work that rolls up

A path's kind is the kind of the nearest registered record at or above it, so an unregistered path under a person's scope answers as that person's.

Private regions

"private": true on a record makes it, and everything under it, a private region: read only by name, by the people on its roster; never through an ancestor or a descendant; never an input to anything CortexDB synthesizes outside it. "private": false reopens a part of a private region (for example a person's aud:work, the part of their work that rolls up to their team). Left out, a record is private when its nearest registered ancestor that says so is.

What changes at a shared scope

An org, team, group or compartment scope is shared. Four rules hold there:

  • No walking views. holistic, descend, structured and lineage answer 422 VIEW_NOT_ALLOWED_AT_SCOPE (details.allowed_views: ["granular", "raw"]). A read that names no view reads granular, the scope's own memories. To read across scopes, name them with extra_scopes (up to 40). A person's or employee's own scope keeps the default views.
  • No auto-registration. A first write to an unregistered path under a shared scope answers 403 SCOPE_REGISTRATION_REQUIRED. Register every scope of the tree before anyone writes to it.
  • Private stays private. No walk enters a private region it did not start in, whatever a roster says, the operator key included. An export of a shared scope reads that scope's own rows.
  • No implicit blending. CortexDB never homes a synthesis of two scopes at a shared scope, and never homes a private scope's material outside its region.

Names belong in the directory, not in records: since 0.14.0 a registry write that sets tree.display_name answers 422 INVALID_SCOPE_TREE (the records are kept for ever in the log; a directory entry can be erased).

Set one up

Tokens and records must belong to the same tenant. For an org tree, make the tenant's id the root's id: org:acme is tenant acme. On a server that holds one organization and mints its own tokens, start it with:

CORTEX_API_KEY=<operator key>
CORTEX_V1_MINTER_ENABLE=1
CORTEX_V1_AUDIENCE=cortexdb:tenant:acme      # minted tokens belong to tenant acme
CORTEX_OPERATOR_INHERIT_TENANT=on            # the operator key's registrations take the root's tenant

Tokens from your identity provider name their tenant through the issuers file instead (Identity and Tokens).

Register the root, bound to its tenant, and make it an org:

curl -sS -X POST "$CORTEX_URL/v1/admin/access/tenant-binding?scope=org:acme" \
  -H "Authorization: Bearer $CORTEX_API_KEY" -H "X-Cortex-Actor: user:admin" \
  -H "Content-Type: application/json" -d '{"from": "default", "dry_run": false}'
# {"scope":"org:acme","from":"default","to":"acme","root_registered":true,…}

curl -sS -X PUT "$CORTEX_URL/v1/scopes/tree?path=org:acme" \
  -H "Authorization: Bearer $CORTEX_API_KEY" -H "Content-Type: application/json" \
  -d '{"kind": "org"}'

The binding call registers org:acme for tenant acme with user:admin (the X-Cortex-Actor) as its Owner. Under the deny operator mode (the default on cloud_shared_saas) the operator key's registrations need a break-glass header (Operator Modes). Then register the people and units. A person's private region:

curl -sS -X POST "$CORTEX_URL/v1/scopes" \
  -H "Authorization: Bearer $CORTEX_API_KEY" -H "X-Cortex-Actor: user:admin" \
  -H "Content-Type: application/json" \
  -d '{"path": "org:acme/user:alice",
       "members": [{"actor": "user:alice", "role": "owner"}],
       "tree": {"kind": "person", "parent": "org:acme", "private": true}}'

A team, with a reorg's worth of people or more, is best added as a change set, which previews who gains and loses access before anything changes. POST /v1/scopes with a tree works for a single record:

curl -sS -X POST "$CORTEX_URL/v1/scopes" \
  -H "Authorization: Bearer $CORTEX_API_KEY" -H "X-Cortex-Actor: user:admin" \
  -H "Content-Type: application/json" \
  -d '{"path": "org:acme/group:payments",
       "members": [{"actor": "user:alice", "role": "writer"}, {"actor": "user:bob", "role": "writer"}],
       "tree": {"kind": "group", "parent": "org:acme"}}'

Then give each person their own token (Identity and Tokens), and name people, teams and groups in the directory:

curl -sS -X PUT "$CORTEX_URL/v1/directory/user:alice?scope=org:acme" \
  -H "Authorization: Bearer $CORTEX_API_KEY" -H "Content-Type: application/json" \
  -d '{"display_name": "Alice Kim", "emails": ["alice@acme.example"], "title": "Engineering manager"}'

Reading in an org tree

A read at a team scope reads that team's memories. A person who reads a team and their own memory names both:

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}'

Asking for a walking view at the team scope is refused:

{"error_code": "VIEW_NOT_ALLOWED_AT_SCOPE",
 "details": {"allowed_views": ["granular", "raw"], "field": "view", "scope_kind": "team", "view": "holistic"},
 "retriable": false}

Publish and rollups

Agents draft; people publish. POST /v1/publish is how a person widens a memory: it copies an event (or a rollup) the person is served into a scope they write, with the original as its provenance, so erasing the original erases the copy. Only a person's own token publishes; an agent's token, a service and the operator key get 403 PUBLISH_PERSON_REQUIRED.

curl -sS -X POST "$CORTEX_URL/v1/publish" \
  -H "Authorization: Bearer $ALICE_TOKEN" -H "X-Cortex-Actor: user:alice" \
  -H "Content-Type: application/json" \
  -d '{"target": "org:acme/group:payments",
       "source": {"scope": "org:acme/user:alice", "event_id": "evt_01a186bd6fa679d3be8735fa0d80ffd6"}}'
# 202 {"event_id":"evt_…","status":"captured","published_from":{"scope":"org:acme/user:alice","event_id":"evt_01a186bd…"},…}

A unit (a team, or the org) whose aud:roll compartment is registered with the rollup audience gets rollups: typed monthly totals of its inputs and, where the background LLM stages run, synthesis items, built on a cadence within a daily budget (CORTEX_ROLLUP_CADENCE, CORTEX_ROLLUP_BUDGET_USD). GET /v1/rollups?scope=<unit>/aud:roll serves them, each only to a reader of every input it was built from.

See also

On this page