Native Shell
Native Shell은 모든 플랫폼의 앱 안에서 만들어지는 AI UI입니다
axon 런타임 기반 터미널 코딩 에이전트. 스트리밍 채팅·도구 호출·추가 전용 세션·보조 LLM 컨텍스트 정리까지 모두 터미널에서
A terminal coding agent built on the axon runtime. Streaming chat, tool dispatch, append-only sessions, and secondary-LLM context pruning — all from your terminal.
개발 도구Go · PythonGitHub @atakang7온라인운영자 미확인
써 보셨어요? 한 번만 눌러 주세요. 로그인은 필요 없어요.
Token efficient coding agent built on the axon runtime.
저장소 README에서 가져옴
README 전체를 불러오는 중입니다.
README 전문 보기 ↗A terminal coding agent built on the axon runtime.
Streaming chat · tool dispatch · append-only sessions · checkpoints the agent decides · a read-only explorer — all from your terminal.
Documentation · Install · Quick start · Architecture · Playground · GitHub
The full documentation site is at atakang7.github.io/cortex. This README renders that same documentation in markdown for reading on GitHub, in your editor, or offline.
cortex/
├── getting-started/
│ ├── install — pre-built binaries · go install · build from source
│ └── quick-start — env vars · interactive & non-interactive · first config
├── guides/
│ ├── configuration — the three-layer cascade · fields · known providers
│ ├── interactive — the transcript · Ctrl-C · the banner
│ ├── non-interactive — stdout/stderr split · iteration limits · piping
│ ├── commands — /new · /undo · /cd · /session · /help · /quit
│ └── keyboard — every key the terminal binds
├── concepts/
│ ├── architecture — axon owns the loop · cortex owns the product · two boundary rules
│ ├── pruner — the secondary model · what it sees · when to skip it
│ ├── trace-log — the third event sink · both models tapped · why it exists
│ └── mcp-servers — MCP protocol · auto-discovery · multiple servers
├── playground/
│ ├── overview — run.sh & trace.sh · the run directory · the six views
│ ├── running-tasks — step by step · what a run keeps · comparing runs
│ ├── reading-traces — timeline · prompt · system · tools · usage · errors
│ └── tasks — ledger · report · pipeline · what the playground found
└── reference/
├── cli — --config · --prompt · --version · exit codes
├── config-file — every field · pruner · mcp_servers · env overrides
└── api-keys — resolution order · providers.json · the leading $
Get the cortex binary on your machine in one of three ways.
Every release ships binaries for linux and darwin on amd64 and arm64. Download and extract in one line:
curl -L https://github.com/atakang7/cortex/releases/latest/download/cortex-$(uname -s | tr '[:upper:]' '[:lower:]')-$(uname -m | sed 's/x86_64/amd64/;s/aarch64/arm64/').tar.gz \
| tar xz -C /usr/local/bin cortex
If you have a Go toolchain:
go install github.com/atakang7/cortex/v2/cmd/cortex@latest
The binary lands in your $GOPATH/bin — make sure that's on your $PATH.
Native Windows builds are supported. With Git for Windows and Go installed,
run the same source build in PowerShell, using go build -o cortex.exe ./cmd/cortex.
Windows shell tools prefer Git Bash and use Windows Job Objects to stop whole
process trees; WSL is not required. CI tests Windows and publishes a
cortex-windows-amd64 executable artifact. The release configuration also
includes a Windows AMD64 ZIP for future releases.
git clone https://github.com/atakang7/cortex.git
cd cortex
go build -o cortex ./cmd/cortex
Check that it runs and prints build metadata:
cortex --version
cortex 1.2.3 (abc1234, built 2025-01-01T00:00:00Z)
ℹ️ If you installed via
go install, the version showscortex dev (none, built unknown)— the build metadata is only injected by the release pipeline's ldflags. The binary itself works identically.
What's next? cortex needs a model before it can start. Continue to Quick start to configure one.
Configure a model and run cortex in under a minute.
cortex needs one thing before it can start: a model. Two environment variables are enough:
export LLM_MODEL=gpt-4o
export LLM_API_KEY=sk-...
cortex
Run cortex with no arguments to start an interactive session in your current directory:
cortex # interactive
cortex --prompt "summarize the TODOs" # one turn, then exit
cortex --config reviewer.yaml # a specific config, no cascade
cortex --version
--prompt writes the assistant's answer to stdout and everything else — tool activity, notices, errors — to stderr, so it composes:
cortex --prompt "list every exported function in internal/ui" > api.txt
See Non-interactive mode for the full picture.
If you don't want to pass env vars every time, write a config file:
# ~/.config/cortex/config.yaml
provider: openrouter
model: deepseek/deepseek-v3.2
See Configuration for the full three-layer cascade.
If cortex starts without a model configured, it prints an onboarding message explaining the three places a model can come from — it does not guess or default.
💡 Tip: The transcript is committed to your terminal's own scrollback, not held in an alternate screen. A finished session stays scrollable and copyable after cortex exits.
Three sources, each overriding the last. Layering is field by field.
~/.config/cortex/config.yaml — your defaults (honours XDG_CONFIG_HOME)./cortex.yaml — this project's overridesLLM_PROVIDER, LLM_MODEL, LLM_BASE_URL, LLM_API_KEYLayering is field by field, so a project config that names only a model keeps the provider from your user config. You only specify what you want to override.
⚠️ Specifying
--configdisables the cascade entirely — only that file is read, plus the environment. Use it when you want a specific, self-contained config.
name: reviewer # shown in the banner
provider: openrouter # the axon provider name
model: deepseek/deepseek-v3.2 # the model within that provider
system_prompt: ./reviewer.md # a path is read from disk; prose is used as written
# pruner: # optional: who decides checkpoints; omit to let the main model decide
# model: some/other-model
explorer: # optional: a cheaper, read-only model behind the explore tool
model: z-ai/glm-5.3-flash
provider: openrouter # defaults to the main provider if omitted
mcp_servers: # spawned at startup, their tools auto-discovered
github:
command: docker
args: ["run", "-i", "--rm", "ghcr.io/github/github-mcp-server"]
env: ["GITHUB_TOKEN=..."]
cortex's config only names which provider and model to use. Endpoints and credentials live in axon's config — the shared providers.json the axon-family tools already use, or the LLM_API_KEY / LLM_BASE_URL environment variables. See API keys for the resolution order.
provider supplies base_url for openai, openrouter, anthropic, groq, deepseek and ollama. Any other provider must state its own base_url (via axon's config or LLM_BASE_URL). A localhost endpoint (e.g. ollama) needs no API key.
Omit system_prompt and cortex uses its built-in coding-agent role text. The tool catalog is appended by axon, so a custom prompt must not enumerate tools — or you pay for those tokens twice on every call. See Architecture for why.
export LLM_MODEL=gpt-4o
export LLM_API_KEY=sk-...
cortex
The terminal UI — a transcript committed to your scrollback, not an alternate screen.
cortex
cortex runs in your current working directory. It reads the repo, streams the model, dispatches tools, and renders everything into your terminal's own scrollback. When cortex exits, the finished session stays scrollable, searchable and copyable.
The transcript is committed to your terminal's own scrollback rather than held in an alternate screen. Only work still in flight redraws. This is a deliberate choice: a finished session is something you scroll back through, search, and copy from — including after cortex exits.
Press Ctrl-C to interrupt the running turn. cortex treats Ctrl-C in raw mode as a key, not a kill signal — it cancels the turn cleanly and returns you to the prompt.
ℹ️ Why not SIGINT? In raw mode Ctrl-C arrives as a key. cortex binds it to cancelling the turn rather than killing the process. Only signals the terminal cannot deliver as a keystroke (
SIGTERM,SIGHUP) end the program from outside.
Each session opens with a banner showing the cortex version, the provider and model in use, and the current turn count. The name field from your config appears here too.
When the model is changed interactively, cortex persists the new provider and model to your user config. The same applies to the checkpoint model and the explorer. So the next session picks up where you left off.
For the keys and commands available inside a session, see:
Pipe cortex into scripts with --prompt.
--prompt runs a single turn with no TUI, then exits. The assistant's answer goes to stdout; tool activity, notices and errors go to stderr. That separation lets you compose:
cortex --prompt "list every exported function in internal/ui" > api.txt
cortex --prompt "summarize the TODOs" | fold -s | head -40
In non-interactive mode, Ctrl-C ends the process — the ordinary expectation for a one-shot command. This is different from interactive mode, where Ctrl-C cancels the turn.
A --prompt run is bounded by maxIterationsOnce, a cap on how many tool turns the agent may take before it must answer. Interactive mode is unbounded — a human is watching, and Ctrl-C ends a turn that has lost its way.
ℹ️ Why bounded? With no one watching, an agent that loops forever is a bill, not a bug. The cap is deliberately conservative for one-shot use.
cortex --config reviewer.yaml --prompt "review the last commit"
--config disables the cascade and reads only that file plus the environment. Combine with --prompt for reproducible one-shot runs against a fixed configuration.
Type a slash command at the prompt to control the session.
| Command | What it does |
|---|---|
/new | Wipe the session and start over. |
/undo | Revert the last file edit, byte for byte. |
/cd <path> | Change the working directory. |
/pwd | Show the working directory. |
/session | Session file, turn count, pending undos. |
/model | Select a different model for this session. |
/pruner | Choose the model that decides checkpoints (default: the main model). |
/explorer | Choose the cheaper, read-only model behind the explore tool, or none. |
/switch | Switch to a different saved session. |
/help | List the commands. |
/quit | Exit (/exit, /q also work). |
/undo — byte-for-byte revertEvery file edit is atomic and recorded. /undo reverts the last edit exactly — the previous content is restored, not reconstructed. Repeat /undo to step backwards through the session's edits.
/session — inspect stateShows the path to the append-only session file, the current turn count, and how many undos are pending. Useful when you want to find the session on disk or understand how far the conversation has gone.
/new — clean slateWipes the in-memory session and starts a fresh turn counter. The old session file remains on disk — /new only affects the live conversation, not persisted history.
💡 Aliases:
/quit,/exitand/qall exit.
Every key the terminal binds, in one table.
| Key | Action |
|---|---|
enter | Send the current input. |
ctrl+j / alt+enter | Insert a newline. |
ctrl+c / esc | Interrupt the running turn. |
ctrl+c (when idle) | Clear the input, then exit. |
ctrl+d | Exit. |
Ctrl-C is context-sensitive. While a turn is running, it cancels the turn — the agent stops and you return to the prompt. When idle (no turn in flight), the first Ctrl-C clears the input buffer; a second exits.
Esc is bound to the same interrupt as Ctrl-C, so you can cancel a turn even on a terminal where Ctrl-C is captured by the shell or a multiplexer.
Ctrl+J and Alt+Enter both insert a literal newline, so you can write multi-line prompts without accidentally sending them.
ℹ️ Raw mode. cortex runs the terminal in raw mode, so keystrokes are intercepted individually rather than line-buffered. That's why Ctrl-C is a key, not a signal — see Interactive mode.
axon owns the loop. cortex owns the product on top. Layers point one direction.
┌─────────────────────────────────────────────────────────┐
│ cortex — the product │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ main.go │ │ config │ │ ui │ │
│ │ flags │ │ resolve │ │ terminal │ │
│ │ wiring │ │ model │ │ transcrip│ │
│ │ dispatch │ │ prompt │ │ commands │ │
│ └──────────┘ └──────────┘ └──────────┘ │
│ config & theme are leaves · ui owns the terminal │
└───────────────────────┬─────────────────────────────────┘
│ the only seam
┌───────────────────────┴─────────────────────────────────┐
│ axon — the runtime (↗ docs) │
│ ┌────────┐ ┌─────────┐ ┌────────┐ ┌────────┐ │
│ │ loop │ │ session │ │ pruner │ │ tools │ │
│ │ stream │ │ append │ │ 2nd-LLM│ │ 7 built│ │
│ │ chat │ │ undo │ │ context│ │ + MCP │ │
│ └────────┘ └─────────┘ └────────┘ └────────┘ │
└─────────────────────────────────────────────────────────┘
internal/config imports nothing else from this module. It is a leaf. If a value there needs a type from a higher layer, it belongs in that layer.
internal/ui/event.go is the only file that touches both axon and Bubble Tea. axon calls its event handler synchronously on the turn goroutine; Bubble Tea owns the terminal from its own. Nothing else may cross that line.
main.go wiresmain.go is wiring and nothing else. Every decision it makes is delegated: configuration to internal/config, presentation to internal/ui, and the loop itself to axon.
Parse flags → Load config → Tap models → Run loop
⚓ ⚙️ 📡 ▶
interactive resolve model trace build axon Options
non-interactive & pruner wiretap attach handler
version vs axon main+pruner go
The tool catalog is appended by axon, natively, to every request. If a custom system_prompt also enumerates the tools, the model sees the catalog twice — and you pay for those duplicated tokens on every single call. This is exactly the kind of thing the trace log was built to catch: a ~2,100-token duplication that was invisible in the event stream and obvious in the wire log.
| alternate screen | committed scrollback |
|---|---|
| Vanishes on exit. The session is gone the moment cortex stops — nothing to scroll, search or copy. | Stays in your terminal's own scrollback. A finished session is scrollable, searchable and copyable — even after cortex exits. Only work in flight redraws. |
📖 Deeper into axon: the loop, session, pruner and tools are all axon's. Read axon's architecture, context management, and the built-in tools for the layer cortex builds on.
The goal is to finish real work in a 20–30K token context instead of hundreds of thousands, without a cheap model ever deciding anything. One rule: cheap models read; the main model decides.
The agent chooses what stays in its own context with the checkpoint tool: keep a result whole, keep only some of its lines, or replace it with a one-line note. Everything dropped stays in the session log and comes back with recall. The agent calls it when a step is done, and the runtime asks for one when the context reaches the trigger (pruner.mode in axon: low 48K, moderate 32K, extreme 20K). Asked of the main model, a checkpoint reuses the cached prompt, so it costs about one ordinary step. If the work really needs more, the agent keeps it and the next checkpoint waits for growth beyond that.
pruner.model names a different model for this decision. Leave it empty to let the main model decide — the default.
With explorer.model set, the agent gets an explore tool. It hands a broad question ("who calls Refresh, starting from auth/") to a cheaper model that may only read and search, in its own throw-away context. It reports facts with quotes; cortex checks every quote against the file and passes on only the verified ones, labelled as the explorer's reading. The agent still reads raw whatever it edits.
Both side models are tapped into the trace log next to the main one, so you can read what a checkpoint kept and what the explorer read.
📖 The mechanism is axon's. See axon's
WORKING_CONTEXT.mdfor the full design and its guarantees.
A third, unfiltered event sink — wire-level request and reply logging.
┌──────────────────┐
│ axon runtime │
│ stream · tools │
│ pruner │
└────────┬─────────┘
│ every request & reply
┌────────┴──────────────────────┐
▼ ▼ ▼
┌─────────────┐ ┌──────────────┐ ┌──────────────┐
│ the TUI │ │ plain render │ │ the trace │
│ what user │ │ stdout = ans │ │ the wire │
│ sees │ │ stderr = all │ │ exact req/rsp│
└─────────────┘ └──────────────┘ └──────────────┘
Both the main model and the pruner are tapped at the one place they are resolved — so the trace holds the actual prompts, not just the rendered replies.
The trace log is written to DefaultTracePath(), a path under your cache directory. At startup cortex prints the path to stderr:
cortex: trace: /home/you/.cache/cortex/trace-20250101-120000.jsonl
A broken cache directory means no trace, not no cortex. If the trace log can't be opened, cortex prints a notice to stderr and continues without it:
cortex: trace log disabled: <error>
The pruner's context decisions are as worth auditing as the agent's, and they are invisible in the event stream — the stream only reports how many tokens a pass moved, never why. Tapping both models at the one place they are resolved is what puts those decisions in the trace, so you can read what the pruner decided to park and what it kept.
The trace opens with a Describe block recording the cortex version, the provider and model for both the main agent and the pruner, and the working directory. This anchors every trace to the exact configuration that produced it.
The trace log is the tool you reach for when the agent did something surprising and you want to know why — what prompt the model actually received, what context the pruner had decided to park, what the model said before the UI rendered it. It's prompt-engineering against reality, not against the abstraction.
📖 The event stream is axon's. axon emits structured events describing every reply — the events reference covers them. The trace log adds the wire layer those events are built on top of. See Reading traces for the tool that views them.
Spawn any Model Context Protocol server at startup; its tools are wired in automatically.
The Model Context Protocol is an open standard for exposing tools, resources and prompts to LLMs. An MCP server is a process that speaks the protocol — GitHub's server, a filesystem server, a database server, anything.
mcp_servers:
github:
command: docker
args: ["run", "-i", "--rm", "ghcr.io/github/github-mcp-server"]
env: ["GITHUB_TOKEN=ghp_..."]
Each entry under mcp_servers names a server and gives the command, arguments and environment to spawn it. cortex starts the server at startup, discovers its tools over the protocol, and wires them into the agent's tool set.
You don't enumerate the tools a server provides — cortex asks the server what it offers and adds every tool to the catalog automatically. If the server adds a new tool, it shows up on the next session with no config change.
The env list is passed to the spawned process. Use it for API tokens and any other secrets the server needs. The values are strings; a leading $ does not expand here (unlike api_key), so pass the literal value or interpolate from your shell before writing the config.
⚠️ Security: an MCP server runs with the same filesystem and network access as the cortex process. Only spawn servers you trust.
mcp_servers:
github:
command: docker
args: ["run", "-i", "--rm", "ghcr.io/github/github-mcp-server"]
env: ["GITHUB_TOKEN=..."]
sqlite:
command: npx
args: ["-y", "@modelcontextprotocol/server-sqlite", "--db-path", "./data.db"]
Tools from all servers are merged into a single catalog. Name collisions are resolved by the server prefix.
📖 MCP is axon's. cortex spawns the servers in config; axon speaks the protocol, discovers the tools, and merges them with its built-in tools into one catalog. The auto-discovery is axon's — see axon's tools reference for the full toolset cortex gets on top of MCP.
A harness for watching cortex work and finding out why it works the way it does.
Not a benchmark —
benchmarks/polyglotscores cortex. This one shows its reasoning so a bad habit can be traced to the prompt or tool description that caused it.
./run.sh <task> [label] # run cortex on one task, keep everything
./batch.sh [-n runs] [task ...] # repeat a suite and keep going on failure
./report.sh [runs/<task-label> ...] # summarize pass/fail, cost and behavior
./trace.sh <view> <trace> # read what happened inside one run
Each run gets its own directory under runs/ holding a pristine copy of the task's seed project, the trace cortex wrote, cortex's own stdout, and verifier output. The seed is never touched, so a run is repeatable and two runs are directly comparable:
trace.jsonl — the wire-level recorddiff.patch — exactly what the agent changedstdout.txt / stderr.txt — the agent's own outputverify_stdout.txt / verify_stderr.txt — final project verificationstatus.txt — git status of the work treeNewline-delimited JSON with one discriminator, kind. Most kinds are axon event names. Three are cortex's own:
| kind | what it holds |
|---|---|
run | which build and which models produced this trace |
wire_request | the exact messages sent to a model, and to which one |
wire_reply | what came back, and how long it took |
The wire kinds are the reason this harness exists. Events are emitted downstream of a request — they describe the reply and never the prompt — so the composed system message, the windowed conversation and the pruner's own curator calls are all invisible without them. Prompt engineering against the event stream alone is guesswork.
trace.sh offers six views, each one jq filter over the trace:
./trace.sh timeline runs/report-final/trace.jsonl
./trace.sh system runs/report-final/trace.jsonl # the prompt under test
./trace.sh prompt runs/report-final/trace.jsonl pruner
./trace.sh tools runs/report-final/trace.jsonl # shows a wrong approach fastest
report.sh is the first thing to read after a suite. It flags failed verification, tool errors, runtime errors, no-op prunes, large call counts, and no-diff runs before you dive into individual traces.
Continue to Running tasks and Reading traces for the details.
run.sh drives cortex through one playground task and keeps everything it did.
./run.sh <task> [label]
run.sh ledger iter1 copies tasks/ledger/seed into runs/ledger-iter1/work.git diff in the work directory is then exactly what the agent changed, with no other setup..gitignore is written so build artefacts (__pycache__/, *.pyc, .pytest_cache/) don't fill the diff with binary noise and bury the one source change that matters.$PATH. The whole point is to test the code you're working on.go test ./..., python -m pytest -q) and runs it in the work directory after cortex exits, recording the result.tasks/<task>/TASK.txt, writing stdout and stderr to the run directory.run.sh copies it into the run directory so the directory is self-contained once the cache is cleaned.go test ./..., python -m pytest -q, or npm test) and records the result.diff.patch — staged so files the agent created show up as additions rather than untracked paths the patch never mentions.runs/ledger-iter1/
├── work/ # pristine seed copy + the agent's changes
├── cortex # the freshly-built binary
├── trace.jsonl # the wire-level record
├── diff.patch # exactly what the agent changed
├── stdout.txt # the agent's answer (stdout)
├── stderr.txt # notices, tool activity, errors
├── verify.status # verifier exit status
├── verify_stdout.txt
├── verify_stderr.txt
└── status.txt # git status of the work tree
./batch.sh -n 3 report pipeline
Batch runs keep going after failures and write a markdown summary under runs/. Use this for prompt or tool changes so one unlucky run does not hide the rest of the suite.
💡 Comparing runs: because the seed is never touched and each run is self-contained, two runs are directly comparable. Diff the
diff.patchfiles, or comparetrace.jsonlwith the trace views.
trace.sh reads a cortex trace. Six views, each one jq filter.
./trace.sh <view> <trace.jsonl> [arg]
timelineOne line per thing that happened, with just enough payload to see the shape of a run. Streaming token noise is dropped — it's the same text the assistant_end line carries in full. Great for getting the overall shape of a run at a glance.
./trace.sh timeline runs/report-final/trace.jsonl
promptEvery message of one model call, exactly as the provider saw it. Defaults to the last main-model request; pass a call number for a specific one, or pruner for the curator's last.
./trace.sh prompt runs/report-final/trace.jsonl # last main call
./trace.sh prompt runs/report-final/trace.jsonl 3 # call #3
./trace.sh prompt runs/report-final/trace.jsonl pruner # the pruner's last call
systemThe system message alone — the prompt under test, plus whatever axon appends (including the tool catalog). This is the view for prompt engineering: it shows the composed system prompt the model actually received.
./trace.sh system runs/report-final/trace.jsonl
toolsWhat the agent actually did, in order, with full arguments and a clipped result. This is the view that shows a wrong approach fastest.
./trace.sh tools runs/report-final/trace.jsonl
usageToken spend per call and in total. Reported by the provider, not estimated.
./trace.sh usage runs/report-final/trace.jsonl
errorsEvery error, tool error and wire error, with timestamps.
./trace.sh errors runs/report-final/trace.jsonl
💡 The pattern worth keeping: when the agent misbehaves, read
toolsfirst to see what it did, thenpromptto see what it was told. The cause is almost always something it was told, and usually somewhere more structural than the role prompt.
A task is a seed/ project and a TASK.txt written the way a user would write it.
Seeds ship with a passing test suite so a regression is unambiguous. The debugging tasks are the useful ones — feature work rewards a competent model; debugging rewards one that plans, reads before concluding, and knows what it has not checked.
| task | shape | what it tests |
|---|---|---|
ledger | small CLI, suite passes | a feature across four files, with a backward-compatibility trap in the on-disk format |
report | four modules, four failing tests | a bug whose symptom and cause are in different files, with a plausible wrong fix at the symptom |
pipeline | 16 modules, 41KB, three failing tests | a bug visible only by comparing two files neither of which is wrong alone; large enough to put the pruner under load |
⚠️
pipelineis the only task big enough to exercise the pruner. Keep it that way — a curator that never runs is a curator nobody is testing.
Each of these was invisible in the event stream and obvious in the wire log:
Duplicated tool catalog (~2,100 tokens/call) — axon restated the whole tool catalog in the system prompt while the same tools were already sent natively. ~2,100 duplicated tokens on every call.
write steered to whole-file rewrites — Rewriting the tool's prose changed nothing; reordering the mode enum in its JSON Schema changed everything. Where prose and schema disagree, the schema wins.
replace_string's not-found error recommended replace_lines — A run took the advice, used line numbers from the same stale read, destroyed a docstring and spent two calls repairing it.
The pruner fired on an empty log — The pruner fired on a context of 10,229 tokens and sent the curator an empty log — no task, no blocks, 223 bytes. ShouldFire counts tokens while the recency window counts blocks, so a few large files early in a task clear the floor while every block is still inside the window. The curator answered {"park":[]}, correctly, having been shown nothing; the context was unchanged, so it re-fired every growth_tokens after that.
💡 The pattern worth keeping: when the agent misbehaves, read
toolsfirst to see what it did, thenpromptto see what it was told. The cause is almost always something it was told, and usually somewhere more structural than the role prompt.
cortex takes a small number of flags. Everything else lives in config.
| Flag | Default | Description |
|---|---|---|
--config <path> | — | Path to a config file instead of the usual cascade. Disables the cascade entirely — only that file is read, plus the environment. |
--prompt <text> | — | Run one prompt non-interactively and exit. The answer goes to stdout; everything else to stderr. Bounded by maxIterationsOnce. |
--version | — | Print the version, commit and build date, then exit. |
| Invocation | Mode |
|---|---|
cortex | Interactive (the TUI) |
cortex --prompt "…" | Non-interactive (one turn, then exit) |
cortex --version | Version only |
0 — normal exit1 — an error occurred (printed to stderr as cortex: <error>)The version, commit and build date are injected at link time by goreleaser. A source build without ldflags reports cortex dev (none, built unknown).
ℹ️ Why so few flags? Configuration belongs in the config file, not on the command line. The only flags are mode switches and the override path.
Every field in a cortex config file, what it means and what it overrides.
| Source | Path | Notes |
|---|---|---|
| User config | ~/.config/cortex/config.yaml | Honours XDG_CONFIG_HOME |
| Project config | ./cortex.yaml | Overrides user config, field by field |
| Environment | LLM_* | Overrides both |
| Explicit | --config <path> | Disables the cascade entirely |
| Field | Type | Description |
|---|---|---|
name | string | Labels this agent personality (e.g. cortex, reviewer). Shown in the banner. |
provider | string | The axon provider name (e.g. openai, openrouter). Supplies a default base_url for known providers. |
model | string | The model within that provider (e.g. gpt-4o, deepseek/deepseek-v3.2). |
system_prompt | string | The agent's role text. A path is read from disk; prose is used as written. Omit to use the built-in coding-agent prompt. Must not enumerate tools — axon appends the catalog. |
pruner | object | The model that decides checkpoints. Empty: the main model. See below. |
explorer | object | The read-only model behind the explore tool. Empty: no explore tool. See below. |
mcp_servers | map | MCP subprocesses to spawn at startup. See below. |
pruner| Field | Type | Description |
|---|---|---|
pruner.provider | string | Provider for the checkpoint model. Defaults to the main provider if omitted. |
pruner.model | string | The model that decides what stays in context. Empty means the main model decides (recommended). |
pruner.models | list | Retired (the free review pool). Accepted and ignored, with a warning. |
explorer| Field | Type | Description |
|---|---|---|
explorer.provider | string | Provider for the explorer model. Defaults to the main provider if omitted. |
explorer.model | string | A cheaper model that answers read-only questions with verified quotes. Empty means no explore tool. |
mcp_serversEach key is a server name. Each value has:
| Field | Type | Description |
|---|---|---|
command | string | The executable to spawn. |
args | []string | Arguments passed to the command. |
env | []string | Environment variables for the spawned process. Literal values — no $ expansion. |
Only model selection is overridable by the environment. Credentials and endpoints are axon's concern.
| Variable | Overrides |
|---|---|
LLM_PROVIDER | provider |
LLM_MODEL | model |
LLM_BASE_URL | The provider's default base_url |
LLM_API_KEY | API key (see API keys) |
⚠️ Providers and credentials are not in this file. cortex does not define endpoints or carry credentials — those live in axon's
providers.jsonor theLLM_*environment. cortex only names which provider and model to use.
Where the key comes from. Checked in order; the first one that answers wins.
api_key in axon's providers.json — a leading $ reads that environment variable at startup, so you never commit the key itself.LLM_API_KEY environment variable.~/.config/agent/providers.json, matched on the provider name — the shared file the axon-family tools already use. Override its path with AXON_PROVIDERS_PATH.The third exists so a key has one home on disk. If you already keep credentials in providers.json, omit the key from your cortex config entirely and cortex will find it:
{ "providers": [
{ "name": "openrouter", "base_url": "https://openrouter.ai/api", "api_key": "sk-or-..." }
] }
cortex reads only name and api_key from that file; the model lists and routing options in it belong to other tools and are ignored.
A localhost endpoint (e.g. ollama running locally) needs no API key. Point LLM_BASE_URL at it and leave the key unset.
$Wherever a config accepts an api_key value, a leading $ reads the rest as an environment variable name and substitutes its value at startup. This keeps the literal key out of your config file:
{ "providers": [
{ "name": "openai", "api_key": "$OPENAI_API_KEY" }
] }
ℹ️ Why three sources? The cascade lets you keep a default provider in your user config, override only the model per project, and keep the actual credential in one shared file. No copying keys between configs.
atakang7.github.io/cortex · MIT · built on axon