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 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
}
}
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": "off" }Shared-context tool output volume. Prefer ctxrelay usage off|lean|strict over editing JSON.
idleShutdownSecondsnumber30Idle window before the daemon shuts itself down when nothing is connected. Overridable per-run with CONTEXTRELAY_IDLE_SHUTDOWN_MS.
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": "compact" }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"compact"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 (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.

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

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

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. Every field defaults to its closed value, so writes are impossible from config alone. See Enabling act:write safely.

FieldTypeDefaultNotes
modestring"off"off | suggest | ask | act.
authorization.allowedKindsstring[][]Which opportunity kinds a write worker may act on. Empty = nothing authorized.
authorization.allowedOwnersstring[][]claude and/or codex. Empty = nothing authorized.
budgets.tokenBudgetnumber0Per-task token budget. Zero = no budget.
budgets.costBudgetUsdnumber0Per-task cost budget. Zero = no budget.
budgets.dailyCapUsdnumber0Per-day cost cap. Zero = no budget.
branchPrefixstring"contextrelay/write/"Branch prefix for the ephemeral write worktree.
worktreeRootstring(absent)Optional root for write worktrees. Omitted by default.
ctxrelay write-mode status
Config is necessary but not sufficient for act:write

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

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