Skip to content
Chuyue Wang edited this page May 19, 2026 · 4 revisions

API Reference

Cortex exposes a REST API on http://127.0.0.1:9472 and a WebSocket server on ws://127.0.0.1:9473.

REST API

Base URL: http://127.0.0.1:9472

All request and response bodies are JSON. Timestamps use monotonic seconds (time.monotonic()).

Authentication

All mutating HTTP routes require a capability token, sent as Authorization: Bearer <token> (canonical) or via the legacy X-Cortex-Auth-Token: <token> header. The token is generated at first daemon start and persisted with mode 0600 at ~/Library/Application Support/Cortex/auth.token. Missing or wrong tokens return 401 Unauthorized with WWW-Authenticate: Bearer. GET /health accepts an optional token (the supervisor liveness probe must reach the daemon without owning the token).

Every mutating response also carries an X-Cortex-Request-ID header. Clients may set their own on the request to correlate across logs (F19); otherwise the server generates one.


Health & Status

GET /health

Health check for all registered services. Capability token is optional.

Response:

{
  "status": "healthy",
  "services": {
    "state_engine": "up",
    "context_engine": "up",
    "llm_engine": "up",
    "intervention_engine": "up"
  },
  "uptime_seconds": 3421.5
}

GET /status/current

Current cognitive state, confidence, and signal quality.

Response:

{
  "state": "FLOW",
  "confidence": 0.87,
  "signal_quality": {
    "physio": 0.92,
    "kinematics": 0.88,
    "telemetry": 0.95,
    "overall": 0.91
  },
  "features": null,
  "timestamp": 12345.6
}

Returns null fields if no state has been computed yet.


Capture & Features

POST /capture/frame_meta

Submit frame metadata from the capture service.

Request body: FrameMeta

{
  "frame_id": 42,
  "timestamp": 1000.5,
  "width": 640,
  "height": 480,
  "brightness": 127.3,
  "blur_score": 85.2,
  "face_detected": true,
  "face_confidence": 0.97,
  "quality_pass": true
}

Response:

{ "status": "ok", "timestamp": 1000.51 }

POST /features/physio

Submit physiology features from the physio engine.

Request body: PhysioFeatures

{
  "pulse_bpm": 72.0,
  "pulse_quality": 0.9,
  "pulse_variability_proxy": 55.0,
  "hr_delta_5s": 0.5,
  "valid": true
}

POST /features/kinematics

Submit kinematic features from the kinematics engine.

Request body: KinematicFeatures

{
  "blink_rate": 16.0,
  "blink_rate_delta": 0.0,
  "blink_suppression_score": 0.0,
  "head_pitch": 0.0,
  "head_yaw": 0.0,
  "head_roll": 0.0,
  "slump_score": 0.1,
  "forward_lean_score": 0.1,
  "shoulder_drop_ratio": 0.05,
  "confidence": 0.9
}

POST /features/telemetry

Submit telemetry features from the telemetry engine.

Request body: TelemetryFeatures

{
  "mouse_velocity_mean": 400.0,
  "mouse_velocity_variance": 5000.0,
  "mouse_jerk_score": 0.1,
  "click_burst_score": 0.1,
  "click_frequency": 0.5,
  "keyboard_burst_score": 0.2,
  "keystroke_interval_variance": 500.0,
  "backspace_density": 0.05,
  "inactivity_seconds": 2.0,
  "window_switch_rate": 5.0
}

All feature endpoints return { "status": "ok", "timestamp": <float> }.


State Inference

POST /state/infer

Compute cognitive state from a fused feature vector.

Request body:

{
  "feature_vector": {
    "pulse_norm": 0.3,
    "hrv_norm": 0.6,
    "blink_norm": 0.5,
    "posture_norm": 0.2,
    "mouse_velocity_norm": 0.4,
    "mouse_jerk_norm": 0.1,
    "click_burst_norm": 0.1,
    "keyboard_burst_norm": 0.2,
    "inactivity_norm": 0.05,
    "window_switch_norm": 0.3,
    "backspace_norm": 0.05,
    "complexity_norm": 0.4,
    "timestamp": 1000.5
  },
  "signal_quality": {
    "physio": 0.9,
    "kinematics": 0.85,
    "telemetry": 0.95,
    "overall": 0.9
  }
}

Response:

{
  "estimate": {
    "state": "FLOW",
    "confidence": 0.87,
    "scores": {
      "flow": 0.87,
      "hypo": 0.05,
      "hyper": 0.08,
      "recovery": 0.0
    },
    "reasons": ["Good HRV", "Normal blink rate", "Steady input"],
    "signal_quality": { "physio": 0.9, "kinematics": 0.85, "telemetry": 0.95, "overall": 0.9 },
    "timestamp": 1000.5,
    "dwell_seconds": 45.2
  },
  "timestamp": 1000.52
}

Context Building

POST /context/build

Build task context from workspace adapters.

Request body:

{
  "include_editor": true,
  "include_terminal": true,
  "include_browser": true
}

Response:

{
  "context": {
    "mode": "coding_debugging",
    "active_app": "vscode",
    "current_goal_hint": "Debugging TypeScript type error",
    "complexity_score": 0.72,
    "editor_context": {
      "file_path": "src/components/App.tsx",
      "visible_range": [45, 95],
      "symbol_at_cursor": "handleSubmit",
      "diagnostics": [
        {
          "severity": "error",
          "message": "Type 'string' is not assignable to type 'number'",
          "line": 67,
          "column": 12,
          "source": "typescript",
          "code": "TS2322"
        }
      ],
      "recent_edits": [],
      "visible_code": "function handleSubmit() { ... }"
    },
    "terminal_context": null,
    "browser_context": null
  },
  "available": true,
  "timestamp": 1000.52
}

LLM Planning

POST /llm/plan

Request an intervention plan from the LLM engine.

Request body:

{
  "state_estimate": { "state": "HYPER", "confidence": 0.91, "...": "..." },
  "task_context": { "mode": "coding_debugging", "...": "..." }
}

Response:

{
  "plan": {
    "intervention_id": "int_a1b2c3d4e5f6",
    "level": "simplified_workspace",
    "situation_summary": "You've been switching between 5 files with type errors for 12 minutes.",
    "headline": "Focus on one error at a time",
    "primary_focus": "Fix the TS2322 type error in App.tsx line 67",
    "micro_steps": [
      "Look at the error on line 67",
      "Check the expected type in the interface",
      "Update the value to match"
    ],
    "hide_targets": ["sidebar", "terminal"],
    "ui_plan": {
      "dim_background": true,
      "show_overlay": true,
      "fold_unrelated_code": true,
      "intervention_type": "simplified_workspace"
    },
    "tone": "direct"
  },
  "fallback_used": false,
  "timestamp": 1002.1
}

Intervention Control

POST /intervention/apply

Apply an intervention plan to the workspace.

Request body:

{
  "plan": { "intervention_id": "int_a1b2c3d4e5f6", "...": "..." }
}

Response:

{
  "applied": true,
  "snapshot": {
    "intervention_id": "int_a1b2c3d4e5f6",
    "timestamp": 1002.2,
    "fold_states": [],
    "editor_visible_range": [45, 95],
    "tab_visibility": [],
    "active_tab_id": null,
    "overlay_present": false,
    "terminal_scroll_position": null
  },
  "timestamp": 1002.3
}

POST /intervention/restore

Restore workspace to pre-intervention state.

Request body:

{
  "intervention_id": "int_a1b2c3d4e5f6",
  "user_action": "dismissed"
}

Response:

{
  "restored": true,
  "outcome": {
    "intervention_id": "int_a1b2c3d4e5f6",
    "started_at": "2025-01-15T10:30:00",
    "ended_at": "2025-01-15T10:32:15",
    "duration_seconds": 135.0,
    "user_action": "dismissed",
    "recovery_detected": false,
    "recovery_confidence": null,
    "workspace_restored": true,
    "restore_errors": []
  },
  "timestamp": 1137.5
}

Valid user_action values: dismissed, engaged, snoozed, timed_out, natural_recovery, system_cancelled.


Stress & Learning

GET /api/stress-integral

Current cumulative standardized HRV-deficit integral (the "biological pomodoro"). When current_value / threshold ≥ 1 the daemon recommends a break.

Response:

{
  "current_value": 12.5,
  "threshold": 30.0,
  "should_break": false,
  "sensitivity_multiplier": 1.0,
  "timestamp": 1000.5
}

GET /api/helpfulness/summary

Summary of intervention helpfulness from the contextual-bandit feedback loop.

Response:

{
  "total_interventions": 15,
  "mean_reward": 0.62,
  "engagement_rate": 0.74,
  "recent_rewards": [0.6, 0.8, 0.55],
  "timestamp": 1000.5
}

Consent

GET /consent/level

Return the consent ladder state for every tracked action type.

Response:

{
  "levels": {
    "close_tab": { "level": 2, "approvals": 4, "rejections": 0 },
    "group_tabs": { "level": 1, "approvals": 1, "rejections": 0 }
  },
  "timestamp": 1000.5
}

POST /consent/reset

Reset the consent ladder to its defaults and return the new state.

Response:

{
  "reset": true,
  "levels": { "close_tab": { "level": 0 } },
  "timestamp": 1000.5
}

Project Launcher

GET /api/projects

List available project profiles.

Response:

{
  "projects": ["default", "research", "leetcode"],
  "timestamp": 1000.5
}

POST /api/launch/{project_name}

Launch a project profile (opens VS Code workspace, Chrome URLs, terminal commands).

Response:

{
  "launched": true,
  "project": "research",
  "timestamp": 1000.5
}

WebSocket Protocol

Endpoint: ws://127.0.0.1:9473

All messages are JSON objects with the following envelope:

{
  "type": "<MESSAGE_TYPE>",
  "payload": { ... },
  "timestamp": 12345.6,
  "sequence": 42
}

Auth Handshake

Every connection MUST send an AUTH frame before any other message. The server holds the connection in pending_auth until the token validates; any non-AUTH frame sent first triggers close(code=1011, reason="auth required") and emits an AUTH_REJECTED event.

Client → server (first frame):

{
  "type": "AUTH",
  "payload": { "auth_token": "<capability_token>" },
  "timestamp": 12300.0,
  "sequence": 0
}

Server → client on success:

{ "type": "AUTH_OK", "payload": {}, "timestamp": 12300.05, "sequence": 0 }

After AUTH_OK, the client follows with IDENTIFY (declaring client_type) and then exchanges normal traffic.

Generated TypeScript Types

The full list below is the canonical message-type catalog (Python source of truth: cortex/libs/schemas/ws_message_types.py::MessageType). The browser extension imports matching types from cortex/apps/browser_extension/types/generated/cortex_schemas.d.ts, which is regenerated from the Pydantic models by python -m cortex.scripts.generate_ts_schemas and gated against drift by pre-commit and CI.

Message Types

Inbound (client → daemon): AUTH, IDENTIFY, USER_ACTION, ACTION_EXECUTE, USER_RATING, CONTEXT_RESPONSE, SETTINGS_SYNC, ACTIVITY_SYNC, TAB_RELEVANCE_FEEDBACK, LEETCODE_CONTEXT_UPDATE, INTERVENTION_APPLIED, SHUTDOWN.

Outbound (daemon → client): AUTH_OK, STATE_UPDATE, INTERVENTION_TRIGGER, INTERVENTION_RESTORE, CONTEXT_REQUEST, ACTIVE_RECALL, BREATHING_OVERLAY, PRE_BREAK_WARNING, MORNING_BRIEFING, COPILOT_THROTTLE, AMBIENT_STATE_UPDATE.

LeetCode cues (daemon → chrome, target_client_types=["chrome"]): LEETCODE_SHOW_SCRATCHPAD, LEETCODE_SHOW_PATTERN_LADDER, LEETCODE_SHOW_LOCKOUT, LEETCODE_SHOW_CONSOLIDATION, LEETCODE_SHOW_SUBMISSION_GATE, LEETCODE_SHOW_SOLUTION_FRICTION, LEETCODE_SHOW_SESSION_BRIEFING, LEETCODE_LOCK_EDITOR, LEETCODE_INTERCEPT_SUBMIT, LEETCODE_GATE_SOLUTIONS, LEETCODE_AI_RESTATEMENT_CHECK, LEETCODE_AI_COMPREHENSION_CHECK, LEETCODE_AI_HYPOTHESIS_CHECK, LEETCODE_AI_STUCK_ANALYSIS, LEETCODE_AI_SESSION_BRIEFING.

Selected payload shapes follow.

STATE_UPDATE (server → client)

Broadcast every 500ms to all connected clients.

{
  "type": "STATE_UPDATE",
  "payload": {
    "state": "FLOW",
    "confidence": 0.87,
    "scores": {
      "flow": 0.87,
      "hypo": 0.05,
      "hyper": 0.08,
      "recovery": 0.0
    },
    "signal_quality": {
      "physio": 0.9,
      "kinematics": 0.85,
      "telemetry": 0.95,
      "overall": 0.9
    },
    "dwell_seconds": 45.2,
    "reasons": ["Good HRV", "Normal blink rate"]
  },
  "timestamp": 12345.6,
  "sequence": 42
}

INTERVENTION_TRIGGER (server → client)

Sent when the intervention engine triggers an intervention.

{
  "type": "INTERVENTION_TRIGGER",
  "payload": {
    "intervention_id": "int_a1b2c3d4e5f6",
    "level": "simplified_workspace",
    "headline": "Focus on one error at a time",
    "situation_summary": "You've been stuck on type errors for 12 minutes.",
    "primary_focus": "Fix TS2322 in App.tsx:67",
    "micro_steps": [
      "Look at the error on line 67",
      "Check the expected type",
      "Update the value"
    ],
    "hide_targets": ["sidebar", "terminal"],
    "ui_plan": {
      "dim_background": true,
      "show_overlay": true,
      "fold_unrelated_code": true,
      "intervention_type": "simplified_workspace"
    },
    "tone": "direct"
  },
  "timestamp": 12346.1,
  "sequence": 43
}

USER_ACTION (client → server)

Sent by extensions when the user interacts with an intervention.

{
  "type": "USER_ACTION",
  "payload": {
    "action": "dismissed",
    "intervention_id": "int_a1b2c3d4e5f6"
  },
  "timestamp": 12400.0,
  "sequence": 1
}

Valid actions: dismissed, engaged, snoozed.

IDENTIFY (client → server)

Sent by extensions on connection to identify their type.

{
  "type": "IDENTIFY",
  "payload": {
    "client_type": "vscode"
  },
  "timestamp": 12300.0,
  "sequence": 0
}

Valid client types: vscode, chrome, desktop.

SETTINGS_SYNC (bidirectional)

Sent by clients to update settings, or by the server to broadcast settings changes.

{
  "type": "SETTINGS_SYNC",
  "payload": {
    "consent_levels": { "close_tabs": 3, "fold_code": 4 },
    "quiet_mode": false,
    "max_autonomy": "REVERSIBLE_ACT"
  },
  "timestamp": 12350.0,
  "sequence": 2
}

ACTIVITY_SYNC (client → server)

Sent by the browser extension to report learning activity progress.

{
  "type": "ACTIVITY_SYNC",
  "payload": {
    "platform": "youtube",
    "url": "https://youtube.com/watch?v=...",
    "title": "Data Structures Lecture 5",
    "position": { "type": "video", "timestamp_seconds": 1234 },
    "duration_seconds": 300
  },
  "timestamp": 12360.0,
  "sequence": 3
}

CONTEXT_REQUEST (server → client)

Sent by the daemon to request context from a specific extension.

{
  "type": "CONTEXT_REQUEST",
  "payload": {},
  "timestamp": 12370.0,
  "sequence": 44
}

Connection Behavior

  • New clients receive the latest STATE_UPDATE immediately on connection
  • Dead connections are automatically cleaned up on next broadcast
  • The server auto-reconnects if the websockets package is available
  • Extensions should implement reconnection with exponential backoff

Clone this wiki locally