Architecture

System design overview of Cortex — dual-storage, policy-enforced, fully audited.

Cortex is built on a dual-storage architecture with a policy engine at the center. Every operation passes through RBAC checks before reaching storage.

System Overview

graph TB
    subgraph "Client Layer"
        CLI[Cortex CLI]
        MCP[MCP Server]
        API[REST API]
        UI[Dashboard UI]
    end

    subgraph "Application Layer"
        ORCH[Memory Orchestrator]
        POLICY[Policy Engine]
        EMBED[Embedding Service]
        AUDIT[Audit Logger]
        CONFLICT[Conflict Resolver]
    end

    subgraph "Storage Layer"
        PG[(PostgreSQL)]
        QDRANT[(Qdrant)]
        RG[(Redis)]
    end

    CLI --> API
    MCP --> API
    UI --> API
    API --> ORCH
    ORCH --> POLICY
    ORCH --> EMBED
    ORCH --> AUDIT
    ORCH --> CONFLICT
    ORCH --> PG
    ORCH --> QDRANT
    POLICY --> PG
    AUDIT --> PG
    CONFLICT --> PG
    EMBED --> RG

Core Components

Memory Orchestrator

The central coordinator for all read and write operations. It enforces policies, manages embeddings, and ensures audit compliance.

Write Path:

  1. Resolve scope and clearance from source/author
  2. Check for near-neighbour conflicts (>0.75 similarity)
  3. Generate embedding via OpenAI
  4. Insert Postgres row
  5. Upsert Qdrant vector point
  6. Audit the write
  7. Resolve conflicts (authority-based)
  8. Update knowledge graph (best-effort)

Read Path:

  1. Resolve caller's allowed scopes from policy matrix
  2. Generate query embedding
  3. Search Qdrant with permission filters
  4. Defence-in-depth re-check on Postgres rows
  5. Attach citations to results
  6. Audit the access (allowed or denied)

Policy Engine

Turns identity (role within an org) into an allowed-scope list. Pure functions for testability.

// Pure: role + policy rows → allowed scopes
function computeScopes(role: string, rows: PolicyRow[]): string[] {
  const allowed = new Set<string>();
  const denied = new Set<string>();
  for (const r of rows) {
    if (r.role !== role) continue;
    if (r.effect === "deny") denied.add(r.scopeKey);
    else allowed.add(r.scopeKey);
  }
  return [...allowed].filter((s) => !denied.has(s)).sort();
}

Deny always beats allow. If a role has both allow and deny for the same scope, the deny wins.

Dual Storage

StorePurposeConsistency
PostgreSQLAuthoritative source for memories, users, policies, audit logsStrong
QdrantVector embeddings for semantic searchEventual

Postgres is always authoritative. If Qdrant is unavailable, writes succeed (vector is retried) but semantic search degrades.

Embedding Pipeline

  1. Content is tokenized and truncated to 20,000 characters
  2. OpenAI embedding API generates a vector
  3. Vector is stored in Qdrant with metadata payload
  4. Metadata includes: memory_id, org_id, scope_key, clearance_min, user_id

Data Model

erDiagram
    organizations ||--o{ users : has
    organizations ||--o{ memories : contains
    organizations ||--o{ policies : defines
    organizations ||--o{ scopes : defines
    users ||--o{ memories : authors
    users ||--o{ agents : operates
    memories ||--o{ conflicts : involved_in
    memories ||--o{ audit_log : logged
    agents ||--o{ audit_log : executes

Key Tables

TablePurpose
organizationsTeam/company workspaces
usersUser accounts with roles and levels
memoriesIndexed knowledge entries
policiesRBAC rules (role → scope → effect)
scopesAllowed scope keys per org
agentsAI agent identities with API keys
audit_logComplete access history
conflictsDetected contradictions
factsDeterministic company facts
fact_valuesVersioned fact values

Security Model

Agent Authentication

Every MCP request must include a valid agent key:

Authorization: Bearer crtx_...

The key is hashed and looked up in the agents table. Each agent is bound to an acting_user, and all operations execute under that user's identity.

Permission Flow

sequenceDiagram
    participant Agent
    participant MCP as MCP Gateway
    participant Policy as Policy Engine
    participant DB as Database

    Agent->>MCP: recall("rate limiting")
    MCP->>MCP: Authenticate agent key
    MCP->>Policy: resolveScopes(agent.actingUser)
    Policy->>DB: Load policy matrix
    DB-->>Policy: Policy rows
    Policy-->>MCP: Allowed scopes
    MCP->>MCP: Vector search (scoped)
    MCP->>DB: Defence-in-depth check
    DB-->>MCP: Verified results
    MCP-->>Agent: Cited memories
    Note over MCP,DB: Audit logged

Clearance Levels

LevelRankCan Access
Contractor1Contractor+ content
Junior2Junior+ content
Senior3Senior+ content
Staff4Staff+ content
Principal5All content
CEO6All content + admin

Technology Stack

LayerTechnology
FrameworkNext.js 16 (App Router, Turbopack)
LanguageTypeScript (strict mode)
DatabasePostgreSQL (Neon)
Vector DBQdrant
ORMDrizzle
EmbeddingsOpenAI
MCP@modelcontextprotocol/sdk
StylingTailwind CSS v4 + shadcn/ui
StateZustand