config.json reference
Each ContextRelay project keeps its settings in a single file:
.contextrelay/config.json
This file is created by ctxrelay init
and lives at the root of the repository you are working in. It holds everything
the daemon needs to run that project: the network ports, the
coordinator and git-write policy,
the permission model, and the
default-off autonomy and act:write gates.
Almost every field has a dedicated CLI command that edits it safely
(ctxrelay coordinator, ctxrelay permissions, ctxrelay autonomy,
ctxrelay act, ctxrelay idle-scanner, ctxrelay standalone, and
more). Prefer those - they validate values and keep the managed instruction
blocks in sync. This page documents the underlying shape for when you want to
read it, review a diff, or audit what a project allows. Unknown or invalid
values are ignored with a warning and fall back to the safe default, so a
typo never silently changes behaviour.
The full shape
Here is a complete config.json with every field at its shipped default. The
defaults are deliberately safe: autonomy off, act:write off, all permission
capabilities present but gated, and auto-connect on.
{
"version": "1.0",
"instanceId": "ctx_a1b2c3d4e5f6",
"stateDir": ".contextrelay/state",
"controlPort": 4502,
"codex": {
"appPort": 4500,
"proxyPort": 4501
},
"autonomy": {
"enabled": false,
"autoFinalize": false,
"backupMode": "read-only",
"backupTimeoutMs": 240000,
"idleScanner": {
"mode": "off",
"debounceMs": 8000,
"dryScanBudget": 3,
"advisoryTrigger": "owner_waiting",
"disabledKinds": [],
"quiescentClaudeStates": ["idle", "offline"],
"quiescentCodexStates": ["idle", "offline"]
},
"idleActionBudgets": {
"tokenBudget": 120000,
"costBudgetUsd": 1,
"warningPct": 80,
"comparisonExtraBudgetUsd": 0
},
"writableAction": {
"enabled": false,
"budgetUsd": 0
}
},
"turnCoordination": {
"attentionWindowSeconds": 15,
"bufferStatusDuringAttention": false,
"turnDigest": true,
"digestUntagged": true,
"respectTarget": false,
"quietTurnPings": true,
"hookCompaction": {
"mode": "count"
}
},
"usageControl": {
"mode": "strict"
},
"rateLimitResume": {
"mode": "off",
"maxAttempts": 3,
"maxScheduleAheadMinutes": 720,
"fallbackBackoffMinutes": 30
},
"collaboration": {
"coordinator": "claude",
"gitWrites": "coordinator"
},
"permissions": {
"readonly": false,
"allowed": [
"read",
"write",
"shell",
"network",
"git",
"secrets",
"browser",
"external_api"
],
"agentOverrides": {}
},
"idleShutdownSeconds": 30,
"activation": {
"autoConnect": true
},
"headless": {
"enabled": true,
"maxConcurrency": 4,
"maxQueue": 64,
"budgetUsd": 0,
"timeoutMs": 180000
}
}
You only need to include the fields you want to override. ContextRelay loads
your file, fills in any missing keys from the defaults above, and ignores
fields it does not recognise. An empty {} resolves to the full default set.
Top-level fields
| Field | Type | Default | Notes |
|---|---|---|---|
version | string | "1.0" | Config schema version. Not the package version. |
instanceId | string | generated | Per-project identifier of the form ctx_<hex> (ctx_ plus the first 12 hex chars of a SHA-256 of the resolved project path). Written at init; you should not edit it. Used to scope the daemon, ledger, and ports to this project. |
appliedVersion | string | (absent) | The package version last reconciled by ctxrelay upgrade. Optional; written by upgrade, read by doctor/status to detect drift. |
stateDir | string | ".contextrelay/state" | Where the ledger and runtime state live, relative to the project root. |
controlPort | number | 4502 | Loopback port for the daemon control channel. Part of the project's port group. |
codex.appPort | number | 4500 | Port for the Codex app-server. |
codex.proxyPort | number | 4501 | Port for the ContextRelay-managed Codex proxy. |
usageControl | object | { "mode": "strict" } | Shared-context tool output volume. Prefer ctxrelay usage off|lean|strict over editing JSON. Per-invocation env override: CONTEXTRELAY_USAGE_MODE. |
rateLimitResume | object | { "mode": "off", ... } | Auto-resume after a Codex usage/rate-limit rejection. mode: off | notify (announce when the limit window resets) | resume (capture the rejected message and re-inject it automatically). maxAttempts (1-10), maxScheduleAheadMinutes (5-4320), fallbackBackoffMinutes (1-720) bound the retry behavior. Toggle from the TUI with l. Codex side only — when Claude itself hits its API limit, ContextRelay already queues Codex→Claude messages and flushes them on reconnect, but it cannot start a Claude turn. |
idleShutdownSeconds | number | 30 | Idle window before the daemon shuts itself down when nothing is connected. Overridable per-run with CONTEXTRELAY_IDLE_SHUTDOWN_MS. |
headless | object | see below | On-demand read-only headless reviewer pool (contained_run / ctxrelay headless run). enabled defaults true and is independent of autonomy. Keys: enabled (bool), maxConcurrency (default 4), maxQueue (default 64), budgetUsd (default 0 = uncapped), timeoutMs (default 180000). Overridable per-run with CONTEXTRELAY_HEADLESS_*. |
controlPort, codex.appPort, and codex.proxyPort form one project port
group. If you override ports through the environment you must set all three
(CONTEXTRELAY_CONTROL_PORT, CODEX_WS_PORT, CODEX_PROXY_PORT) or none -
partial overrides are rejected. See the
environment variables reference.
collaboration - coordinator and git policy
Owns the single most important policy decision: who is allowed to write to git. See Coordinator and git-write policy for the why.
| Field | Type | Default | Allowed values |
|---|---|---|---|
collaboration.coordinator | string | "claude" | claude | codex | human |
collaboration.gitWrites | string | "coordinator" | coordinator (fixed) |
The coordinator is the one agent that may run branch / commit / merge / push / PR operations. Edit it with the dedicated command, which also rewrites the managed instruction blocks so both agents see the same role:
ctxrelay coordinator status
ctxrelay coordinator codex
permissions - the mediated capability model
ContextRelay mediates eight capabilities. By default all eight are present in
allowed (so they are available to be gated), and readonly is false.
Putting the project into read-only mode, or removing a capability, makes the
daemon block that class of operation and tell the agent why. See
Read-only by default.
| Field | Type | Default | Notes |
|---|---|---|---|
permissions.readonly | boolean | false | When true, write/shell/network/git/secrets/browser/external_api are blocked. |
permissions.allowed | string[] | all eight | Subset of the capabilities below. |
permissions.agentOverrides | object | {} | Per-agent overrides keyed by agent id, each with optional readonly and allowed. |
The eight capabilities are:
read write shell network
git secrets browser external_api
Manage them with ctxrelay permissions rather than editing JSON:
ctxrelay permissions status
ctxrelay permissions readonly on
ctxrelay permissions deny network
ctxrelay permissions allow git --agent codex
activation - auto-connect vs dormant
| Field | Type | Default | Notes |
|---|---|---|---|
activation.autoConnect | boolean | true | When true, the SessionStart / per-prompt hooks surface ContextRelay automatically. |
ctxrelay standalone on sets this to false to make ContextRelay
dormant-by-default for the project (and slims the managed instruction
blocks); ctxrelay standalone off sets it back to true.
The activation gate reads the explicit activation.autoConnect field
straight from the JSON - not the normalised config value. A project file that
omits the field is treated as "unset" so a global or per-session setting can
take effect; a file that explicitly sets true/false short-circuits the
global tier. The resolution order is: env CONTEXTRELAY_AUTO_CONNECT →
per-session attach marker → this project field → global
~/.contextrelay/activation.json → shipped default (on). Full details in
Activation: auto-connect vs dormant-by-default.
turnCoordination - message flow between the agents
Controls how Codex's transcript chatter reaches Claude's live context. The defaults collapse routine narration into one digest per turn so Claude is not flooded.
| Field | Type | Default | Notes |
|---|---|---|---|
turnCoordination.attentionWindowSeconds | number | 15 | Window during which fresh attention is expected after a turn. |
turnCoordination.bufferStatusDuringAttention | boolean | false | Buffer status messages during the attention window. |
turnCoordination.turnDigest | boolean | true | Buffer Codex transcript/status chatter and emit one summary per turn. |
turnCoordination.digestUntagged | boolean | true | Also buffer untagged narration unless addressed with @claude / Claude: / Claude,. |
turnCoordination.respectTarget | boolean | false | When true, honour explicit non-Claude audience tags (still logged to the ledger). |
turnCoordination.quietTurnPings | boolean | true | Keep routine ⏳/✅ turn-lifecycle pings out of Claude's live context. |
turnCoordination.hookCompaction | object | { "mode": "count" } | Compaction of the UserPromptSubmit hook output (see below). |
[IMPORTANT] messages, handoffs, and MCP relay commands always push live
regardless of these settings.
hookCompaction
| Field | Type | Default | Notes |
|---|---|---|---|
hookCompaction.mode | string | "count" | verbose | compact | count. |
hookCompaction.previewLimit | number | preset | How many message previews to include. |
hookCompaction.previewChars | number | preset | Max characters per preview. |
hookCompaction.dedupeSeconds | number | preset | Suppress duplicate previews within this window. |
Each mode ships a preset (count, the default, is header-only with no
previews; compact previews up to 1 message at 200 chars with 60s dedupe;
verbose shows more). The three numeric fields are optional overrides on top
of the preset. Use the command rather than the JSON:
ctxrelay usage hook status
ctxrelay usage hook count
ctxrelay usage hook set --preview-limit 2 --preview-chars 240 --dedupe-seconds 45
usageControl - shared-context tool output
usageControl.mode controls how much ledger/context data ContextRelay returns
from MCP tools such as read_context and task_state.
| Field | Type | Default | Values |
|---|---|---|---|
usageControl.mode | string | "strict" | off | lean | strict |
Use the preset command for normal operation:
ctxrelay usage status
ctxrelay usage lean
ctxrelay usage strict
ctxrelay usage off
The lean preset sets shared-context output to lean and keeps the Claude hook
compact. The strict preset — the shipped default — sets shared-context output
to strict and changes the hook to count. The mode also sets the cadence of
the bridge contract reminder appended to Claude→Codex messages: off sends the
full reminder every message (legacy), lean/strict send it periodically with
a one-line micro reminder in between.
CI and one-off automation can override the mode per invocation with the
CONTEXTRELAY_USAGE_MODE=off|lean|strict environment variable — highest
precedence, never written back to config. ctxrelay usage status shows
effectiveMode and modeSource when an override is active.
autonomy - backup agents, idle scanner, and act:write
This block is off by default in every dimension. Nothing here lets the
agents act on their own until you explicitly opt in. The most powerful surface
(act:write) is armed entirely from config, but the contained worker always runs
inside Codex's workspace-write OS sandbox, which confines writes to the
worktree + system temp; your project and this config file live outside those, and
act:write refuses if the project is under system temp - so it cannot edit your
primary tree or re-arm itself.
| Field | Type | Default | Notes |
|---|---|---|---|
autonomy.enabled | boolean | false | Master switch for read-only backup agents. When off, ask_codex_backup / ask_claude_backup are refused. |
autonomy.autoFinalize | boolean | false | When off, finality proposals wait for human acceptance. See Finality and sign-off. |
autonomy.backupMode | string | "read-only" | Fixed. Backup agents are always read-only. |
autonomy.backupTimeoutMs | number | 240000 | Timeout for a backup-agent run (4 minutes). |
autonomy.idleScanner | object | (see below) | The idle opportunity scanner. |
autonomy.idleActionBudgets | object | (see below) | Spend ceilings for read-only idle workers. |
autonomy.writableAction | object | (see below) | The default-off act:write surface. |
Manage the top-level switches with:
ctxrelay autonomy status
ctxrelay autonomy on
ctxrelay finalize manual
autonomy.idleScanner
The deterministic scanner that can surface concrete opportunities when both agents are quiescent. See Autonomy, idle scanner, and safe automation.
| Field | Type | Default | Notes |
|---|---|---|---|
mode | string | "off" | off | suggest | ask | act. ask/act require autonomy.enabled. |
debounceMs | number | 8000 | Debounce between scans. |
dryScanBudget | number | 3 | Bounded number of dry scans before re-arming. |
advisoryTrigger | string | "owner_waiting" | owner_waiting | strict. owner_waiting surfaces a lane when the opportunity owner can act and Codex is not busy; strict uses the older dual-idle gate. |
disabledKinds | string[] | [] | Opportunity kinds to suppress (see kinds below). |
quiescentClaudeStates | string[] | ["idle", "offline"] | Claude states treated as quiescent for act dispatch. |
quiescentCodexStates | string[] | ["idle", "offline"] | Codex states treated as quiescent for act dispatch. |
askForWork | object | (see below) | Automatic nudge that asks an idle non-coordinator to request the next task from the active coordinator. Requires autonomy.enabled. |
ctxrelay idle-scanner status
ctxrelay idle-scanner suggest
A bare string is accepted as shorthand for the mode, so "idleScanner": "act"
is equivalent to "idleScanner": { "mode": "act" }.
The three opportunity kinds (valid in disabledKinds) are:
failed_check_no_followup
dirty_tree_finalizable
claimed_complete_unverified
autonomy.idleScanner.askForWork
This is separate from the scanner's mode: "ask" rung. It uses the same agent-state model, but it is triggered by daemon state transitions when the coordinator is active and the other agent is idle.
| Field | Type | Default | Notes |
|---|---|---|---|
enabled | boolean | true | Enables the nudge when autonomy.enabled is also true. |
cooldownMs | number | 300000 | Minimum time between nudges across idle stretches. 300000 = 5 min. |
autonomy.idleActionBudgets
Per-action ceilings for read-only idle workers.
| Field | Type | Default | Notes |
|---|---|---|---|
tokenBudget | number | 120000 | Token budget per read-only idle action. |
costBudgetUsd | number | 1 | Cost budget per read-only idle action, in USD. |
warningPct | number | 80 | Warn when usage reaches this percentage of a budget (0–100). |
comparisonExtraBudgetUsd | number | 0 | Extra budget for single-vs-fleet comparison runs. |
ctxrelay idle-budget status
autonomy.writableAction - the act:write surface
This is the autonomous-edit surface, and it is the most heavily gated thing in ContextRelay. Both fields default to their closed value, so writes stay off until you arm them. See Enabling act:write safely.
| Field | Type | Default | Notes |
|---|---|---|---|
enabled | boolean | false | Master switch for act:write. Armed only together with a positive budgetUsd, and only while the global autonomy.enabled master switch is on. |
budgetUsd | number | 0 | Daily spend cap in USD. Must be > 0 to arm act:write; a missing or invalid value fails closed. |
These are the only two act:write fields, and there are no act:write environment
variables. act:write is armed when enabled === true AND budgetUsd > 0,
and it is additionally gated by the global autonomy.enabled switch — so
ctxrelay autonomy off disables it. Enable autonomy first, then arm act:write:
ctxrelay autonomy on # global master switch (prerequisite)
ctxrelay act status
ctxrelay act on --budget 1.00
ctxrelay act off
Arming act:write with enabled: true and a positive budgetUsd does not let it
run unguarded. It is first gated by the global autonomy.enabled master switch
(ctxrelay autonomy off disables act:write outright). Beyond that, a
non-configurable internal floor still applies: only the
dirty_tree_finalizable opportunity kind may write, only claude/codex
owners, strict dual-idle quiescence, single-flight, and the daily budget check
(spent + per-task estimate <= budgetUsd, estimate derived internally as
min($1, budgetUsd)), plus a refusal to run if the primary repo is itself under
a system-temp directory (os.tmpdir(), $TMPDIR, /tmp, /private/tmp). The
contained worker always runs inside Codex's workspace-write OS sandbox on an
ephemeral git worktree, which confines writes to the worktree and the system temp
area; your primary tree and this config file live outside those, which is what
prevents it from re-arming itself - and it never commits, merges, or pushes.
(Live-verified on macOS; the Codex sandbox is expected to behave the same on
other platforms but is not yet separately verified.) See
Read-only by default. Legacy
mode/authorization/budgets config from older releases is migrated to
{ enabled, budgetUsd } on config load; ctxrelay upgrade preserves your
values but the migration runs on any config load, not only during upgrade.
Keeping config.json current: ctxrelay upgrade
After you update the package
(npm i -g @proofofwork-agency/contextrelay@latest), reconcile this file and the
rest of the Claude/Codex-facing surface with:
ctxrelay upgrade
For config specifically, upgrade performs a migrate-merge: it loads your
existing config.json, deep-merges the current defaults so that new default
keys are added while your existing values are preserved (coordinator,
permissions, activation, act:write, and everything else you set are left
untouched; the legacy act:write mode/budgets → { enabled, budgetUsd }
normalization is applied on any config load, so upgrade just persists it),
records the new package version in appliedVersion, and writes the
file only if something actually changed. It also refreshes the managed
instruction blocks while preserving each file's slim/dormant state, refreshes
the bare /contextrelay command only if it is already present, and re-registers
and reinstalls the Claude plugin. It then prints the from → to version and
reminds you to run /reload-plugins if Claude Code is open.
Useful flags:
ctxrelay upgrade --dry-run # show the full plan, write nothing
ctxrelay upgrade --no-plugin # skip the plugin re-register/reinstall step
ctxrelay upgrade --instructions skip # touch no instruction files
--instructions also accepts refresh (the default - refresh only files that
already carry a managed block) or project / global / both (also install
blocks where missing, like init).
ctxrelay upgrade?You can reach the same end state manually: ctxrelay dev (or re-register the
plugin), ctxrelay instructions install, and ctxrelay doctor to verify.
Next steps
- Environment variables reference - the env-var overrides that layer on top of these fields.
- CLI command reference - every command that reads or writes this file.
- Read-only by default: safety and containment - why the autonomy and permission defaults are set the way they are.
- Activation: auto-connect vs dormant-by-default - how
activation.autoConnectis resolved.