Skip to main content

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.

You rarely edit this file by hand

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
}
}
Partial config is fine

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

FieldTypeDefaultNotes
versionstring"1.0"Config schema version. Not the package version.
instanceIdstringgeneratedPer-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.
appliedVersionstring(absent)The package version last reconciled by ctxrelay upgrade. Optional; written by upgrade, read by doctor/status to detect drift.
stateDirstring".contextrelay/state"Where the ledger and runtime state live, relative to the project root.
controlPortnumber4502Loopback port for the daemon control channel. Part of the project's port group.
codex.appPortnumber4500Port for the Codex app-server.
codex.proxyPortnumber4501Port for the ContextRelay-managed Codex proxy.
usageControlobject{ "mode": "strict" }Shared-context tool output volume. Prefer ctxrelay usage off|lean|strict over editing JSON. Per-invocation env override: CONTEXTRELAY_USAGE_MODE.
rateLimitResumeobject{ "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.
idleShutdownSecondsnumber30Idle window before the daemon shuts itself down when nothing is connected. Overridable per-run with CONTEXTRELAY_IDLE_SHUTDOWN_MS.
headlessobjectsee belowOn-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_*.
Ports are a group - change all three or none

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.

FieldTypeDefaultAllowed values
collaboration.coordinatorstring"claude"claude | codex | human
collaboration.gitWritesstring"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.

FieldTypeDefaultNotes
permissions.readonlybooleanfalseWhen true, write/shell/network/git/secrets/browser/external_api are blocked.
permissions.allowedstring[]all eightSubset of the capabilities below.
permissions.agentOverridesobject{}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

FieldTypeDefaultNotes
activation.autoConnectbooleantrueWhen 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 raw value matters for precedence

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.

FieldTypeDefaultNotes
turnCoordination.attentionWindowSecondsnumber15Window during which fresh attention is expected after a turn.
turnCoordination.bufferStatusDuringAttentionbooleanfalseBuffer status messages during the attention window.
turnCoordination.turnDigestbooleantrueBuffer Codex transcript/status chatter and emit one summary per turn.
turnCoordination.digestUntaggedbooleantrueAlso buffer untagged narration unless addressed with @claude / Claude: / Claude,.
turnCoordination.respectTargetbooleanfalseWhen true, honour explicit non-Claude audience tags (still logged to the ledger).
turnCoordination.quietTurnPingsbooleantrueKeep routine ⏳/✅ turn-lifecycle pings out of Claude's live context.
turnCoordination.hookCompactionobject{ "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

FieldTypeDefaultNotes
hookCompaction.modestring"count"verbose | compact | count.
hookCompaction.previewLimitnumberpresetHow many message previews to include.
hookCompaction.previewCharsnumberpresetMax characters per preview.
hookCompaction.dedupeSecondsnumberpresetSuppress 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.

FieldTypeDefaultValues
usageControl.modestring"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.

FieldTypeDefaultNotes
autonomy.enabledbooleanfalseMaster switch for read-only backup agents. When off, ask_codex_backup / ask_claude_backup are refused.
autonomy.autoFinalizebooleanfalseWhen off, finality proposals wait for human acceptance. See Finality and sign-off.
autonomy.backupModestring"read-only"Fixed. Backup agents are always read-only.
autonomy.backupTimeoutMsnumber240000Timeout for a backup-agent run (4 minutes).
autonomy.idleScannerobject(see below)The idle opportunity scanner.
autonomy.idleActionBudgetsobject(see below)Spend ceilings for read-only idle workers.
autonomy.writableActionobject(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.

FieldTypeDefaultNotes
modestring"off"off | suggest | ask | act. ask/act require autonomy.enabled.
debounceMsnumber8000Debounce between scans.
dryScanBudgetnumber3Bounded number of dry scans before re-arming.
advisoryTriggerstring"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.
disabledKindsstring[][]Opportunity kinds to suppress (see kinds below).
quiescentClaudeStatesstring[]["idle", "offline"]Claude states treated as quiescent for act dispatch.
quiescentCodexStatesstring[]["idle", "offline"]Codex states treated as quiescent for act dispatch.
askForWorkobject(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.

FieldTypeDefaultNotes
enabledbooleantrueEnables the nudge when autonomy.enabled is also true.
cooldownMsnumber300000Minimum time between nudges across idle stretches. 300000 = 5 min.

autonomy.idleActionBudgets

Per-action ceilings for read-only idle workers.

FieldTypeDefaultNotes
tokenBudgetnumber120000Token budget per read-only idle action.
costBudgetUsdnumber1Cost budget per read-only idle action, in USD.
warningPctnumber80Warn when usage reaches this percentage of a budget (0–100).
comparisonExtraBudgetUsdnumber0Extra 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.

FieldTypeDefaultNotes
enabledbooleanfalseMaster switch for act:write. Armed only together with a positive budgetUsd, and only while the global autonomy.enabled master switch is on.
budgetUsdnumber0Daily 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
Config arms it; a hard internal floor and the OS sandbox keep it contained

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).

On an older release without 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