CORTEX Docs

Reference

Command reference

Note

There is no cortex command-line tool. It is planned (see Planning/Plan.md) and not implemented. Cortex is driven in two ways: through its window (see Interface actions) and through its MCP server (see MCP tools), which scripts and other tools can call. The executable cortex-desktop.exe takes no arguments.

What follows are the commands used to build and test Cortex, the 32 MCP tools, and the actions the window offers. Setup and configuration are in Installation, a walkthrough of the window is in User guide, and the architecture is in Development and architecture.

Development, build and test commands#

Prerequisites (Rust, Node.js, WebView2) are in Installation. All commands are for Windows.

Desktop UI#

Command Directory What it does
npm ci apps/desktop/ui Installs the locked UI dependencies
npm run dev apps/desktop/ui Starts Vite only (vite). The UI reaches the Core only through Tauri, so this alone shows nothing useful
npm run build apps/desktop/ui tsc --noEmit && vite build into dist/. cortex-desktop embeds dist/ at compile time, so build it before cargo build
npm run typecheck apps/desktop/ui tsc --noEmit
npm test apps/desktop/ui vitest run (jsdom). Needs a recent Node.js, see Development and architecture
npm run installer apps/desktop/ui cd .. && tauri build: rebuilds the UI, builds the release host and packs target\release\bundle\nsis\Cortex_<version>_x64-setup.exe. See Release and packaging

Rust workspace#

Run from the repository root.

Command What it does
cargo build -p cortex-desktop Builds target\debug\cortex-desktop.exe (needs apps/desktop/ui/dist)
cargo build -p cortex-desktop --release Builds target\release\cortex-desktop.exe; no installer
cargo run -p cortex-desktop Builds and starts the host (needs apps/desktop/ui/dist)
cargo test --workspace --exclude cortex-desktop The whole Rust suite without the UI build; tests that drive real CLIs are #[ignore]d
cargo test -p cortex-desktop Tauri commands on a mock runtime (needs apps/desktop/ui/dist)
cargo test --workspace The two rows above together
cargo test -p cortex-bootstrap --test architecture Crate layering checks only
cargo test -p cortex-mcp MCP server tests, including the check that this page lists every MCP tool and parameter
cargo fmt --all --check Formatting check
cargo clippy --workspace --all-targets -- -D warnings Lint with warnings as errors

Opt-in tests against the real CLIs. The CLI must be installed and logged in by you, and the tests can spend model tokens.

Command What it does
cargo test -p cortex-bootstrap --test runtime -- --ignored --test-threads=1 Claude Code
cargo test -p cortex-bootstrap --test codex -- --ignored Codex; CORTEX_CODEX_EXECUTABLE overrides the PATH lookup
cargo test -p cortex-bootstrap --test opencode -- --ignored --test-threads=1 OpenCode; OPENCODE_EXE overrides the PATH lookup

Start.bat#

Command Directory What it does
Start.bat repo root Runs npm install if apps/desktop/ui/node_modules is missing, then npm run build, cargo build -p cortex-desktop, and starts target\debug\cortex-desktop.exe with a console window
Start.bat release repo root The same with --release; starts target\release\cortex-desktop.exe, which has no console window

Set CORTEX_DATA_DIR to an absolute directory before starting to keep development data out of %LOCALAPPDATA%\Cortex (a commented line in Start.bat). A second instance also needs a different mcp.port in that directory's config.json.

Landing page#

apps/landing is a separate Vite site, outside the Cargo workspace.

Command Directory What it does
npm install apps/landing Installs the dependencies
npm run dev apps/landing Vite dev server (Vite's default port, 5173)
npm run build apps/landing vite build into dist/
npm run preview apps/landing Serves dist/
start.bat apps/landing Runs npm install if node_modules is missing, then npm run dev -- --open

Documentation site#

apps/docs is a separate Vite site, outside the Cargo workspace. Its pages are generated at build time from README.md and docs/*.md; the ADRs are not part of it.

Command Directory What it does
npm ci apps/docs Installs the locked dependencies
npm run generate apps/docs Renders the Markdown into generated/ (pages and search-index.json)
npm run dev apps/docs generate, then the Vite dev server. Run generate again after editing a .md file
npm run build apps/docs generate, then vite build into dist/
npm run preview apps/docs Serves dist/
npm run check apps/docs Checks dist/: every local link, asset and #anchor resolves, no link still points at a .md file, and every search entry points at an existing section

MCP tools#

The MCP server runs inside cortex-desktop while the window is open and mcp.enabled is true. The headings and parameter tables of this section are checked against the tool catalog by the test reference_docs in crates/cortex-mcp/src/tools.rs.

Endpoint. POST http://127.0.0.1:<port>/mcp, where <port> is mcp.port (default 47821; loopback only). The body is one JSON-RPC 2.0 message with Content-Type: application/json and a Content-Length of at most 1 MiB. There is no event stream: GET and DELETE answer 405. The methods are initialize (protocol versions 2025-11-25, 2025-06-18 and 2025-03-26; server name cortex), ping, tools/list and tools/call. Notifications get 202; JSON-RPC batches are refused. A tool name that does not exist is a JSON-RPC error -32602.

Authentication. Every request carries Authorization: Bearer <credential>:

Caller Credential Can call
You, locally The user token (64 hex characters). It is off until you turn it on in the MCP screen ("Local clients"), which copies it and regenerates it; mcp.json holds it sealed with Windows DPAPI. A refused or turned-off token is auth.credential_invalid. Whoever holds it acts as you All 32 tools
An agent session The per-session CORTEX_MCP_TOKEN, with the endpoint in CORTEX_MCP_URL; Cortex sets both in the environment of each agent process while MCP is on Read tools, post_message and ack_message. Any other tool fails with permission.denied (kind authorization_error). The reads return the agent's own project only: list_agents, poll_agent_events, list_tasks and list_proposals leave out other projects, and get_agent_status or get_task_status on another project's agent or task fails as not found
An orchestrator session The same per-session CORTEX_MCP_TOKEN of an agent whose role is orchestrator Everything an agent session can, plus create_agent, update_agent and start_agent, for the agents of its own project and workers only. Every other tool fails with permission.denied
An OAuth client An OAuth access token. Authorization code with PKCE S256 and dynamic client registration, at /.well-known/oauth-protected-resource, /.well-known/oauth-authorization-server, /register, /authorize, /authorize/wait and /token. You approve the client on the MCP screen. Access tokens last 1 hour and refresh tokens 30 days. The routes are served whether or not mcp.public_url is set; that setting (your own HTTPS tunnel, Cortex opens none) is what lets a client on another machine reach them All 32 tools

A missing or refused credential gets HTTP 401 with a WWW-Authenticate: Bearer resource_metadata="..." challenge (plus error="invalid_token" when a credential was sent). Before a tool runs, the server can also refuse with 400, 403 (Host is not 127.0.0.1:<port> or localhost:<port>, or the host of mcp.public_url; or a non-local Origin while mcp.public_url is not set), 404, 405, 408, 411, 413, 415, 431 or 503 (too many connections, or the Core is stopped). These bodies are {"error": {"code": "...", "message": "..."}}; the 401 and the Core-stopped 503 carry the full error object described below.

Results. A tool call always returns a CallToolResult: content holds one text item with the JSON below as text, structuredContent holds the same object, and isError is false. A failure is isError: true with:

json
{ "error": { "kind": "not_found", "code": "agent.not_found", "message": "...", "remediation": "...", "correlation_id": "...", "agent_id": "..." } }
Field Meaning
kind Stable class to branch on: invalid_argument, not_found, state_conflict, unsupported, authorization_error, runtime_error or internal_error
code Specific error code, such as agent.not_found, agent.state_conflict, validation.invalid_arguments, core.not_ready
message What went wrong
remediation What to do about it. Present only when the error has one
correlation_id Search logs\cortex.log for it to see the entries behind the error
agent_id Present only when the tool names an agent (get_agent_status, start_agent, send_message, interrupt_agent, stop_agent) and the failure comes from the Core

Arguments that do not match the schema fail with kind invalid_argument and code validation.invalid_arguments, naming the field. Every schema is closed: an unknown property is refused by name. Absent or null arguments count as {}. Tools that change something (create, send, start, stop, interrupt, cancel) fail with core.not_ready (kind state_conflict) unless the Core is READY.

Timestamps are RFC 3339 strings. The objects named below (Project, Agent, Runtime, Objective, Task, Attempt, Event, Message, Proposal) are defined in Objects returned by the tools.

Tool Class Agent sessions
list_projects, list_runtimes, list_agents, get_agent_status, poll_agent_events, list_tasks, get_task_status, list_skills, read_messages, list_proposals read allowed
post_message, ack_message write allowed
create_agent, update_agent, send_message, create_objective, edit_proposal, reject_proposal write denied (create_agent and update_agent are allowed to an orchestrator session)
start_agent control denied (allowed to an orchestrator session)
interrupt_agent, stop_agent, cancel_task, cancel_objective, pause_objective, resume_objective, reassign_task, approve_task, retry_task, integrate_task, plan_objective, propose_objective, approve_proposal control denied

list_projects#

List the projects registered in Cortex. Use it to find the project_id that create_agent needs.

Annotations: read-only, not destructive.

Parameters: none.

Returns { "projects": [Project] }. Projects are created in the window (Add agent dialog); MCP cannot create them.

list_runtimes#

List the runtimes agents can run on: the runtime_id create_agent needs, what each one declares it supports (capabilities, process strategy, model) and whether it can start now (health, with a remediation when it cannot).

Annotations: read-only, not destructive.

Parameters: none.

Returns { "runtimes": [Runtime] }, ordered by runtime_id. The call never waits for a health check: a runtime whose health is stale or unknown starts a check in the background, and a later call sees the result.

list_agents#

List every agent with its state.

Annotations: read-only, not destructive.

Parameters: none.

Returns { "agents": [Agent] }.

get_agent_status#

Get one agent: its state, configuration and the reason of its last failure, if any.

Annotations: read-only, not destructive.

Parameter Type Required Constraints
agent_id string yes 1 to 128 characters. Id of the agent (see list_agents)

Returns { "agent": Agent }. Fails with agent.not_found (kind not_found) for an unknown id.

create_agent#

Create an agent in the CREATED state; nothing runs until start_agent. Returns the new agent_id.

Annotations: not read-only, not destructive.

Parameter Type Required Constraints
display_name string yes Name shown for the agent. The Core trims it and requires 1 to 64 characters, at least one visible, with no control or text-direction characters (validation.invalid_name)
project_id string yes 1 to 128 characters. Project the agent works in (see list_projects); project.not_found otherwise
runtime_id string yes Id of a registered runtime (see list_runtimes): claude-code, codex or opencode. A value that is not a runtime id (1 to 32 characters from a-z, 0-9, _, -, starting with a letter or digit) fails with validation.invalid_runtime_id; an unknown but well-formed one with agent.runtime_not_registered
config object no Runtime settings as text values; default {}. At most 32 entries; keys of 1 to 64 characters from a-z, 0-9, _, ., -; values of at most 1024 characters and no NUL (validation.invalid_config). Stored and logged as is, so never put secrets in it. The three runtimes understand only model (a non-blank model name); any other key makes the start fail with runtime.invalid_config
role string no worker (default) or orchestrator. An orchestrator only coordinates other agents (see Orchestrator role); it needs a runtime that declares orchestrator_role (runtime.capability_missing otherwise). Only you and an authorized client can create one: an orchestrator creating an agent can ask for worker only (permission.denied)

Returns { "agent": Agent } with state CREATED and interaction structured. Agents created over MCP are always structured, and this is the only way to create a Codex or OpenCode agent. An orchestrator or an agent session can create agents in its own project only (project.not_found for another).

update_agent#

Rename or reconfigure a CREATED, STOPPED, FAILED or CRASHED agent of the project. It fails with a state conflict while the agent has a live session (stop it first). A role is not changed here.

Annotations: not read-only, not destructive.

Parameter Type Required Constraints
agent_id string yes 1 to 128 characters. Id of the agent
display_name string no New name; omit to keep it. Same rules as in create_agent
config object no Replaces the runtime settings as a whole, as text values; omit to keep them. Same rules as in create_agent

Returns { "agent": Agent }. Fails with agent.state_conflict while the agent has a live session (STARTING, READY, BUSY, PAUSED or STOPPING), with agent.not_found for an unknown agent or, from an orchestrator, one of another project, and with permission.denied for an agent session that is not an orchestrator.

start_agent#

Start a created or stopped agent. The agent is READY when the call returns; if the start fails, the call fails and the agent is FAILED, with the reason in last_failure.

Annotations: not read-only, not destructive, idempotent.

Parameter Type Required Constraints
agent_id string yes 1 to 128 characters. Id of the agent

Returns { "agent": Agent }, normally with state READY. A start waits up to 60 seconds for the runtime. Starting an agent that is already STARTING, READY or BUSY returns it unchanged, without waiting. Typical failures: agent.state_conflict (the agent is STOPPING), project.invalid_root (the project folder no longer exists), runtime.not_installed, resource.limit_reached (kind state_conflict; see resources.max_active_processes).

send_message#

Send a message to a READY agent; it starts a turn. The reply is not returned here: when the turn ends, poll_agent_events reports session.turn_completed with the result (or session.turn_failed), and the full output appears in the Cortex window. A BUSY agent (running a task) refuses it: cancel_task instead.

Annotations: not read-only, not destructive.

Parameter Type Required Constraints
agent_id string yes 1 to 128 characters. Id of the agent
message string yes Text to send. Must not be blank and at most 100,000 characters (validation.invalid_input)

Returns { "agent_id": string, "delivered": true }. delivered means the session accepted the message, not that the turn finished. Fails with agent.state_conflict when the agent is not READY, with runtime.unsupported_in_terminal_mode (kind unsupported) for a terminal-mode agent, and with runtime.turn_in_progress (kind state_conflict) when a turn is running and the runtime does not accept input during a turn (it lacks the interactive_input capability).

interrupt_agent#

Interrupt the current turn of a READY agent without ending its session; the turn then ends as session.turn_failed with cause interrupted. Does nothing when no turn runs. Fails with an unsupported error when the agent's runtime cannot interrupt (see the capabilities in list_runtimes). A BUSY agent (running a task) refuses it: cancel_task instead.

Annotations: not read-only, not destructive, idempotent.

Parameter Type Required Constraints
agent_id string yes 1 to 128 characters. Id of the agent

Returns { "agent": Agent, "interrupted": true }. interrupted is always true on success, also when no turn was running. Fails with runtime.capability_missing (kind unsupported) when the runtime lacks the interrupt capability, with runtime.unsupported_in_terminal_mode (kind unsupported) for a terminal-mode agent, and with agent.state_conflict when the agent is not READY.

stop_agent#

Stop an agent and end its session. Stopping a stopped agent is not an error. Stopping a BUSY agent cancels the task attempt it runs; the task waits for an agent again.

Annotations: not read-only, not destructive, idempotent.

Parameter Type Required Constraints
agent_id string yes 1 to 128 characters. Id of the agent

Returns { "agent": Agent }, normally with state STOPPED. An agent that is not running, or that another stop already owns (STOPPING), is returned as it is.

poll_agent_events#

Read the agent events (created, state changed, removed, and the end of each turn with its result) after a cursor. Pass the next_after of the previous call as after; omit it to read from the oldest event kept. dropped counts the events that were lost because the cursor was too old.

Annotations: read-only, not destructive.

Parameter Type Required Constraints
after integer no Minimum 0. The seq of the last event already seen. Omit it to read from the oldest event kept. A cursor from before the last start of Cortex is treated as omitted

Returns

text
{ "events": [Event, ...], "dropped": 0, "next_after": 1790000000000123 }
Field Meaning
events Events with seq greater than after, oldest first
dropped How many events the cursor missed because they left the feed. The feed keeps the last 256 events and counts from the start of the current run of Cortex
next_after The seq of the last event returned, to pass as after next time. If no event was returned it is after again, or null when there was no valid cursor and no event

The seq values only increase. They start from a number derived from the time Cortex started, not from 0, so never assume a value.

create_objective#

Create an objective: a goal split into 1 to 10 tasks that you list. Nothing is planned by an LLM. Each task runs as one turn on an existing READY structured agent of the project (assigned_agent_id pins one; otherwise any eligible READY agent takes it), and a task starts only after the tasks it depends on (depends_on names their keys inside this objective, no cycles) have COMPLETED; their results are passed on in its instruction. A task that cannot start yet waits: list_tasks says why. Returns the objective with the task_id of every task.

Annotations: not read-only, not destructive.

Parameter Type Required Constraints
project_id string yes 1 to 128 characters. Project the objective belongs to (see list_projects); project.not_found otherwise
title string yes Name of the objective. Trimmed, 1 to 200 characters, at least one visible, no control characters
tasks array of objects yes 1 to 10 items, in the order you want them listed. Each item has the properties below

Properties of each item of tasks. The schema is closed, so other properties are refused.

Property Type Required Constraints
key string yes Name of the task inside this objective; other tasks name it in depends_on. 1 to 64 visible characters, no control characters, unique inside the objective
title string yes Short title of the task. Trimmed, 1 to 200 characters, no control characters
description string no What the agent must do; it is the body of the instruction of the turn. Default empty; at most 20,000 characters, no control characters other than line breaks and tabs
type string no Kind of work; informative. One of implementation (default), review, test, analysis, integration
priority string no Order among tasks ready to run: higher first. One of low, normal (default), high
depends_on array of strings no Keys of the tasks of this objective that must COMPLETE first. Default empty. An unknown key is refused with task.unknown_dependency and a cycle (a task depending on itself included) with task.dependency_cycle
assigned_agent_id string no 1 to 128 characters. Run the task on this agent of the project (see list_agents); omit to let any eligible READY agent take it. The agent must exist (agent.not_found) and belong to the project, be structured and run on a runtime that reports the end of a turn (task.agent_not_eligible)
requirements array of strings no Capabilities the runtime of the agent must declare (see list_runtimes). Default empty. Allowed values: interactive_input, interrupt, streaming, turn_completion, workspace_isolation, heartbeat. A capability the runtime does not declare counts as missing, never as supported: its agents are excluded (routing.capability_missing). heartbeat asks for a runtime whose liveness the Supervisor can watch (none of the shipped adapters declares it). With workspace_isolation every attempt runs in a Git worktree of its own, <data dir>/worktrees/<task_id> on the branch cortex/<task_id>, created from the project's HEAD (the project folder must be the top of a Git repository with a commit), and its result stays there until integrate_task; only a runtime that can run a turn in another folder takes it (today Codex)
outputs array of strings no Files of the project this task produces, relative to its folder and / separated (no .., no drive, no \; at most 8, each at most 200 characters; repeated paths count once). Only these may reach the tasks that depend on it: when the task is approved, each one that exists is sealed with its size and SHA-256 in the attempt's handoff; a link is never followed and a file over 4 MiB is refused. A path that is not a plain relative one fails with validation.invalid_objective. Default empty
paths array of strings no Files or folders of the project this task writes, with the syntax of outputs (at most 8); a folder covers what it holds, and paths compare without case. While an attempt of the task runs it holds a lease on them: a task of the project whose paths overlap waits with waiting.paths_leased until that attempt ends. Default empty: no lease
timeout_secs integer no 10 to 86400. The longest one attempt may run, counted from its start (the worktree preparation included). Past it the Supervisor ends the attempt FAILED with the cause supervisor.deadline_exceeded, stops the agent that ran it (it becomes STOPPED; start it again) and the retry policy decides what follows, except that a task which declares paths and does not require workspace_isolation goes BLOCKED for a person, because what it wrote in the project folder belongs to no worktree. Default none: an attempt on a runtime that does not declare heartbeat is then not supervised, which is not a failure
max_retries integer no 0 to 5. New attempts after a retryable failure. Default 1. A retry waits 5 s, doubling for each further retry up to 60 s, less a random jitter of up to half, so tasks that failed together do not retry together
model string no The model its turns run on, e.g. sonnet, opus or haiku: it must be one a registered runtime that declares model_switching offers (else validation.invalid_objective), it beats Automatic Decisions and the Default Model, and only an agent of such a runtime runs the task (otherwise it waits with routing.capability_missing, missing: ["model_switching"]). Default none: Cortex chooses, see the attempt's model

Returns { "objective": Objective }, with every task in CREATED or already READY (tasks without dependencies are made READY at once). Invalid objectives fail with validation.invalid_objective (kind invalid_argument).

plan_objective#

Ask Cortex's planner to split a goal into 1 to 10 tasks. The planner is one turn of Claude Code, the reference runtime, started in the project's root: it may read the project but not change it, and it gets no credential for these tools. Nothing is created. The draft is checked by every rule of create_objective; pass it to create_objective, unchanged or edited, to create the objective.

Annotations: not read-only, not destructive.

Parameter Type Required Constraints
project_id string yes 1 to 128 characters. Project the objective is for (see list_projects); project.not_found otherwise
goal string yes What the objective must achieve. Trimmed, 1 to 20,000 characters, at least one visible, no control characters other than line breaks and tabs

Returns { "draft": { "project_id", "title", "tasks": [{ "key", "title", "description", "type", "priority", "depends_on", "requirements" }] } }: the arguments of create_objective. The draft names no agent and no max_retries, so create_objective applies the retry behaviors. Errors of kind runtime_error: planner.timed_out after 5 minutes without an answer; planner.invalid_plan when the answer is not exactly one JSON object of that shape (text around it included); planner.failed when the turn failed; planner.unavailable when Claude Code is not registered; runtime.not_installed when it cannot start. A draft that breaks a rule of create_objective fails with that rule's error (validation.invalid_objective, task.unknown_dependency, task.dependency_cycle). The call spends one turn of Claude Code each time.

propose_objective#

Ask Cortex's planner to split a goal into 1 to 10 tasks and keep its answer as a pending proposal for a person to review. The planner is the one of plan_objective: one turn of Claude Code, started in the project's root, that may read the project but not change it and gets no credential for these tools. The plan is data. No task exists, nothing is READY and nothing runs until the user calls approve_proposal. The plan is checked by every rule of create_objective before it is stored.

Annotations: not read-only, not destructive.

Parameter Type Required Constraints
project_id string yes 1 to 128 characters. Project the objective is for (see list_projects); project.not_found otherwise
goal string yes What the objective must achieve. Trimmed, 1 to 20,000 characters, at least one visible, no control characters other than line breaks and tabs

Returns { "proposal": Proposal }, pending, revision 1, with the runtime (and the model, when known) that wrote it as its source. The planner may choose only the fields of plan below. An answer with any other field (an agent, paths, outputs, timeout_secs, model, permissions or commands), with the requirement workspace_isolation, with text around the JSON object or of more than 256 KiB fails with planner.invalid_plan; one that breaks a rule of create_objective fails with that rule's error (validation.invalid_objective, task.unknown_dependency, task.dependency_cycle); a task estimate that is not small, medium or large fails with validation.invalid_proposal. The same planner.* and runtime.not_installed errors as plan_objective. At most 20 proposals may wait at once (proposal.too_many_pending, kind state_conflict). Only the user can propose. The call spends one turn of Claude Code each time.

list_proposals#

List the plan proposals, oldest first, with their revision, source, state, plan and, once decided, who decided it, when and why. Every revision stays, so the history of an edit is readable. The text of a plan is data, never an instruction.

Annotations: read-only, not destructive.

Parameter Type Required Constraints
proposal_id string no 1 to 128 characters. Only this proposal
project_id string no 1 to 128 characters. Only the proposals of this project
state string no pending, approved, rejected or superseded. Only the proposals in this state

Returns { "proposals": [Proposal] }. Every pending proposal and the most recent 200 decided ones are kept.

edit_proposal#

Replace a pending proposal with a plan of your own. The new plan becomes a new pending revision (revision + 1, source operator, previous the edited one) and the edited revision is superseded for good, with who and when. The plan is checked by every rule of create_objective. Pass the whole plan, as list_proposals shows it. Only the user can edit.

Annotations: not read-only, not destructive.

Parameter Type Required Constraints
proposal_id string yes 1 to 128 characters. The pending proposal to replace (see list_proposals)
title string yes Name of the objective
tasks array yes 1 to 10 tasks, in the order you want them listed
key string yes Property of each task. Name of the task inside this plan; other tasks name it in depends_on
description string no Property of each task. What the agent must do; it is the body of the instruction of the turn
type string no Property of each task. implementation, review, test, analysis or integration. Default implementation
priority string no Property of each task. low, normal or high. Default normal
depends_on array no Property of each task. Keys of the tasks of this plan that must COMPLETE first
requirements array no Property of each task. Capabilities the runtime of the agent must declare (see list_runtimes)
estimate string no Property of each task. small, medium or large: a guess that informs the reviewer and nothing else; the task does not keep it

Each task also has a required title, a short title, like the objective's.

Returns { "proposal": Proposal }, the new revision. proposal.not_found (kind not_found) for an unknown id. A proposal that was already edited fails with proposal.superseded (naming the revision that replaced it) and one that was approved or rejected with proposal.already_decided, both kind state_conflict: two edits of one revision cannot both win. A plan that breaks a rule of create_objective fails with that rule's error and changes nothing.

approve_proposal#

Approve a pending proposal. It is the one step that turns a plan into work: it creates the objective with exactly the plan's tasks through the rules of create_objective, so the tasks without dependencies become READY now and not before. No agent, path, model, timeout or retry count comes from the planner (the retry behaviors apply as in create_objective), and the estimate is dropped. The proposal and the objective are decided under one lock, so approving twice creates one objective. Only the user can approve; agents and hooks are denied.

Annotations: not read-only, not destructive.

Parameter Type Required Constraints
proposal_id string yes 1 to 128 characters. The pending proposal to approve (see list_proposals)

Returns { "proposal": Proposal, "objective": Objective }, the proposal approved with its resolution.objective_id. A proposal that was edited fails with proposal.superseded; one already approved (the error names its objective) or rejected, with proposal.already_decided. If creating the objective fails, the proposal stays pending. If the objective was created and the approval could not be written, the error's remediation names the objective: reject the proposal instead of approving it again.

reject_proposal#

Reject a pending proposal for good. No objective is created. Only the user can reject.

Annotations: not read-only, not destructive.

Parameter Type Required Constraints
proposal_id string yes 1 to 128 characters. The pending proposal to reject (see list_proposals)
reason string no Why it was rejected; trimmed, at most 500 characters, no control characters. Kept with the decision

Returns { "proposal": Proposal }, rejected, with resolution.by, resolution.at and resolution.reason. The same errors as approve_proposal for a proposal that was already decided.

list_tasks#

List every objective with its tasks: the status of each task, why a task that has not started waits (waiting), its attempts and, once COMPLETED, its result. It is the same state the Cortex window shows.

Annotations: read-only, not destructive.

Parameters: none.

Returns { "objectives": [Objective] }, oldest first. This includes the objectives created from the window's Orchestrator panel.

get_task_status#

Get one task: its status, its attempts (the agent that ran each and how it ended), why it waits, the cause of a failure or cancellation and, once COMPLETED, its result.

Annotations: read-only, not destructive.

Parameter Type Required Constraints
task_id string yes 1 to 128 characters. Id of the task (see list_tasks)

Returns { "task": Task }. Fails with task.not_found (kind not_found) for an unknown id.

cancel_task#

Cancel a task that has not ended, and every task that depends on it. A running attempt ends CANCELLED and its agent is released (the turn is interrupted); if the turn cannot be interrupted, the agent is stopped instead. Cancelling a CANCELLED task is not an error; a COMPLETED or FAILED one is a state conflict.

Annotations: not read-only, not destructive, idempotent.

Parameter Type Required Constraints
task_id string yes 1 to 128 characters. Id of the task (see list_tasks)

Returns { "task": Task }, the cancelled task. Fails with task.state_conflict (kind state_conflict) for a COMPLETED or FAILED task and with task.not_found for an unknown id.

cancel_objective#

Cancel an objective: every task of it that has not ended ends CANCELLED, with its running attempt, and the agents are released (the turn is interrupted; an agent that has not let go of it within 30 s is stopped). It also ends a pause and raises flow.generation. Cancelling an objective with nothing left to cancel changes nothing. Agents cannot cancel.

Annotations: not read-only, not destructive, idempotent.

Parameter Type Required Constraints
objective_id string yes 1 to 128 characters. Id of the objective (see list_tasks)

Returns { "objective": Objective }. Fails with objective.not_found (kind not_found) for an unknown id and with permission.denied for an agent.

pause_objective#

Pause an objective: none of its tasks starts until resume_objective, and each keeps its own state (a READY task stays READY and waits with waiting.objective_paused; a BLOCKED one can still be approved, retried or cancelled). With policy drain (the default) the attempts that run end by themselves; with interrupt their turns are interrupted, the attempts end CANCELLED (cause objective.paused) without spending a retry, and the tasks run again after the resume as the next attempt. A verification that already runs is not a turn and ends by itself. Pausing a paused objective changes nothing, except interrupt over a drain pause. Every decision raises flow.generation. Agents cannot pause.

Annotations: not read-only, not destructive, idempotent.

Parameter Type Required Constraints
objective_id string yes 1 to 128 characters. Id of the objective (see list_tasks)
policy string no drain (default) or interrupt. What becomes of the attempts that run
note string no Up to 1,000 characters, no control characters. Why it was decided; kept with the decision

Returns { "objective": Objective }, with status PAUSED and flow.state PAUSED. Fails with objective.state_conflict (kind state_conflict) when every task has ended, with objective.not_found for an unknown id and with permission.denied for an agent.

resume_objective#

Resume a paused objective: its graph is read afresh, and each task is eligible again or says why it waits (waiting). What a pause interrupted runs as the next attempt; a backoff that ran goes on from where it was. Resuming an objective that runs changes nothing. Agents cannot resume.

Annotations: not read-only, not destructive, idempotent.

Parameter Type Required Constraints
objective_id string yes 1 to 128 characters. Id of the objective (see list_tasks)
note string no Up to 1,000 characters, no control characters. Why it was decided; kept with the decision

Returns { "objective": Objective }. Fails with objective.not_found for an unknown id and with permission.denied for an agent.

reassign_task#

Move a task that waits (CREATED, READY or BLOCKED, with no attempt running) to another agent of its project, or to none (omit to_agent_id: any eligible agent may run it). The move is recorded in reassignments with who decided, from whom, to whom and why; the status does not change. Refused while the agent of its last attempt still ends it (at most 30 s after a cancellation) or its verification still runs. Pass execution_id, the attempt you read, to move the task only if that is still its last attempt; asking again for the same move is not an error. Agents cannot reassign.

Annotations: not read-only, not destructive, idempotent.

Parameter Type Required Constraints
task_id string yes 1 to 128 characters. Id of the task (see list_tasks)
to_agent_id string no 1 to 128 characters. Agent of the task's project that runs it from now on (see list_agents); omit for any eligible agent
execution_id string no 1 to 128 characters. The execution_id of the attempt you read; a newer attempt is a state conflict
note string no Up to 1,000 characters, no control characters. Why it was decided; kept with the decision

Returns { "task": Task }. Fails with task.state_conflict for a task that does not wait, that has a newer attempt than execution_id, or whose last attempt is still ending; with agent.not_found for an unknown agent and task.agent_not_eligible when the agent cannot run tasks of the project; and with permission.denied for an agent.

approve_task#

Approve a BLOCKED task whose result has no evidence: its project has no verification, or the verification did not pass or could not run. It becomes COMPLETED with the result of its last attempt, and the decision is recorded with who made it (decisions). Agents cannot approve. A task blocked for another cause can only be retried or cancelled. Pass execution_id, the attempt you read, to approve that result and nothing newer: a task that has a newer attempt is refused. Asking again for an approval already recorded for that attempt returns the task as it is, with no second decision.

Annotations: not read-only, not destructive, idempotent.

Parameter Type Required Constraints
task_id string yes 1 to 128 characters. Id of the task (see list_tasks)
execution_id string no 1 to 128 characters. The execution_id of the attempt whose block you decide. Without it, the decision applies to the last attempt and a repeat is a state conflict
note string no Up to 1,000 characters, no control characters. Why it was decided; kept with the decision

Returns { "task": Task }, the completed task. Fails with task.state_conflict (kind state_conflict) for a task that is not BLOCKED by its verification, and with permission.denied for an agent.

retry_task#

Ask for a new attempt of a BLOCKED task: it becomes READY at once and the decision is recorded. After a retry a person asked for, a failure blocks the task again instead of failing it. execution_id works as for approve_task: a retry already recorded for that attempt is not made twice.

Annotations: not read-only, not destructive, idempotent.

Parameter Type Required Constraints
task_id string yes 1 to 128 characters. Id of the task (see list_tasks)
execution_id string no 1 to 128 characters. The execution_id of the attempt whose block you decide. Without it, the decision applies to the last attempt and a repeat is a state conflict
note string no Up to 1,000 characters, no control characters. Why it was decided; kept with the decision

Returns { "task": Task }, the task, READY. Fails with task.state_conflict for a task that is not BLOCKED, and with permission.denied for an agent.

integrate_task#

Bring the result of a COMPLETED task that ran in a Git worktree of its own (requirement workspace_isolation) into its project folder. The changes of the worktree of the attempt that completed it are committed on its branch cortex/<task_id>, then squashed onto the project folder: they are staged there, never committed, for you to review and commit. Nothing is merged into your branch, pushed or deleted. The worktree is removed afterwards; the branch stays. Agents cannot integrate. A task is integrated once.

Annotations: not read-only, not destructive.

Parameter Type Required Constraints
task_id string yes 1 to 128 characters. Id of the task (see list_tasks)
note string no Up to 1,000 characters, no control characters. Kept with the integration

Returns { "task": Task }, with integration set. Fails, with nothing changed, with task.state_conflict for a task that is not COMPLETED, ran in the project folder or was integrated already; workspace.index_not_clean when the project folder has staged changes; workspace.conflict when the changes conflict with its HEAD (the paths are named; merge the branch yourself); workspace.local_changes when they would overwrite a change you have not committed; workspace.unowned when the worktree is not proven to be the task's; permission.denied for an agent.

list_skills#

List the skills Cortex found: the global ones (the skills folder of the data directory) and each project's .cortex/skills. A skill is a folder with a skill.json manifest and its Markdown content. The result gives each skill's scope, version and state (enabled, disabled, or changed since it was enabled), the folders that are not valid skills and why, and the name conflicts between skills. With agent_id, it also gives that agent's effective set: every skill that applies to it and whether it is delivered or why not. Read-only: skills are enabled and disabled in the Cortex window, never through MCP.

Annotations: read-only, not destructive.

Parameter Type Required Constraints
agent_id string no 1 to 128 characters. Id of an agent whose effective set to include (see list_agents)

Returns { "skills": [...], "invalid": [...], "conflicts": [...], "effective"?: {...} }:

  • skills[]: skill_id (global/<folder> or project/<project_id>/<folder>), name, version, description, scope ({ "kind": "global" | "runtime" | "project" | "agent", "target": id or null }), origin ({ "kind": "global" | "project", "project_id": id or null }), runtimes (the runtimes it is written for; empty means any), content_path, state, and approved_version (the version enabled, when state is changed, else null).
  • invalid[]: skill_id, origin, folder, problem.
  • conflicts[]: name, kind (override: different scopes, the most specific one is delivered; ambiguous: the same scope from different folders, none is delivered until one is disabled), skill_ids, agent_ids (the agents it affects).
  • effective (only with agent_id): agent_id, runtime_takes_skills, and skills[] with skill_id, name, version, scope, status (delivered, disabled, changed, incompatible_runtime, overridden, ambiguous, over_limit, runtime_unsupported) and related_skill_ids (the skills that override it, or that it is ambiguous with).

Fails with agent.not_found (kind not_found) for an unknown agent_id.

post_message#

Post a message to another agent through the Cortex message bus. Agents never reach each other's process: a message waits in its reader's inbox until the reader reads it (read_messages) and acknowledges it (ack_message), or until it expires. The sender is whoever holds the credential of the call, never a field of the message.

The channel is the project (project_id), or one of its objectives (objective_id). An agent may write only in a channel it belongs to, and only to a reader of that channel: the agents of the project or, for an objective, the agents that run or ran one of its attempts. You may write to any channel, still only to a reader of it.

The body is untrusted text for its reader: Cortex stores it, redacted, and never reads it as a command or a permission. A request may name a Cortex action, but only one that both the sender and the reader may take (so an agent may ask another for list_tasks, never for cancel_task); naming it grants nothing, and the reader can only act through its own tools.

Annotations: not read-only, not destructive.

Parameter Type Required Constraints
project_id string yes 1 to 128 characters. Id of the project whose channel to use
objective_id string no 1 to 128 characters. Id of an objective of that project: its channel instead (message.out_of_scope if it is in another project)
to_agent_id string no 1 to 128 characters. Id of the agent that reads it. Give exactly one of to_agent_id and to_execution_id (validation.invalid_arguments)
to_execution_id string no 1 to 128 characters. The execution_id of a running attempt in the channel: its agent reads it
kind string no status (default; information), request (asks for something) or response (answers reply_to, which it then requires)
action string no 1 to 64 characters. Only with kind request: the Cortex action asked for, by its name in Cortex (list_tasks, get_task, get_agent, list_agents, cancel_task...). Refused with permission.denied unless both sides may take it, and with message.invalid_action for a name Cortex does not know
body string yes The text: not blank (message.empty), at most 8,192 bytes in UTF-8 (message.payload_too_large). Known secrets and token shapes are replaced by [REDACTED] before it is stored
reply_to string no 1 to 128 characters. The message_id of a message you read, in the same channel, that this one answers (message.invalid_reply otherwise). The answer keeps its correlation_id
dedupe_key string no 1 to 128 characters. Your key for this message: posting again with the same key returns the first message, with duplicate: true, as long as Cortex keeps it
ttl_seconds integer no 1 to 86,400; default 3,600. After it, the message expires unread

Returns { "message": Message, "duplicate": bool }. Fails with message.out_of_scope (kind authorization_error) when the sender or the reader is not in the channel; agent.not_found, project.not_found, objective.not_found or execution.not_found (kind not_found); message.recipient_unavailable (kind state_conflict) for an attempt that is not running; message.inbox_full (kind state_conflict) when the reader already has 32 messages waiting; and message.sender_limit (kind state_conflict) when the sending agent has 32 messages waiting in other inboxes. Every refusal is published as the event message.rejected with its reason.

read_messages#

Read the messages waiting for you: not acknowledged and not expired, oldest first (by sequence), at most 32 per call. An agent reads only its own; your user credential reads every agent's. Reading changes nothing: a message stays until it is acknowledged or expires.

Agents do not need to poll: when Cortex starts a turn for an agent (a task, or your input) and messages wait for it, the turn ends with a line such as [Cortex] 2 messages from other agents wait in your inbox... that names these tools. The line gives the count only, never a message's text. An idle agent sees it with its next turn.

Annotations: read-only, not destructive.

Parameter Type Required Constraints
project_id string no 1 to 128 characters. Only the messages of this project's own channel (not those of its objectives). An agent must belong to it (message.out_of_scope)
objective_id string no 1 to 128 characters. Only the messages of this objective's channel. An agent must be one of its participants
after integer no Minimum 0. The sequence of the last message already read

Returns { "messages": [Message, ...], "more": bool }. more is true when more messages wait after the last one returned: pass its sequence as after.

ack_message#

Acknowledge a message you read: it leaves your inbox and frees its place. Acknowledging it again returns it unchanged. Only its reader acknowledges a message (you may acknowledge any); another agent's message is message.not_found.

Annotations: not read-only, not destructive, idempotent.

Parameter Type Required Constraints
message_id string yes 1 to 128 characters. The message_id of a message you read

Returns { "message": Message }, now ACKED. Fails with message.not_found (kind not_found) and with message.expired (kind state_conflict) for a message whose TTL passed first.

Orchestrator role#

An agent whose role is orchestrator coordinates other agents and has no tool to read, change or run anything in the project (ADR 0035). Its session reaches Cortex only through these tools, with the credential in CORTEX_MCP_TOKEN:

Allowed Notes
list_projects, list_runtimes, list_agents, get_agent_status, poll_agent_events, list_tasks, get_task_status, list_skills, list_proposals, read_messages Reads, limited to its own project like any agent session
create_agent Workers only (role orchestrator is permission.denied), and only in its own project (another project is project.not_found)
update_agent Rename or reconfigure a stopped worker of its own project; it cannot change a role
start_agent Start an agent of its own project
post_message, ack_message The message bus, as for any agent

Everything else fails with permission.denied (kind authorization_error): stopping, interrupting or messaging an agent directly, creating objectives or tasks, deciding anything a person decides. The Core also answers every tool request the CLI of an orchestrator session makes: a Cortex tool is allowed and anything else (shell, file, web) is denied at once and listed in Approvals, decided by role, whatever Automatic Decisions say. The runtime must declare orchestrator_role (list_runtimes shows it) and the session is always structured; the start is refused with runtime.capability_missing, agent.orchestrator_needs_structured or mcp.disabled rather than run as a worker. An orchestrator is never given a task.

Objects returned by the tools#

Keys marked optional are omitted when they do not apply. Keys of the Task and Attempt objects are always present; an absent value is null.

Project

Field Type Meaning
project_id string Id
name string Display name
root_path string Folder the agents of the project work in
created_at string Creation time

Agent

Field Type Meaning
agent_id string Id
display_name string Name shown for the agent
project_id string Project it works in
runtime_id string Runtime it runs on
interaction string structured or terminal. Agents created in the window can be terminal; send_message and tasks need structured
role string worker or orchestrator (see Orchestrator role)
state string CREATED, STARTING, READY, BUSY, PAUSED, STOPPING, STOPPED, FAILED or CRASHED. BUSY means it is running a task; PAUSED means a person holds an idle agent, which keeps its session but is handed nothing
config object The settings given at creation, text values (for example model)
created_at, updated_at string Times
last_failure object Optional. { "code": string, "message": string }, present when the agent has a recorded failure
current_execution_id string Optional. Present while the agent hosts a task attempt: the execution_id of that attempt

Runtime

Field Type Meaning
runtime_id string Id to pass to create_agent
display_name string Name for people
adapter_version string Version of the adapter
cli_version string Optional. Version of the CLI, once known
process_strategy string persistent (one long-lived process per session) or per_turn (one short process per turn)
capabilities array of strings What the runtime declares, from interactive_terminal, interactive_input, interrupt, streaming, turn_completion, skills, context_handoff (its turns may carry the handoffs of the tasks a task depends on; a task with dependencies runs only on a runtime that declares it), workspace_isolation, heartbeat (it reports a beat at least every 30 s while a turn runs; without one for 90 s the Supervisor ends the attempt CRASHED with supervisor.heartbeat_lost. A runtime that does not declare it is never ended for silence; no adapter declares it yet)
capability_state string Always declared: capabilities are what the adapter says, not tested
model object { "selectable": bool, "hint": string }. selectable says whether the model entry of config chooses the model; hint (optional) says what a model value looks like
health object { "state": "unknown" | "available" | "unavailable", "checked_at"?, "detail"?, "code"?, "message"?, "remediation"? }. checked_at is absent while unknown; detail says what an available runtime's check found; code, message and remediation say why an unavailable runtime cannot start

Objective

Field Type Meaning
objective_id string Id
project_id string Project
title string Title
status string PAUSED (a person paused it and some task has not ended), RUNNING (some task has not ended), BLOCKED (a task is BLOCKED and none runs or is ready), COMPLETED (every task completed), FAILED (every task ended and at least one failed) or CANCELLED (every task ended, none failed, at least one cancelled)
flow object { "state": "RUNNING" | "PAUSED", "generation": integer, "policy": "drain" | "interrupt" or null, "reason": string or null, "decided_by": { "origin", "caller" } or null, "changed_at": string or null }. generation counts the pauses, resumes and cancellations of the objective and only grows: the events of these decisions (objective.paused, objective.resumed, objective.cancelled) carry it, and an event whose number is not greater than the one in the snapshot you hold is old news. policy is set while PAUSED
created_at string Creation time
tasks array The Tasks, in the order given at creation

Task

Field Type Meaning
task_id string Id
objective_id string Objective it belongs to
title, description string As given
type string implementation, review, test, analysis or integration
status string CREATED, READY, RUNNING, BLOCKED, COMPLETED, FAILED or CANCELLED. A turn that ends well is not a completion: the project's verification must pass, or a person approves; until then the task is RUNNING (verifying) or BLOCKED
priority string low, normal or high
dependencies array of strings task_ids of the tasks it waits for (the depends_on keys, resolved to ids)
requirements array of strings Capabilities required, sorted without duplicates
outputs array of strings The project-relative files it declared it produces, in order, without duplicates
paths array of strings The project-relative files and folders it declared it writes, leased while an attempt runs
timeout_secs integer or null The longest one attempt may run (see create_objective); null declares none
model string or null The model the task named, if any
assigned_agent_id string or null The pinned agent, if any
max_retries integer New attempts allowed after a retryable failure
attempts array The Attempts, oldest first
result object or null Set once COMPLETED: { "execution_id": string, "text": string or null, "truncated": bool, "evidence": object or null }. text is the final message of the turn, cut to 4,096 characters (truncated then), and null if the runtime ended the turn without one. evidence is { "kind": "verification", "execution_id", "fingerprint", "exit_code" } or { "kind": "human_approval", "approver": { "origin", "caller" }, "decided_at" }; null only for a task completed before verification existed
decisions array What people decided about the task while BLOCKED: decision (approve, retry, cancel), task_id, objective_id, execution_id (the attempt whose block it resolved; null in a record made before it was kept), blocked_cause, approver (origin: desktop or mcp; caller: user or client:<id>), decided_at, note
reassignments array The moves of the task to another agent, oldest first: from, to (agent ids; null is any eligible agent), execution_id (its last attempt when it moved; null if it never ran), reason, decided_by (origin, caller), decided_at
integration object or null Set once integrate_task brought its worktree into the project folder: execution_id, commit (on cortex/<task_id>), files (how many the changes touch), approver, integrated_at, note. null until then
waiting object or null { "code": string, "message": string, "excluded": array }: why a CREATED or READY task does not run; null otherwise and for a task about to be dispatched. excluded lists, when the router was asked, every agent it ruled out: { "agent_id", "runtime_id", "code", "message", "rule", "missing" }, where rule is the deny rule (else null) and missing the capabilities its runtime does not declare (else []); empty for a wait that is not the router's. Exclusion codes: routing.not_assigned, routing.other_project, routing.runtime_missing, routing.runtime_unavailable (its last health check failed; for a per-turn runtime also when the check failed after the agent started), routing.capability_missing, routing.denied_by_rule, routing.agent_unavailable, routing.taken_this_round, routing.runtime_at_limit. Codes: waiting.dependencies, waiting.dependency_blocked (a task it depends on is BLOCKED), waiting.retry_backoff, waiting.parallel_limit, waiting.project_limit (its project runs orchestrator.max_parallel_tasks_per_project tasks), waiting.runtime_limit (every agent that could run it is on a runtime that runs orchestrator.max_parallel_tasks_per_runtime tasks), waiting.assigned_agent_missing, waiting.assigned_agent_ineligible, waiting.agent_unavailable, waiting.no_eligible_agent, waiting.no_available_agent, waiting.not_routable (the router excludes every agent for a reason the user can change: a deny rule, an unavailable runtime, manual routing without an assigned agent), waiting.paths_leased (a running task writes paths its paths overlap), and waiting.blocked for a BLOCKED task
cause object or null { "code": string, "message": string }: why the task is FAILED, CANCELLED or BLOCKED (verification.not_configured, verification.failed, verification.timed_out, verification.not_run, a workspace.* cause when its worktree could not be prepared, a supervisor.deadline_exceeded or supervisor.heartbeat_lost cause when the Supervisor ended its attempt, or the cause of an attempt a person asked for), or why it went back to READY
not_before string or null While READY after a failure: no attempt starts before this time
created_at string Creation time
started_at, finished_at string or null First start and end

Attempt (one try of a task)

Field Type Meaning
execution_id string Id of this attempt
attempt_number integer Order of the attempt, starting at 1
agent_id, runtime_id, session_id string Where it ran
process_refs array of strings Ids of the processes it ran in
started_at string Start time
finished_at string or null End time; null while running
outcome string or null COMPLETED, FAILED, CRASHED or CANCELLED; null while running
cause object or null { "code": string, "message": string } when it did not complete
failure_class string or null For FAILED and CRASHED: retryable, fatal or user_action_required. Decides whether a retry follows
handoff object or null What the attempt handed over to the tasks that depend on its task (ADR 0026), published in the write that approves the task: { "version": 1, "source": { "execution_id", "task_id", "objective_id", "project_id", "attempt_number" }, "published_at", "outcome": { "status": "completed", "summary", "result", "truncated", "artifacts": [{ "path", "bytes", "sha256" }], "refused": [{ "path", "reason" }], "evidence" } }. summary is the first line of the result (at most 240 characters), result at most 4 KiB. reason is invalid, outside_project, missing, not_a_file, too_large or unreadable. null for an attempt that failed, crashed or was cancelled (its outcome, cause and failure_class say why: nothing of it reaches a dependent) and until a task blocked for approval is approved
routing object or null How the router chose its agent: decision_id, task_id, decided_at, correlation_id, mode (manual, policy or auto), considered and fallbacks ({ "agent_id", "runtime_id" }), excluded (as in a task's waiting), selected, selected_by, rule_applied (the prefer rule, when one decided) and reasons (for people). selected_by is why the selected agent went before the next eligible one, the first criterion that told them apart: routing.assigned, routing.only_eligible, routing.untried_after_failure, routing.fallback_after_failure, routing.preferred_by_rule, routing.preferred_runtime, routing.least_loaded (fewest working agents on its runtime, counting the ones taken in the same round), routing.least_recently_used (its last attempt started longest ago, or it never ran one) or routing.creation_order; absent in a decision recorded before it existed. The agent is checked again right before the attempt starts: one that no longer passes is not used, and the next round decides again (log orchestrator.dispatch_skipped, code routing.candidate_stale). null for an attempt started before the router
workspace object or null The Git worktree it ran in, for a task that requires workspace_isolation: { "path", "branch", "base" } (base is the commit it was created from). null when it ran in the project folder
model object or null The model its turn ran on (S13.1): model, source (task: the task named it; default: Automatic Decisions was off; router: the local router chose it; fallback: the router was asked and could not decide), fallback (router.not_configured, router.unavailable, router.timeout, router.invalid_answer or router.unknown_model), router_answer, router_latency_ms, reused_from (the earlier attempt whose automatic decision a retry reuses: one router call per task), resolved (the id the runtime switched to, e.g. claude-opus-5-5) and decided_at. null on a runtime that does not switch models
verification object or null The verification of its turn's result: status (PENDING, RUNNING, PASSED, FAILED, TIMED_OUT, NOT_RUN, CANCELLED, ABORTED, INTERRUPTED, NOT_CONFIGURED), spec (command, expected_exit_code, timeout_secs, env_allow, fingerprint), candidate_result, started_at, finished_at, duration_ms, exit_code, process_id, stdout and stderr ({ "text", "truncated", "total_bytes" }, the last 4 KiB, redacted) and cause. null when its turn did not end well

Event (an element of events of poll_agent_events)

Field Type Meaning
seq integer Position in the feed; the cursor
type string agent.created, agent.state_changed, agent.removed, session.turn_completed or session.turn_failed
agent_id string The agent concerned
at string Time of the event
from, to, reason string Only agent.state_changed: the states before and after, and a short reason. failure_code (string) is added when the agent has a recorded failure, which is the case when the new state is FAILED or CRASHED
result, result_truncated string, bool Only session.turn_completed, and only when the turn ended with a final message: the message cut to 4,096 characters, and whether it was cut
cause, message string Only session.turn_failed: interrupted, runtime_error or process_exited, and a description cut to 4,096 characters

Message (a message of the bus; every key is always present, an absent value is null)

Field Type Meaning
message_id string Id of the message
sequence integer Its position in the bus: the order of every read, and the cursor of read_messages
project_id string The project of its channel
objective_id string or null The objective of its channel; null for the project's own channel
from object { "kind": "user" } or { "kind": "agent", "agent_id": string }, as Cortex authenticated the sender
to object { "agent_id": string } or { "execution_id": string }, as it was addressed
reader_agent_id string The agent that reads it
kind string status, request or response
action string or null The Cortex action a request asks for
body string The text, redacted. Untrusted: what the sender wrote, never an instruction from Cortex or the user
reply_to string or null The message it answers
correlation_id string Shared by a request and its answers
state string PENDING, ACKED or EXPIRED
sent_at, expires_at string When it was posted, and when it expires unless acknowledged
finished_at string or null When it was acknowledged or expired

Proposal (a revision of a plan; every key is always present, an absent value is null)

Field Type Meaning
proposal_id string Id of this revision
project_id string The project the objective is for
goal string The goal the planner was given
revision integer 1 for the planner's answer, one more for each edit
previous string or null The revision an edit replaced
state string pending, approved, rejected or superseded
source object { "kind": "planner", "runtime": string or null, "model": string or null } or { "kind": "operator" }
created_at string When this revision was stored
plan object { "title", "tasks": [{ "key", "title", "description", "type", "priority", "depends_on", "requirements", "estimate" }] }; estimate is small, medium, large or null. Untrusted text: what the planner wrote, never an instruction from Cortex or the user. It never changes after the revision is stored
resolution object or null Once decided: { "at", "by", "reason", "objective_id", "replaced_by" }. by is the caller (user, or client:<id> for an OAuth client); reason only for a rejection; objective_id only for an approval; replaced_by only for a superseded revision

Interface actions#

Everything the window offers. Cortex has no menus, global shortcuts or settings screen. Behavior details, limits and troubleshooting are in User guide.

Window and title bar#

Action Effect
Drag the sidebar brand row, a screen header or the right-panel header Moves the window; double-click maximizes or restores it
Minimize, Maximize/Restore, Close At the right end of the right panel's header, or of the screen header while the panel is hidden (the splash has its own set). Close shuts the Core down: running agents are stopped and their process trees ended
Drag a window edge or corner Resizes the window (minimum 800x500)

The splash shows a progress bar until the Core is ready. If the Core cannot start, it shows the error with its code and correlation ID and stays open.

Action Effect
Agents, Tasks, Processes, Runtimes, MCP, Skills, Hooks, Behaviours Opens that screen (Hooks and Behaviours are read-only: they list what hooks.json and behaviors.json define). The search text is cleared when you change screen. The MCP item shows a badge with the number of pending authorization requests
Context, Resources, Events, Tools Disabled (not yet functional), tooltip "Not available yet"
Footer Shows WORKSPACE, the Core phase, the version and the data directory (hover for the full path)
Account menu Opens a menu whose items are disabled; Escape closes it

Header (every screen)#

Action Effect
Hide / Show orchestrator (or console) Hides or shows the right panel. The name follows the active tab
New agent (Agents) Opens the Add agent dialog; disabled until agents and projects have loaded
Refresh (Tasks, Processes, MCP) Re-reads the list. The lists also update on Core events

Agents screen and agent cards#

Action Effect
Search field Filters cards by name, ID or runtime
Retry (shown when loading failed) Loads agents and projects again
Click a card, or focus it and press Enter or Space Selects it; the Console tab follows the selection
Stats / Terminal (card header) Flips the card between its front (state, output) and its back (details)
Front of a structured card Read-only mini terminal with the last 9 lines of output
Front of a terminal-mode card A live terminal: click it and type. Keys typed there go to the CLI and trigger no card or app shortcut
Start Starts the agent. Enabled in CREATED, STOPPED, FAILED, CRASHED
Stop Stops it. Enabled in STARTING, READY, BUSY, PAUSED; a BUSY agent's task attempt is cancelled and the task waits for an agent again
Restart Stops if needed and starts again with a new session. Enabled in READY, BUSY, PAUSED, STOPPED, FAILED, CRASHED
Edit Opens the Edit agent dialog. Enabled in CREATED, STOPPED, FAILED, CRASHED
Pause Holds an idle agent: READY to PAUSED, keeping its process and session, and handing it nothing until Resume. Enabled in READY for a structured agent with no reply running; otherwise disabled with the reason as its tooltip
Resume (the Pause button of a PAUSED agent) PAUSED to READY
Remove Removes the agent after a second click on "Confirm remove". Enabled in CREATED, STOPPED, FAILED, CRASHED
Right click on a card, or the context-menu key or Shift+F10 on a focused card Opens the agent's context menu (below) at the pointer, and selects the card

Start, Stop, Restart and Edit are on the back of the card and in the Console header (Start, Stop and Restart only). While a command of an agent is in flight, Start and Restart are disabled; Stop stays available (except while a stop is itself in flight), so a start that hangs can be cancelled.

Agent context menu#

Cortex's own menu, in document.body, kept inside the window. It lists Pause (Resume for a PAUSED agent), Stop, Start, Edit, Restart and Remove, in that order, with the same enabling rules as the card buttons; a disabled row gives its reason as its tooltip and as its accessible description. While a command of the agent is in flight, every row but Stop is disabled.

Input Effect
Choose a row (click, Enter, Space) Closes the menu and runs the action. Pause, Resume, Stop, Start and Restart go to the Core and report their outcome at the bottom of the window; Edit opens the Edit agent dialog; Remove opens a "Remove agent?" dialog (Cancel has the focus), and removal runs only on "Remove agent"
Arrow keys, Home, End Move between the enabled rows
Escape Closes the menu and returns the focus to the card
Click or right click elsewhere, wheel, window resize, window loses focus, Tab Closes the menu

The result messages are Paused, Resumed, Stopped, Started, Restarted and Removed followed by the name, a note (Stopping alpha…) while a Stop, Start or Restart runs, and Could not <action> alpha with the Core's message and remediation when it refuses. Successes disappear after 4 s and errors after 12 s; at most 4 messages show.

Add agent dialog#

The dialog is titled "Add agent". It opens from the New agent button of the Agents header, and from the one shown when there are no agents.

Field or action Effect
Name Required
Project Pick an existing project or "New project…" (shown only if a project exists)
Project name, Folder path Required for a new project. The folder is typed, not picked, and must be an absolute path to an existing directory
Runtime Read-only: Claude Code
Set orchestrator role Checkbox, off by default. Available for a runtime that declares orchestrator_role (Claude Code); it forces Structured and disables the Interaction field. Only sent when it is checked
Interaction Structured (console and Send) or Terminal (the CLI's own interface in the card)
Model Optional; passed to Claude Code as --model
Create agent Creates the project if new, then the agent in CREATED, and selects it. A failure is shown in the dialog
Cancel, Escape Closes the dialog (not while it is creating)

Edit agent dialog#

Field or action Effect
Name Read-only
Interaction Structured or Terminal; Terminal is disabled for a runtime without an interactive terminal; Claude Code, Codex and OpenCode all have one
Set orchestrator role Sets or clears the role; it forces Structured while set. Only sent when it changed
Save Applies the new interaction mode, model and role; enabled only when one changed. The Core refuses it while the agent has a live session and the dialog shows its error
Cancel, Escape Closes the dialog

Right panel#

Action Effect
Orchestrator / Console tabs Switch tabs. Arrow Left and Right, Home and End move between tabs when a tab has focus. The text typed in each tab is kept when you switch

Console tab (selected agent):

Action Effect
Start, Stop, Restart As on the card
Output The agent's stdout and stderr (structured agents). "Select an agent." with none selected; a note instead for a terminal-mode agent
Instruction field and Send Sends the text to the agent; disabled unless the agent is READY and structured, or when the text is blank. While the agent replies ("Replying…" in the header) Send waits and the text stays. Enter sends, Shift+Enter inserts a newline. The Core accepts 1 to 100,000 characters
Dismiss (on an error notice) Hides the command error
Details (on an error notice) Shows the error code, the correlation id and the causes, for a report

Orchestrator tab:

Action Effect
Instruction field and Send Creates one objective with one task for the project of the selected agent (or the only project). Enter sends, Shift+Enter inserts a newline. The title is the first non-blank line, cut to 200 characters; the description is the full text, at most 20,000 characters. Disabled when no project can be chosen
Cancel (on a RUNNING or PAUSED objective) Cancels every task of the objective that has not ended
Pause (on a RUNNING objective) pause_objective with drain: nothing new starts, the running attempts end by themselves. The tasks keep their state and say they wait for the resume
Interrupt (on a RUNNING objective, or a PAUSED one that drains) pause_objective with interrupt: nothing new starts and the running turns are interrupted (their attempts end CANCELLED, no retry is spent)
Resume (on a PAUSED objective) resume_objective: the tasks are eligible again or say why they wait. What a pause interrupted runs as the next attempt
List of objectives Read-only: each objective's status (and, when paused, what the pause does and why), the agent a running task is on, why a task waits, its result or its failure cause

Tasks screen#

Action Effect
Refresh Re-reads the objectives
Table One row per task: Objective, Task, Status, Attempts, Detail
Cancel (a CREATED, READY or RUNNING task) Cancels the task and the tasks that depend on it. A running attempt ends CANCELLED and the agent is released (or stopped if its turn cannot be interrupted)
Move (a CREATED, READY or BLOCKED task with no attempt running) Opens a field with the agents of the task's project and "Any eligible agent"; Confirm calls reassign_task for the attempt shown. A refusal (the last attempt still ends, the task moved on) shows above the table and the field stays open
Graph line (an objective with more than one task) Read-only: "Step N · after A (STATUS) · unblocks B (STATUS)". Tasks of one step wait for the same tasks and can run side by side
Evidence (a task that has any) A disclosure under the row: why the task is complete (the check that passed, or who approved it), the verification, what its attempts handed over (artifacts and refused outputs), the worktree they ran in, the decisions and the moves

Processes screen#

Action Effect
Search field Filters by PID, command or owner
Refresh Re-reads the runtime processes the Core has alive

MCP screen#

Action Effect
Refresh Re-reads the pending requests and connected clients
Server Read-only: status (Running or Off), local endpoint, public URL or "Local only"
Deny (a pending request) Refuses the remote client's authorization request, with one click
Approve Opens a field for the code shown on the browser page that started the connection. The window never displays that code
Confirm Approves the request if the typed code matches (otherwise an error appears under the field and the request stays pending). A request expires after 5 minutes
Cancel, Escape (in the code form) Closes the code form without deciding
Revoke, then Confirm revoke Takes away the access of a connected client. Cancel keeps it

Skills screen#

Lists the skills Cortex finds: the folders under skills in the data directory (global) and under .cortex/skills in each project. A skill is a folder with a skill.json manifest and its Markdown content, and nothing else but files. A new skill is disabled.

Action Effect
Rescan Reads the skill folders again
Enable Approves the version and content shown and starts delivering the skill to the agents it applies to. If the folder changed since the list was read, the Core refuses it and the list shows the new version
Disable Stops delivering the skill; its folder is kept. A changed skill (its files changed after it was enabled) offers Disable, or Approve to accept the new content
Not valid Folders that are not skills, with the reason
Conflicts Skills with the same name: override (the most specific scope is delivered) or ambiguous (same scope, different folders: none is delivered until one is disabled), with the agents affected
Effective set, Agent For the chosen agent, every skill that applies to it and whether it is delivered or why not. Changes apply when an agent starts: a running agent keeps the skills it started with

Keyboard summary#

Keys Where Effect
Enter Console and Orchestrator fields Send
Shift+Enter Console and Orchestrator fields New line
Enter or Space A focused agent card Select the agent
Arrow Left, Arrow Right, Home, End Side panel tabs Move between Orchestrator and Console
Escape Add agent and Edit agent dialogs, the MCP code form, the account menu Close or cancel
Tab Dialogs Focus stays inside the dialog

Type to search.