Cortex — Frontend Developer Reference

Cortex is a Kubernetes-native AI agent orchestrator. It manages Claude Code agents running in Docker containers, coordinates work through NATS JetStream, and exposes real-time updates via WebSocket.

The hierarchy is simple: Projects contain Agents. Each agent runs in its own K8s pod with a git workspace. You interact with agents through a REST API and receive real-time events over WebSocket.

Entities

Project

A project maps to a git repository. Agents are scoped to a project.

{
  "id": "uuid",
  "name": "my-project",
  "description": "...",
  "repository_url": "https://github.com/org/repo.git",
  "repository_branch": "main",
  "default_model": "anthropic/claude-opus-4-6",
  "max_agents": 10,
  "agent_image": "...",
  "created_at": "2026-01-01T00:00:00Z",
  "updated_at": "2026-01-01T00:00:00Z"
}

Agent

An agent is a Claude Code instance running in a K8s pod. Each agent works on a git branch.

{
  "id": "uuid",
  "project_id": "uuid",
  "agent_config_id": "uuid | null",
  "spawned_by": "uuid | null",
  "status": "pending | starting | ready | busy | terminating | terminated | failed | timeout",
  "current_branch": "main",
  "pod_name": "agent-xxx",
  "pod_ip": "10.0.0.1",
  "started_at": "timestamp | null",
  "last_seen_at": "timestamp | null",
  "terminated_at": "timestamp | null",
  "created_at": "timestamp",
  "updated_at": "timestamp"
}

Status values:

StatusActive?Meaning
pendingyesPod requested, waiting for K8s
startingyesPod running, containers initializing
readyyesAgent is idle, accepts commands
busyyesProcessing a prompt or task
terminatingnoShutdown in progress, saving work
terminatednoGone. Terminal state.
failednoPod crashed or errored
timeoutnoHeartbeat lost (90s threshold)

AgentConfig

A reusable configuration template for agents. Defines the system prompt, model, and tools.

{
  "id": "uuid",
  "name": "code-reviewer",
  "description": "Reviews PRs",
  "prompt": "You are a code reviewer...",
  "model": "anthropic/claude-opus-4-6",
  "tools": [{}],
  "chrome_enabled": false,
  "created_at": "timestamp",
  "updated_at": "timestamp"
}

TaskDefinition

A named, reusable task with a Go text/template prompt and optional JSON result schema.

{
  "id": "uuid",
  "slug": "analyze-pr",
  "name": "Analyze Pull Request",
  "description": "...",
  "prompt_template": "Analyze PR #{{.number}} in {{.repo}}...",
  "result_schema": { "type": "object", "properties": { ... } },
  "created_at": "timestamp",
  "updated_at": "timestamp"
}

TaskResult

The output of executing a task on an agent.

{
  "id": "uuid",
  "task_definition_id": "uuid",
  "project_id": "uuid",
  "agent_id": "uuid",
  "params": { "number": 42, "repo": "org/repo" },
  "data": { "summary": "...", "score": 8 },
  "status": "pending | running | completed | failed",
  "error": "",
  "created_at": "timestamp",
  "completed_at": "timestamp | null"
}

TaskTrigger

Auto-executes a task when a matching event fires. Can be global or project-scoped.

{
  "id": "uuid",
  "scope": "global | project",
  "project_id": "uuid | null",
  "task_definition_id": "uuid",
  "task_definition_slug": "analyze-pr",
  "pattern": "ai.git.push.completed",
  "filter": { "data.branch": "main" },
  "debounce_seconds": 5,
  "enabled": true,
  "created_at": "timestamp"
}

Event (CloudEvent v1.0)

All events use CloudEvents. This is the wire format for WebSocket messages and persisted history.

{
  "specversion": "1.0",
  "type": "ai.opencode.message.updated",
  "source": "/projects/{projectID}/agents/{agentID}",
  "id": "uuid",
  "time": "2026-01-01T00:00:00Z",
  "subject": "",
  "datacontenttype": "application/json",
  "data": { ... }
}

Agent State Machine

                +---------+
  (create) ---->| pending |-----> failed
                +----+----+       |
                     |            |
                     v            |
                +---------+       |
                |starting |-----> failed
                +----+----+
                     |
                     v
                +---------+       +---------+
                |  ready  |<----->|  busy   |
                +----+----+       +----+----+
                     |                 |
          any active state             |
                  |  |                 |
                  v  v                 v
              +------------+
              |terminating |     (heartbeat lost)
              +-----+------+          |
                    |                 v
                    v            +---------+
              +------------+    | timeout |
              | terminated |    +---------+
              +------------+

  Key rules:
  - ready <-> busy is the normal work cycle
  - Any active state can go to: terminating, failed, timeout
  - terminating can ONLY go to terminated (protected)
  - terminated is final, no way back

REST API

Full endpoint documentation with request/response schemas: Swagger UI (raw JSON spec)

WebSocket

Connect

const ws = new WebSocket("ws://HOST/ws?clientId=my-frontend-123");

clientId is required. If a new connection uses the same ID, the old one is closed.

Subscribe to events

ws.send(JSON.stringify({
  specversion: "1.0",
  type: "cortex.subscribe",
  source: "/clients/my-frontend-123",
  id: crypto.randomUUID(),
  data: {
    projects: ["project-uuid-1"],
    filter: {
      agents: ["agent-uuid-1"],           // optional: only these agents
      event_types: ["ai.opencode.*"]      // optional: wildcard supported
    },
    since: "last-known-event-id"          // optional: replay missed events
  }
}));

Server responds with cortex.subscribe.ack, then (if since was set) replays up to 1000 events followed by cortex.replay.complete.

Unsubscribe

ws.send(JSON.stringify({
  specversion: "1.0",
  type: "cortex.unsubscribe",
  source: "/clients/my-frontend-123",
  id: crypto.randomUUID(),
  data: { projects: ["project-uuid-1"] }
}));

Send a command to an agent

Set subject to {projectID}/{agentID}:

ws.send(JSON.stringify({
  specversion: "1.0",
  type: "ai.agent.command.prompt",
  source: "/clients/my-frontend-123",
  id: crypto.randomUUID(),
  subject: "project-uuid/agent-uuid",
  data: {
    prompt: "Fix the failing tests",
    session_id: "optional-session-id"
  }
}));

Server responds with cortex.command.ack. Then you receive agent events as they happen.

Available commands

TypeData
ai.agent.command.prompt{ prompt, session_id?, model? }
ai.agent.command.abort{ session_id }
ai.agent.command.shutdown{ reason? }
ai.agent.command.session.create{ title? }
ai.agent.command.git.clone{ url, branch?, path? }
ai.agent.command.git.checkout{ branch, create? }
ai.agent.command.git.pull{ remote?, branch? }
ai.agent.command.git.push{ remote?, branch?, force? }
ai.agent.command.git.commit{ message, paths? }
ai.agent.command.git.merge{ branch, strategy? }

Event Types

Agent lifecycle

TypeDescription
ai.agent.startedPod started
ai.agent.readyAgent ready to accept commands
ai.agent.busyAgent is working
ai.agent.idleAgent finished, back to idle
ai.agent.errorSomething went wrong
ai.agent.heartbeatPeriodic heartbeat with status + branch
ai.agent.work.savedWork committed and pushed to git
ai.agent.timeoutHeartbeat lost, agent timed out

Chat / OpenCode session

TypeDescription
ai.opencode.session.createdNew session started
ai.opencode.session.updatedSession metadata changed
ai.opencode.session.deletedSession removed
ai.opencode.session.statusStatus change (busy/idle/retry)
ai.opencode.session.idleSession done processing — primary completion signal
ai.opencode.session.errorSession error
ai.opencode.message.updatedMessage content updated
ai.opencode.message.part.updatedStreaming message chunk
ai.opencode.permission.updatedPermission request created/resolved

Git

TypeDescription
ai.git.clone.startedClone in progress
ai.git.clone.completedClone finished
ai.git.clone.errorClone failed
ai.git.push.startedPush in progress
ai.git.push.completedPush finished
ai.git.push.errorPush failed
ai.git.checkoutBranch checkout
ai.git.commitNew commit created
ai.git.conflictMerge conflict detected

Files

TypeDescription
ai.agent.file.createdFile created in workspace
ai.agent.file.modifiedFile modified
ai.agent.file.deletedFile deleted
ai.agent.file.renamedFile renamed (has old_path)

Inter-agent

TypeDescription
ai.agent.message.sentAgent sent a message to another agent
ai.agent.spawnedAgent spawned a new agent

Wakeup schedules

TypeDescription
ai.agent.wakeup.scheduledPeriodic schedule created
ai.agent.wakeup.triggeredWakeup fired, prompt delivered
ai.agent.wakeup.skippedWakeup skipped (agent was busy)
ai.agent.wakeup.cancelledSchedule cancelled
ai.agent.wakeup.expiredSchedule expired naturally

Tasks

TypeDescription
cortex.task.result.completedTask result ready
cortex.task.result.failedTask failed

Other

TypeDescription
ai.email.receivedEmail received for an agent

WebSocket protocol

TypeDirectionDescription
cortex.subscribeclient → serverSubscribe to project events
cortex.subscribe.ackserver → clientSubscription confirmed
cortex.unsubscribeclient → serverUnsubscribe from project events
cortex.command.ackserver → clientCommand forwarded to agent
cortex.replay.completeserver → clientHistorical event replay finished

Common Workflows

1. Create a project, spawn an agent, send a prompt

// 1. Create project
POST /api/projects
{ "name": "my-app" }

// 2. Create agent
POST /api/projects/{projectID}/agents
{ "agent_config_id": "optional-config-id", "branch": "main" }
// Returns agent with status "pending"

// 3. Connect WebSocket and subscribe
ws = new WebSocket("ws://HOST/ws?clientId=frontend-1")
ws.send({ type: "cortex.subscribe", data: { projects: [projectID] } })

// 4. Wait for ai.agent.ready event

// 5. Send prompt
ws.send({
  type: "ai.agent.command.prompt",
  subject: "{projectID}/{agentID}",
  data: { prompt: "Add input validation to the signup form" }
})

// 6. Watch for events:
//    ai.opencode.message.part.updated  (streaming response)
//    ai.opencode.message.updated       (complete message)
//    ai.opencode.session.idle          (agent done)

2. Execute a task and get structured results

// 1. Create a task definition (once)
POST /api/tasks
{
  "slug": "analyze-pr",
  "name": "Analyze PR",
  "prompt_template": "Review PR #{{.number}} and return a summary.",
  "result_schema": {
    "type": "object",
    "properties": {
      "summary": { "type": "string" },
      "score": { "type": "integer" }
    },
    "required": ["summary", "score"]
  }
}

// 2. Execute on an agent
POST /api/projects/{projectID}/agents/{agentID}/tasks/analyze-pr/execute
{ "params": { "number": 42 } }
// Returns: { "result_id": "uuid" }

// 3. Poll for result (or listen for cortex.task.result.completed via WebSocket)
GET /api/projects/{projectID}/tasks/analyze-pr/results/{resultID}
// When status is "completed", data contains the structured result

3. Reconnect and replay missed events

// Keep track of the last event ID you received
let lastEventId = null;
ws.onmessage = (e) => {
  const event = JSON.parse(e.data);
  lastEventId = event.id;
  // handle event...
};

// On reconnect, pass "since" to replay missed events
ws.send({
  type: "cortex.subscribe",
  data: {
    projects: [projectID],
    since: lastEventId   // replays up to 1000 events
  }
});
// Wait for cortex.replay.complete, then you're caught up

Notes