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.
- Write:
memory.remember(state-only). - Read:
memory.recall/memory.inspect(state-only). - Correct:
memory.correct— appends a correction without overwriting the original.
Rules
Behavioral rules with a lifecycle: candidate → active → suspended → retired. 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.
- List / read:
rules.list. - Propose:
rules.create— returns acandidate_idonly. - Suspend:
rules.suspend. - Audit:
rules.provenance— every rule must justify its origin.
Trust
Autonomy graduates per-(action class × counterparty), never globally. There are three levels:
| Level | Behavior | How it's reached |
|---|---|---|
| 1 — ask | Action returns pending_approval; user must approve before the engine runs the side effect. | Default for every new (action × counterparty) pair. |
| 2 — act, notify after | Action 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 — silent | Action 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:
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:
- Register:
handler.register. - Manage:
handler.list/handler.update/handler.delete/handler.rotate_secret. - Inspect:
trigger.history/trigger.inspect/trigger.replay. - Route (V1.1):
routing.set_default_handler/routing.get_default_handler/routing.list_app_registrations. - Full contract: /pitch/docs/dispatch; recipes: /pitch/docs/recipes.
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.”
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.
- Resolve:
entity.resolve— name / email / handle / phone → canonical id. - Walk:
entity.relationships— directed edges with kind and strength.
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 plane —
connect_calendar,disconnect_calendar,sync_calendar,list_sources. OAuth lifecycle is bound to the web dashboard for consent reasons. - Rule activation —
accept_rule,accept_rule_candidate,transition_rule. Rule activation requires user-in-the-loop confirmation; this surface only proposes candidates. - Raw memory dumps —
list_episodes,get_about_me. Use trust-gatedmemory.recallinstead. - Agent-loop primitives —
clarify_ask_user,web_search. Your client has its own clarification UI and search. - Internal observability —
meta.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:
| Endpoint | Audience | Auth |
|---|---|---|
https://mcp.cortex.jakeselby.com/mcp | Public — 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 |