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 write-mode, 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": {
"mode": "off",
"authorization": {
"allowedKinds": [],
"allowedOwners": []
},
"budgets": {
"tokenBudget": 0,
"costBudgetUsd": 0,
"dailyCapUsd": 0
},
"branchPrefix": "contextrelay/write/"
}
},
"turnCoordination": {
"attentionWindowSeconds": 15,
"bufferStatusDuringAttention": false,
"turnDigest": true,
"digestUntagged": true,
"respectTarget": false,
"quietTurnPings": true,
"hookCompaction": {
"mode": "compact"
}
},
"usageControl": {
"mode": "off"
},
"collaboration": {
"coordinator": "claude",
"gitWrites": "coordinator"
},
"permissions": {
"readonly": false,
"allowed": [
"read",
"write",
"shell",
"network",
"git",
"secrets",
"browser",
"external_api"
],
"agentOverrides": {}
},
"idleShutdownSeconds": 30,
"activation": {
"autoConnect": true
}
}
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": "off" } | Shared-context tool output volume. Prefer ctxrelay usage off|lean|strict over editing JSON. |
idleShutdownSeconds | number | 30 | Idle window before the daemon shuts itself down when nothing is connected. Overridable per-run with CONTEXTRELAY_IDLE_SHUTDOWN_MS. |
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": "compact" } | 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 | "compact" | 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 (compact previews up to 1 message at 200 chars with
60s dedupe; verbose shows more; count is header-only with no previews). 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 | "off" | 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 sets shared-context output to strict and changes
the hook to count.
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, and the most powerful surface (act:write) additionally requires hard environment flags that config alone cannot satisfy.
| 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. |
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, and also in
writableAction.authorization.allowedKinds) are:
failed_check_no_followup
dirty_tree_finalizable
claimed_complete_unverified
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. Every field defaults to its closed value, so writes are impossible from config alone. See Enabling act:write safely.
| Field | Type | Default | Notes |
|---|---|---|---|
mode | string | "off" | off | suggest | ask | act. |
authorization.allowedKinds | string[] | [] | Which opportunity kinds a write worker may act on. Empty = nothing authorized. |
authorization.allowedOwners | string[] | [] | claude and/or codex. Empty = nothing authorized. |
budgets.tokenBudget | number | 0 | Per-task token budget. Zero = no budget. |
budgets.costBudgetUsd | number | 0 | Per-task cost budget. Zero = no budget. |
budgets.dailyCapUsd | number | 0 | Per-day cost cap. Zero = no budget. |
branchPrefix | string | "contextrelay/write/" | Branch prefix for the ephemeral write worktree. |
worktreeRoot | string | (absent) | Optional root for write worktrees. Omitted by default. |
ctxrelay write-mode status
Even with mode: "act", positive budgets, and matching allowlists, a contained
write worker will not run unless you also set the hard environment opt-ins
CONTEXTRELAY_WRITE_MODE_ENABLED and CONTEXTRELAY_WRITE_DAILY_CAP_USD, and
the strict dual-idle and containment checks pass. The worker only ever edits
inside an ephemeral git worktree and never commits, merges, or pushes. See the
environment variables reference and
Read-only by default.
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, write-mode, and everything else you set are left
untouched), 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.