Skip to main content

Read-only by default: safety and containment

ContextRelay runs two capable coding agents in one session. That power only stays safe if the defaults are conservative and every step toward more autonomy is explicit and reversible. ContextRelay is therefore read-only by default and fails closed: any missing precondition denies the action rather than allowing it.

This page explains the why (the safety philosophy) and the how (the exact commands, flags, config keys, and environment variables that control it). These are security claims, so everything here is grounded in the implementation - see Trust boundaries and threat model for the full boundary analysis.

What "read-only by default" means

Out of the box, ContextRelay coordinates and records. It does not let either agent make autonomous edits, and it does not spawn extra worker agents on its own. The two paths that could relax this - backup-agent autonomy and autonomous edits (act:write) - are both off by default and independent of each other. Turning one on never turns the other on.

The layered, fail-closed model

There are three distinct safety layers. Each is opt-in, and each fails closed on its own:

  1. Permissions - a capability model that mediates what an agent may do at all (read, write, shell, git, and more). Fully permissive by default for a single trusted operator, but tightenable to read-only.
  2. Backup-agent autonomy - whether agents may explicitly request read-only helper agents. Off by default.
  3. act:write (autonomous edits) - whether an idle, contained worker may edit files. Off by default behind a chain of independent gates, several of which a config file can never satisfy.

The ordering matters: even with autonomy on, act:write stays off until its own gate chain passes. Even with act:write configured to act, a hard environment flag that no config can supply is still required. There is no single switch that unlocks autonomous writes.

Layer 1 - the permission capability model

ContextRelay mediates agent actions through eight capabilities plus a readonly flag. The policy lives in .contextrelay/config.json under permissions:

{
"permissions": {
"readonly": false,
"allowed": [
"read",
"write",
"shell",
"network",
"git",
"secrets",
"browser",
"external_api"
],
"agentOverrides": {}
}
}

The eight capabilities are exactly: read, write, shell, network, git, secrets, browser, external_api.

When readonly is true, every capability except read is denied. When a capability is not in allowed, that operation is blocked and the daemon returns a reason to the agent (for example, git is blocked by ContextRelay readonly mode). You can also scope policy per agent via agentOverrides, so one agent can be more restricted than the other.

Inspect and change the policy with the permissions command:

# Show the current mediated permission policy
contextrelay permissions status

# Make the whole project read-only (denies write/shell/network/git/secrets/browser/external_api)
contextrelay permissions readonly on

# Deny or allow a single capability
contextrelay permissions deny git
contextrelay permissions allow git

# Restrict just one agent (per-agent override)
contextrelay permissions readonly on --agent codex

# Reset back to defaults (clears overrides, re-allows all capabilities)
contextrelay permissions reset
The default policy is permissive on purpose

The threat model assumes a single trusted operator on one workstation - not mutually untrusted tools. So the default permissions.allowed list grants every capability. "Read-only by default" refers to the two autonomy layers below, which are off regardless of how permissive your permission policy is. Use permissions readonly on when you want to additionally constrain what the agents may do.

Layer 2 - backup-agent autonomy (read-only helpers)

Autonomy controls whether agents may explicitly request read-only backup agents - headless helper workers for a second analysis pass. It is off by default (contextrelay autonomy on enables it), and nothing dispatches a backup agent on its own: an agent has to ask, using the ask_codex_backup / ask_claude_backup MCP tools.

The security substance is in how the workers are spawned: a Claude backup runs with --allowedTools Read,Grep,Glob,LS (read and search only, no edit or shell), a Codex backup runs with --sandbox read-only, and both inherit CONTEXTRELAY_WORKER=1 so a worker can never recursively start another ContextRelay session. Results are recorded as idle_action_result artifacts in the ledger.

When to turn this on, the trigger phrases, throttling, and the budgets that bound each run are covered in Autonomy, idle scanner, and safe automation.

Backup agents never write

Read-only backup agents exist to analyze, not to act. Their tool sets contain no edit, write, shell, or git capability, so they cannot modify your tree even if asked. Treat their output as evidence for you or the coordinating agent to act on - not as completed work.

Layer 3 - act:write (autonomous edits), and why it fails closed

act:write is the strongest capability ContextRelay offers and the one held to the strictest standard. It lets an idle worker actually edit files to address a detected opportunity (for example, a failed check with no follow-up). It is off by default and fully fail-closed.

The mode is configured under autonomy.writableAction.mode and controlled with the write-mode command:

# Show the act:write config surface (default: off)
contextrelay write-mode status

# Set the mode (off | suggest | ask | act). Even "act" only records config -
# it does NOT by itself enable writes.
contextrelay write-mode act

The default config is the closed/safe value at every field:

{
"autonomy": {
"writableAction": {
"mode": "off",
"authorization": { "allowedKinds": [], "allowedOwners": [] },
"budgets": { "tokenBudget": 0, "costBudgetUsd": 0, "dailyCapUsd": 0 },
"branchPrefix": "contextrelay/write/"
}
}
}

The gate chain - ALL of these must pass

Before a single contained writable worker may run, every gate in a fail-closed chain must pass, evaluated in order: write mode act, autonomy on, the hard environment flag CONTEXTRELAY_WRITE_MODE_ENABLED, a valid daily spend cap in CONTEXTRELAY_WRITE_DAILY_CAP_USD, a positive per-task budget, matching kind/owner allowlists (empty allowlists authorize nothing), strict dual-idle quiescence, single-flight, and worktree containment. The first failing gate denies the action with a specific reason.

The gate-by-gate table with defaults, and the recommended enablement order, live in Enabling act:write safely.

A config file alone can never enable writes

The hard environment gate is the keystone: CONTEXTRELAY_WRITE_MODE_ENABLED is read from the environment, never from any config file. So even a fully act-configured .contextrelay/config.json with budgets and allowlists set cannot produce a write on its own - the operator must also export the env flag in the shell that launched ContextRelay. In stock config with no environment overrides, nothing passes. That is the headline default-off guarantee.

Containment - where the writes actually go

When (and only when) every gate passes, the contained worker does not touch your working tree: a single bounded worker runs inside an ephemeral git worktree on a throwaway contextrelay/write/ branch, leaves its edits uncommitted, and the daemon captures the diff before teardown and records it as an idle_write_result artifact. The worktree and its branch are then removed unconditionally, even on failure.

So the worker never writes to your primary tree, never commits, never merges, and never pushes. What you get is a captured diff to review - evidence, not an applied change. The full containment guarantees, current capture limitations, and the opt-in procedure are in Enabling act:write safely.

act:write writers also carry a marker

A contained write worker advertises CONTEXTRELAY_WRITE_WORKER=1 (in addition to CONTEXTRELAY_WORKER=1), so a worker - or a skill it loads - can tell the contained-write mode apart from a read-only run.

The human-authority boundary

The read-only-by-default stance has a matching rule for the agents themselves. Outside an explicitly dispatched contained act:write worker, agents must not:

  • edit files, or run git writes outside the configured coordinator/git policy;
  • spend beyond the configured opt-in budgets and the daily cap;
  • publish, release, or take outward/destructive actions;
  • kill or restart daemons.

These require human authority. Git writes in particular are owned by the configured coordinator - see Coordinator and git-write policy. Final sign-off on completed work is also human-gated by default - see Finality and human sign-off.

What ContextRelay does not claim

Per the threat model, ContextRelay does not sandbox Claude, Codex, backup agents, shell commands, or code under test, and it does not protect against another process running as the same OS user. Its containment guarantees are about ContextRelay's own autonomous paths (backup workers and act:write), not about isolating the agents from the workstation. The ledger is local operational state, not a tamper-proof audit trail.

Quick reference

LayerDefaultTurn on withHard requirement a config can't satisfy
Permissions (permissions.readonly)permissive (all 8 capabilities)contextrelay permissions readonly on to tighten-
Backup autonomy (autonomy.enabled)offcontextrelay autonomy on-
act:write (autonomy.writableAction.mode)offcontextrelay write-mode act plus the full gate chainCONTEXTRELAY_WRITE_MODE_ENABLED + CONTEXTRELAY_WRITE_DAILY_CAP_USD (environment only)

Next steps