Configuration¶
Cortex loads all configuration from $CORTEX_HOME/config/ at startup. The
only required variables are CORTEX_PLATFORM and the platform credentials
(Slack). Everything else has sensible defaults and most users
never touch them.
File hierarchy¶
All paths below are relative to $CORTEX_HOME (default: ~/.cortex/).
$CORTEX_HOME/
├── .env # Platform tokens, feature flags
├── config/
│ ├── .env # Same file (symlinked / canonical location)
│ ├── settings.json # Runtime behavior settings (hot-reloaded)
│ ├── profiles.json # Named agent profiles
│ ├── thread-templates/ # Thread config — one JSON per agent/template/shell
│ ├── machines.json # Machine registry for remote clients
│ ├── budget.json # Daily/monthly budget limits
│ ├── mcp-config.json # Direct-session MCP configuration
│ ├── mcp-config-core.json # Remote execution/time layer
│ ├── mcp-config-tasks.json # Read-only task-monitoring layer
│ ├── mcp-config-manager-qa.json # Shared manager-answer layer
│ ├── mcp-config-thread.json # Thread-control layer
│ ├── mcp-config-tui.json # TUI interaction layer
│ └── hooks/ # Hook registry — one JSON declaration per hook
├── data/
│ ├── mode.json # Current runtime mode and profile
│ ├── schedules.json # Persistent scheduled task list
│ ├── executions.json # Unified execution registry
│ ├── costs.jsonl # 90-day rolling cost records
│ └── sessions.json # Channel-to-agent session mapping
├── .claude/
│ └── settings.json # Claude Code hooks and permissions
├── hooks/ # Hook scripts (.mjs)
├── plugins/ # Role-scoped skill plugins
├── prompts/ # System prompts, directives, templates
├── rules/ # Context rules for agent sessions
├── context/ # Dense Context knowledge repository
│ └── projects/ # Research project files
├── logs/ # Daemon and LLM session logs
└── tmp/ # Temporary workspaces (threads, etc.)
Loading order and precedence¶
- Built-in defaults (
agent-server/defaults/) ship with the npm package and provide fallback values for every config file. $CORTEX_HOME/config/.envis loaded at daemon startup viadotenv. These override the process environment for the daemon and all forked child processes.$CORTEX_HOME/config/settings.jsonholds the runtime behavior settings. It is read at the point of use and hot-reloaded on change. For every key it defines, it overrides the legacy environment variable of the same setting.$CORTEX_HOME/config/profiles.jsonis read on every agent spawn to resolve model, backend, and extra environment.$CORTEX_HOME/.claude/settings.jsonis read by Claude Code (not by Cortex directly) to configure hooks and permissions for the coding-agent backend.
The .env file supports standard KEY=VALUE syntax and # comments.
Environment variables already set in the shell take precedence over the
.env file (dotenv default behavior).
Environment variables¶
All values are loaded from the .env file at $CORTEX_HOME/config/.env.
Only CORTEX_PLATFORM and your platform credentials are required.
Server behavior settings no longer live here: they moved to
config/settings.json, where they hot-reload instead of
requiring a restart. Their old variables still work as a deprecated fallback and
are migrated out of .env automatically on the next daemon start.
Paths¶
| Variable | Default | Purpose |
|---|---|---|
CORTEX_HOME |
~/.cortex/ |
User data root (config, context, logs, store) |
CORTEX_PROJECTS_DIR |
<CORTEX_HOME>/context/projects/ |
Override project directory |
CORTEX_REPO |
— | Repo path for daemon auto-rebuild / hot-reload |
Startup¶
| Variable | Default | Purpose |
|---|---|---|
CORTEX_MACHINE |
os.hostname() |
Machine label for startup DM |
CORTEX_RESTART_REASON |
— | Reason string for restart notification |
CORTEX_CLIENT_PORT |
3002 |
WebSocket port for cortex-client manager |
Platform¶
CORTEX_PLATFORM selects the messaging platform(s). It accepts a single value
(slack, feishu) or a comma-separated list to run several at once
(slack,feishu). Each platform whose credentials are present is brought online;
the optional TUI gateway (CORTEX_TUI) is added on top. With multiple platforms,
messages route by platform and system notices fan out to each platform's admin
channel.
| Variable | Required | Purpose |
|---|---|---|
CORTEX_PLATFORM |
yes | slack (default). Comma list for multi-platform, e.g. slack,feishu |
SLACK_BOT_TOKEN |
for slack | Slack Bot OAuth token (xoxb-...) |
SLACK_SIGNING_SECRET |
for slack | Slack app signing secret |
SLACK_APP_TOKEN |
for slack | Slack app-level token for Socket Mode (xapp-...) |
FEISHU_APP_ID |
for feishu | Feishu app ID (cli_...) |
FEISHU_APP_SECRET |
for feishu | Feishu app secret |
FEISHU_ENCRYPT_KEY |
no | Feishu event encrypt key (optional with long-connection) |
FEISHU_VERIFICATION_TOKEN |
no | Feishu event verification token (optional) |
FEISHU_DOMAIN |
no | feishu (default) or lark for the international edition |
FEISHU_CHANNEL |
no | Feishu channel ID (auto-set by session) — identifies the current Feishu conduit for MCP tools |
The admin channels for system notices are settings, not environment variables:
see adminChannel and feishuAdminChannel in
config/settings.json. The legacy variables
SLACK_ADMIN_CHANNEL, CORTEX_ADMIN_CHANNEL, and FEISHU_ADMIN_CHANNEL are
still read as a deprecated fallback.
API¶
| Variable | Purpose |
|---|---|
ANTHROPIC_API_KEY |
Anthropic API key for direct-API mode |
ANTHROPIC_BASE_URL |
Override API base URL (auto-set by gateway proxy) |
Rate limiting (Slack)¶
| Variable | Default | Purpose |
|---|---|---|
CORTEX_SLACK_RL_GLOBAL_CAPACITY |
20 |
Global API call bucket capacity |
CORTEX_SLACK_RL_GLOBAL_REFILL_PER_SEC |
1 |
Global refill rate per second |
CORTEX_SLACK_RL_CHANNEL_CAPACITY |
1 |
Per-channel bucket capacity |
CORTEX_SLACK_RL_CHANNEL_REFILL_PER_SEC |
1 |
Per-channel refill rate per second |
Webhook¶
| Variable | Default | Purpose |
|---|---|---|
WEBHOOK_PORT |
3001 |
Webhook HTTP server port |
WEBHOOK_HOST |
127.0.0.1 |
Fallback host for remote clients (when Tailscale/LAN IP not detected) |
GITHUB_WEBHOOK_SECRET |
— | GitHub webhook HMAC-SHA256 signing secret |
Data file overrides¶
| Variable | Default | Purpose |
|---|---|---|
CORTEX_EXECUTIONS_FILE |
<STORE_DIR>/executions.json |
Execution records |
CORTEX_COSTS_FILE |
<STORE_DIR>/costs.jsonl |
Cost tracking |
CORTEX_BUDGET_FILE |
<CONFIG_DIR>/budget.json |
Budget configuration |
Feature flags¶
| Variable | Default | Purpose |
|---|---|---|
DEBUG |
— | Enable server-wide debug mode. In addition to debug-level logs, the desktop transcript shows hover inspectors for the exact agent message and each tool call's complete input/result. Any non-empty value enables it; restart agent-server after changing it |
CORTEX_DEBUG_TOOL_WARNING_CHARS |
10000 |
In DEBUG transcripts, mark a tool-name badge amber when its formatted complete parameters plus complete result strictly exceed this Unicode character count. Positive integers only; invalid values use the default. Restart agent-server after changing it |
CORTEX_GPU_MONITOR_MOCK |
— | Mock GPU data JSON for testing (overrides real nvidia-smi queries) |
The event log, tool-call rendering, user-context injection, update check,
compaction notice, turn notification, and auto-resume switches are now settings
keys — see config/settings.json.
DEBUG persists unabridged prompts, tool parameters, and tool results in per-session conversation-history files. These values can contain secrets, private file contents, or large outputs, and storage grows with their full size. Enable this mode only on a trusted development server and turn it off when inspection is complete. Turning it off immediately removes debug fields and buttons from transcript responses, but it does not delete records captured earlier.
profiles.json¶
Located at $CORTEX_HOME/config/profiles.json. Defines named agent profiles
that control which backend, model, and extra configuration each agent session
uses. For a comparison of available backends, see
backends.md.
Schema¶
{
"defaultProfile": "plan",
"profiles": {
"plan": {
"model": "claude-sonnet-4-20250514",
"backend": "claude",
"mode": "plan",
"claudeBackend": "print",
"extraEnv": {},
"extraOption": {},
"fallback": []
},
"execute": {
"model": "claude-sonnet-4-20250514",
"backend": "claude",
"mode": "execute",
"claudeBackend": "print",
"extraEnv": {},
"extraOption": {}
}
}
}
Fields¶
| Field | Type | Required | Description |
|---|---|---|---|
defaultProfile |
string | yes | Name of the default profile when none is specified |
profiles |
object | yes | Map of profile name to profile entry |
profiles.<name>.model |
string | yes | Model identifier (e.g. claude-sonnet-4-20250514) |
profiles.<name>.backend |
string | no | Backend: claude or pi (default: claude) |
profiles.<name>.mode |
string | no | Operational mode identifier (free-form, e.g. plan, execute) |
profiles.<name>.extraEnv |
object | no | Extra environment variables passed to the backend process. Keys must match ^[A-Z_][A-Z0-9_]*$. |
profiles.<name>.extraOption |
object | no | Extra CLI flags passed to the backend. Keys must start with --. |
profiles.<name>.claudeBackend |
string | no | Claude adapter mode: print (default, uses -p + stream-json) or tui (interactive Claude under tmux + jsonl tail). Ignored for non-claude backends. |
profiles.<name>.thinking |
string | no | Thinking level, in the backend's native value set: for claude one of low/medium/high/xhigh/max (passed as --effort), for pi one of off/minimal/low/medium/high/xhigh (passed as --thinking). Absent → no flag is passed. Fallback entries do not inherit it — each declares its own. |
profiles.<name>.fallback |
array | no | Ordered list of fallback profile entries. If the primary backend fails, Cortex tries each fallback in order. Each fallback inherits unspecified fields from the primary. |
Profile resolution¶
At agent spawn time, Cortex resolves the profile through this chain:
- If a profile name is explicitly provided (via
--profileor thread template), use it. - Otherwise, use
defaultProfilefromprofiles.json. - The resolved profile supplies
model,backend,mode,extraEnv,extraOption,claudeBackend, andthinking. - If the backend call fails with a transient error, Cortex iterates
through the
fallbackarray (if any), trying each entry in order.
Validation rules¶
Profile names must match ^[a-zA-Z0-9_-]+$. Backend must be claude
or pi. claudeBackend must be print or tui if specified.
thinking, if specified, must be a value from the entry's backend value
set (see the fields table). Unknown fields are silently ignored.
config/settings.json¶
Located at $CORTEX_HOME/config/settings.json. This file holds the runtime
behavior settings of the server: switches and limits that used to be
environment variables and needed a daemon restart to change. Editing this file
takes effect without a restart.
It is a flat JSON object that stores only the keys you explicitly override. Any key you leave out falls back to its legacy environment variable when one exists, and then to the built-in default. The file itself is optional — a fresh install has none and runs entirely on defaults. It is created the first time something writes it: the startup migration below, admin-channel auto-detection, or the Web workbench settings panels.
Values are type-checked against the schema below when the file is read. A key whose JSON type does not match rejects the whole file: at startup Cortex falls back to environment variables and defaults, and on a reload it keeps the last valid settings. Either way the reason is logged. Unknown keys are ignored.
Keys¶
| Key | Type | Default | Effect | Legacy env variable |
|---|---|---|---|---|
turnNotify |
boolean | true |
When a long-running turn finishes, post a fresh message to the conversation so you get a push notification (the inline status seals to "✓ Done" with an edit, which Slack and Feishu do not notify on). Both success and failure are announced | CORTEX_TURN_NOTIFY |
turnNotifyThresholdS |
number | 60 |
Minimum turn duration, in seconds, before that completion notification is posted. Shorter turns stay quiet | CORTEX_TURN_NOTIFY_THRESHOLD_S |
notifyCompaction |
boolean | false |
Post a chat notice when an agent's context is compacted. Covers the Claude Code (print mode) and pi backends; the notice names the trigger and, for Claude Code, the pre-compaction token count | CORTEX_NOTIFY_COMPACTION |
showToolCalls |
boolean | false |
Inline tool-call rendering in VirtualMessage tails | CORTEX_SHOW_TOOL_CALLS |
statusNewqButton |
boolean | false |
Show the "New (quiet)" button on status messages (=!newq, which skips the pre-close hook) |
CORTEX_STATUS_NEWQ_BUTTON |
autoResume |
boolean | true |
When a usage-limit window resets, automatically continue the conversations and threads the limit interrupted, injecting a note to pick up where they left off. Set to false to leave interrupted work paused for manual continuation |
CORTEX_AUTO_RESUME |
streamDeltas |
boolean | true |
Stream assistant text token by token. Disable to deliver each assistant message in one piece | CORTEX_STREAM_DELTAS |
bgContinuation |
boolean | true |
Forward the output of background tasks back into the conversation when they finish | CORTEX_BG_CONTINUATION |
eventLog |
boolean | true |
Write the event bus to the daily rolling JSONL event log | CORTEX_EVENT_LOG |
diskMonitor |
boolean | true |
Check free space on the filesystem containing $CORTEX_HOME every five minutes and send a system notice below 500 MiB. false stops the timer; switching back to true runs an immediate check |
CORTEX_DISK_MONITOR |
disableUserContext |
boolean | false |
Set to true to stop injecting USER.md context into direct conversation turns (injected by default; multi-agent thread steps never receive it) |
CORTEX_DISABLE_USER_CONTEXT |
serverUpdateDisable |
boolean | false |
Set to true to disable the server auto-update check (enabled by default) |
CORTEX_SERVER_UPDATE_DISABLE |
hooksLegacy |
boolean | false |
Bypass the hook registry and build Claude hook settings from the fixed built-in table instead. See hooks.md | CORTEX_HOOKS_LEGACY |
managerRotateSteps |
number | 10 |
Steps a manager session runs before it is rotated into a fresh incarnation. See threads.md | CORTEX_MANAGER_ROTATE_STEPS |
waitingSweepMs |
number | 60000 |
Interval, in milliseconds, of the disk-driven sweep that reconciles waiting manager threads against on-disk task state. 0 disables the sweep (see the hot-reload exception below) |
CORTEX_WAITING_SWEEP_MS |
injectWaitMaxS |
number | 600 |
Upper bound, in seconds, on how long an injected mid-turn message waits for a reply before the busy gate is released. Prevents a wedged process from holding the daemon restart gate forever | CORTEX_INJECT_WAIT_MAX_S |
threadMaxDepth |
number | 5 |
Maximum nesting depth for spawning nested threads; a spawn at or beyond this depth is rejected | CORTEX_THREAD_MAX_DEPTH |
taskArtifactTemplates |
string[] | ["manager"] |
Templates whose dispatch threads keep their artifact on the task node instead of the temporary workspace | CORTEX_TASK_ARTIFACT_TEMPLATES (comma-separated) |
taskDispatchMaxConcurrent |
number | null | null |
Max number of task-dispatch threads allowed to run concurrently. A number set here is used as-is (use a positive value); null keeps the automatic policy max(4, os.cpus().length - 2) — all-but-2 cores, floored at 4 |
TASK_DISPATCH_MAX_CONCURRENT |
taskDispatchEnabled |
boolean | true |
Run the built-in task dispatcher. Switching from false to true triggers an immediate dispatch check |
— |
taskDispatchIntervalMs |
number | 30000 |
Interval between built-in dispatch checks, in integer milliseconds | — |
taskArchiveEnabled |
boolean | true |
Run the built-in completed-task archiver. Switching from false to true runs it immediately |
— |
taskArchiveIntervalMs |
number | 21600000 |
Interval between completed-task archive runs, in integer milliseconds | — |
memoryIndexRegenEnabled |
boolean | true |
Run the built-in experiment/knowledge/pattern index rebuild. Switching from false to true runs it immediately |
— |
memoryIndexRegenIntervalMs |
number | 86400000 |
Interval between memory-index rebuilds, in integer milliseconds | — |
uiCorsOrigins |
string[] | [] |
Origins that receive CORS headers from the Web UI HTTP host. See desktop-app.md | CORTEX_UI_CORS_ORIGINS (comma-separated) |
adminChannel |
string | null | null |
Slack channel for system notices (startup, rate-limit, disk alerts). The first DM to the bot is auto-detected and persisted here | SLACK_ADMIN_CHANNEL, then CORTEX_ADMIN_CHANNEL |
feishuAdminChannel |
string | null | null |
Feishu admin chat_id (oc_...) for the same notices. Independent of adminChannel — Slack channel ids are not usable on Feishu |
FEISHU_ADMIN_CHANNEL |
The Web workbench writes a subset of these from Settings → Notifications
(turnNotify, autoResume, notifyCompaction) and Settings → Advanced
(eventLog, diskMonitor, showToolCalls, disableUserContext,
serverUpdateDisable, and the built-in job switches and intervals). Every other key is edited by hand in the file.
Built-in job intervals must be integer milliseconds from 1000 through
2147483647, the safe range for Node timers. Enabled jobs run once at daemon
startup. Archive and memory-index runs never overlap; a changed interval takes
effect after an active run finishes.
Hot reload¶
The config directory is watched. A change to settings.json is debounced for
300 ms, the file is re-read, and the new values are used from then on — no
daemon restart. Two consequences worth knowing:
- Each setting takes effect at its own point of use: the next turn, the next
agent spawn, the next dispatch cycle, or the next HTTP request.
uiCorsOriginsis resolved per request;adminChannelandfeishuAdminChannelare pushed into the running platform adapter as soon as they change.diskMonitor=falsestops its timer, while restoringtruestarts the timer and checks immediately. Each built-in job switch likewise cancels or starts its timer immediately, and interval edits reschedule its next idle run. - A broken file never takes the server down. Invalid JSON or a type mismatch leaves the previous settings in place and logs the error; fix the file and the next write reloads it.
Exception — waitingSweepMs. The waiting-manager sweep re-arms itself after
each run using the current value, so raising or lowering the interval applies
from the next round. Setting it to 0 while the server runs stops the loop for
good: nothing re-arms it, and restoring a positive value later does not
restart it. Only a daemon restart does. (A value of 0 at startup means the
loop never starts.)
Legacy environment variables and deprecation¶
Keys with a legacy environment variable in the table keep it as a fallback,
parsed with its established semantics — CORTEX_EVENT_LOG=off, CORTEX_TURN_NOTIFY=0/false/off/no,
CORTEX_NOTIFY_COMPACTION=1, comma-separated lists for the two string[] keys,
and so on. Precedence is always: key in settings.json → legacy variable →
built-in default. For adminChannel the original chain is preserved:
SLACK_ADMIN_CHANNEL is consulted before CORTEX_ADMIN_CHANNEL.
The first time a legacy variable supplies a value, the daemon logs a
deprecation warning naming the variable and the setting it feeds
(Deprecated env <VAR> supplies settings.<key>; move it to settings.json). The
fallback exists for compatibility during migration — new configuration belongs
in settings.json.
Automatic migration out of .env¶
On every daemon start, right after .env is loaded, Cortex scans
$CORTEX_HOME/config/.env for the legacy variables listed above. If it finds at
least one:
.envis copied to.env.bak-<timestamp>in the same directory, where<timestamp>is an ISO 8601 instant with:and.replaced by-(for example.env.bak-2026-07-30T09-12-33-482Z).- Each legacy value is parsed with its old semantics and written into
settings.json. A key already present insettings.jsonis never overwritten — the file wins over.env. - The migrated assignments are removed from
.env, which keeps its original file permissions and gains the header comment# Legacy server settings migrated to settings.json; secrets remain in .env. - The dead variable
CORTEX_SERVER_UPDATE_ENABLE, which nothing reads any more, is dropped in the same pass.
The migration is idempotent: a .env with no legacy variables is left
untouched, and later starts do nothing. If any step fails, the error is logged,
.env is left intact, and the server keeps running on the environment
fallback — so a failed migration costs nothing but the deprecation warnings.
What stays in .env¶
Three categories of variables deliberately remain in .env:
- Secrets and credentials —
SLACK_BOT_TOKEN,SLACK_SIGNING_SECRET,SLACK_APP_TOKEN,FEISHU_APP_ID,FEISHU_APP_SECRET,ANTHROPIC_API_KEY,GITHUB_WEBHOOK_SECRET,CORTEX_CLIENT_TOKEN. They are settings in a loose sense, but keeping them out of a file that the Web UI can read and write keeps the blast radius small. - Variables consumed by child processes —
CORTEX_HOME,CORTEX_PROJECTS_DIR,WEBHOOK_PORT,DEBUG,CORTEX_LANG, and the data file overrides. Hooks, MCP servers, the CLI, andcortex-clientinherit the daemon's environment; a server-side JSON file would never reach them. - Startup topology —
CORTEX_PLATFORM,CORTEX_MACHINE,CORTEX_UI_HTTP,CORTEX_UI_PORT,CORTEX_UI_SPA_DIR. These decide which adapters and listeners exist at boot, so hot-reloading them would have no meaning; they require a restart by construction.
Not the same file as .claude/settings.json¶
$CORTEX_HOME/config/settings.json (this file) configures the Cortex
server. $CORTEX_HOME/.claude/settings.json configures Claude Code's
hooks and permissions and is read by the Claude CLI, not by Cortex. Same file
name, different directory, different owner, different schema — see the next
section.
.claude/settings.json (Claude Code)¶
Located at $CORTEX_HOME/.claude/settings.json. This file configures
Claude Code's hook and permission system. Cortex seeds it from
defaults/.claude/settings.json during cortex init and never
overwrites it on subsequent runs.
The file follows Claude Code's settings format with hooks and
permissions sections. See hooks.md for the hook
system documentation. It has nothing to do with the Cortex runtime
settings in config/settings.json.
defaults/config/ layout¶
The agent-server/defaults/ directory in the npm package contains
shipped defaults. Most are copied to $CORTEX_HOME/ during init; the hook
assets and the thread-template entities are additionally re-synced by the
server on every startup:
| Source | Destination | Overwrite behavior |
|---|---|---|
defaults/CORTEX.md |
$CORTEX_HOME/CORTEX.md |
Never |
defaults/gitignore |
$CORTEX_HOME/.gitignore |
Never |
defaults/.claude/settings.json |
$CORTEX_HOME/.claude/settings.json |
Never |
defaults/config/budget.json |
$CORTEX_HOME/config/budget.json |
Only with --force |
defaults/config/thread-templates/ |
$CORTEX_HOME/config/thread-templates/ |
Per-file copy-if-missing, at init and again at every server start: a shipped agent/template/shell file you do not have yet is added; a file you already have is never overwritten — --force does not apply to this tree |
defaults/config/hooks/ |
$CORTEX_HOME/config/hooks/ |
Per-file CalVer sync at every server start: added when missing, refreshed when the shipped version is newer. A declaration with no version — yours — is never overwritten |
defaults/prompts/ |
$CORTEX_HOME/prompts/ |
Per-file: new files always added, existing preserved unless --force |
defaults/plugins/ |
$CORTEX_HOME/plugins/ |
Per-file: new files always added, existing preserved unless --force |
defaults/rules/ |
$CORTEX_HOME/rules/ |
Per-file: new files always added, existing preserved unless --force |
defaults/hooks/ |
$CORTEX_HOME/hooks/ |
Per-file at init: never overwrite unless --force. At every server start, a shipped script is re-copied unless the deployed file already carries an equal or newer @cortex-hook-version |
defaults/data/schedules.json |
$CORTEX_HOME/data/schedules.json |
Never (unless --force) |
defaults/context/ |
$CORTEX_HOME/context/ |
Scaffold files: never overwrite |
This design means npm package upgrades automatically deliver new prompts,
plugins, rules, hooks, and thread-template entities without overwriting user
customizations. Whole-file config such as budget.json still requires
--force to replace; config/thread-templates/ is merged per file instead,
so newly shipped entities reach existing installs without touching your edits.
Hot-reload behavior¶
Watcher-backed files under config/ automatically switch to filesystem
snapshot polling every 5 seconds if the watcher cannot start or reports a
runtime error. Each loader keeps the same validation and failure behavior in
polling mode as it uses with filesystem events.
config/settings.json— watched via file watcher, debounced 300 ms. New values apply at the next point of use without a restart; a broken file keeps the previous settings.waitingSweepMsset to0at runtime is the one exception that needs a restart to recover. See config/settings.json.schedules.json— watched via file watcher. Changes are picked up within seconds without restart. See scheduling.md for the full scheduling system.profiles.json— cached and watched for parseable JSON changes. An update refreshes the cache before the next agent spawn; malformed JSON keeps the previous profiles.machines.json— watched and reloaded after valid changes. Invalid entries keep the previous machine registry.thread-templates/— each entity subdirectory (agents/,templates/,shells/) is watched. Changes are debounced (300ms) and the whole config is reloaded without a restart; a legacy singlethread-templates.jsonis watched the same way until it is migrated. See threads.md..env— requires a daemon restart to pick up changes (loaded once at startup via dotenv). Behavior settings that used to live here have moved toconfig/settings.json, which does not.- Hook declarations (
config/hooks/*.json) — the registry is re-read on every agent spawn, so a newagent:*/cc:*/pi:*entry applies to the next agent that starts.cortex:*entries are snapshotted at server start and need a restart. See hooks.md. - Hook scripts (
hooks/*.mjs) — read fresh on every hook invocation. - Plugins, prompts, rules — read fresh on each agent session spawn.
Where each file lives¶
| File | Purpose | Path |
|---|---|---|
.env |
Environment variables | $CORTEX_HOME/config/.env |
settings.json |
Runtime behavior settings (hot-reloaded) | $CORTEX_HOME/config/settings.json |
profiles.json |
Agent profiles | $CORTEX_HOME/config/profiles.json |
thread-templates/ |
Thread definitions — one JSON per agent/template/shell. Used when present; otherwise the loader falls back to the legacy single file thread-templates.json, which is split into this directory by a one-time startup migration |
$CORTEX_HOME/config/thread-templates/ |
machines.json |
Machine registry | $CORTEX_HOME/config/machines.json |
budget.json |
Budget limits | $CORTEX_HOME/config/budget.json |
mcp-config.json |
MCP server config | $CORTEX_HOME/config/mcp-config.json |
.claude/settings.json |
Claude Code hooks/permissions (not the Cortex settings file) | $CORTEX_HOME/.claude/settings.json |
mode.json |
Runtime mode | $CORTEX_HOME/data/mode.json |
schedules.json |
Scheduled tasks | $CORTEX_HOME/data/schedules.json |
hooks/*.json |
Hook declarations | $CORTEX_HOME/config/hooks/ |