Concepts

Core concepts

Five primitives, one action-approval ceremony, a pluggable entity model, a push-trigger dispatch protocol, and a deliberate public/internal surface boundary. Read this once and the rest of the tool reference falls into place.

The five primitives

Every tool on the public surface composes from one of five primitives. Each primitive has a state-only read shape (you decide), an engine shape (Cortex decides), or both. Per primitive, you choose. The fifth — Dispatch — is engine-only and is how the substrate reaches back into your stack when it decides an action should happen.

Memory

A unified retrieval surface over episodic memory, conversation turns, knowledge-graph entities, and rules. Writes are atomic episodes; reads come back ranked across all four sources in a single call.

Rules

Behavioral rules with a lifecycle: candidateactive suspendedretired. Activation requires user confirmation through the operator dashboard — new rules cannot activate themselves via this surface, by design. This is the prompt-injection blast-radius limit.

Trust

Autonomy graduates per-(action class × counterparty), never globally. There are three levels:

LevelBehaviorHow it's reached
1 — askAction returns pending_approval; user must approve before the engine runs the side effect.Default for every new (action × counterparty) pair.
2 — act, notify afterAction executes; a notification fires so the user can review (and reverse) within the cooldown window.User-initiated graduation via the dashboard or trust.graduate.
3 — silentAction executes silently; only the ledger entry records it.Earned after a clean streak at Level 2. Auto-demotes on reversal.

Read with trust.check; graduate with trust.graduate. There is intentionally no trust.demote tool — demotion is system-driven on observed reversals.

Action

Send-class operations — communicate, schedule, remind — route through the action engine. The engine checks trust, fires applicable rules, and returns one of three results:

typescriptMcpActionResult
type McpActionResult =
  | { status: 'success';          decision_id?: string; message?: string }
  | { status: 'pending_approval'; decision_id: string;  message?: string }
  | { status: 'error';            decision_id?: string; error: string  };

Carry the decision_id through to action.approve or action.reject when the user resolves it. Decisions expire after 24 hours.

Dispatch

Push-mode complement to the pull-mode action approval flow. When the substrate decides an action should happen, Cortex HMAC-signs an envelope and POSTs it to a handler you registered. You execute through your app's native channels and call back trigger.complete / trigger.reverse. The full contract — envelope schema, signing, idempotency, retries, DLQ, callback semantics, the three handler tiers (HTTPS webhook today, MCP-executor V1.1, hosted channels out-of-scope) — lives on its own page:

The action approval ceremony

When an action returns pending_approval, the engine emits an ApprovalCeremonyPayload to the dashboard's decision queue (and, on the live channel, to any client subscribed via SSE). The ceremony is the contract for “how a human approves a thing an AI wants to do.”

typescriptApprovalCeremonyPayload
interface ApprovalCeremonyPayload {
  decision_id: string;
  action_class: string;                // e.g. "communicate"
  what: string;                        // 1–2 sentence plain-language summary
  why: string;                         // why Cortex wants to do this now
  counterparty?: { id: string; display_name: string };
  preview: { mime: 'text/plain' | 'application/json'; body: string };
  reversal_path: string;               // how the user can undo if approved in error
  expires_at: string;                  // ISO-8601, default +24h
}

Approving resolves the decision; the engine commits the side effect and appends a ledger entry with the approval rationale. Rejecting discards it and demotes trust if the rejection pattern matches a confidence-shaking heuristic.

Entities

Cortex's knowledge graph is parameterized by entity type. The default and dogfood implementation is person — everything in the founder's production instance is people, conversations, and the relationships between them. ADR-0018 made entity type pluggable so the same primitives serve accounts, repos, transactions, products, or anything your application defines.

The governance boundary — what isn't on this surface

Cortex runs its own agent over a much larger internal tool surface. The public MCP server deliberately exposes the smaller, trust-aware subset. The tools below exist in the codebase but are not on this endpoint, and won't be:

  • Connector / OAuth control planeconnect_calendar, disconnect_calendar, sync_calendar, list_sources. OAuth lifecycle is bound to the web dashboard for consent reasons.
  • Rule activationaccept_rule, accept_rule_candidate, transition_rule. Rule activation requires user-in-the-loop confirmation; this surface only proposes candidates.
  • Raw memory dumpslist_episodes, get_about_me. Use trust-gated memory.recall instead.
  • Agent-loop primitivesclarify_ask_user, web_search. Your client has its own clarification UI and search.
  • Internal observabilitymeta.why_this_notification, meta.why_this_action, meta.what_rules_fired. Audit lives in the dashboard, not over the wire.

This isn't a roadmap gap — it's the trust/consent architecture. The boundary is enforced by an explicit allow-list and reviewed every time the external surface version increments.

Public vs internal MCP surface (ADR-0020)

Two MCP endpoints exist in the Cortex codebase. They are not the same surface and never share a bearer token:

EndpointAudienceAuth
https://mcp.cortex.jakeselby.com/mcpPublic — the cognitive substrate consumed by third-party builders. Everything in these docs.Bearer (beta) → OAuth (V1.0)
apps/web /api/mcp (internal)Internal — consumed only by the Cortex chat app's agent runtime. Not a reference architecture; rejects any bearer other than MCP_SERVICE_TOKEN.MCP_SERVICE_TOKEN only