This is the complete reference for Echo’s configuration. Every key documented
here comes from the plugin’s shipped skills/echo-core/config/defaults.json,
and every leaf key in that file appears somewhere on this page.
defaults.json (in the plugin) holds every key and its baseline value.
The registry entry (~/.config/echo/registry.json, override with
ECHO_REGISTRY) can carry a few globals.
.claude/echo/config.json in each project holds only the overrides —
you never copy the whole defaults file into a project. echo:setup writes
only the keys you changed during the interview; the merge at read time fills
in the rest.
Two merge rules to keep in mind (both enforced by echo-config, the keystone
resolver every script reads through):
Objects deep-merge; arrays replace wholesale. A project that sets
gatedPaths overrides the default list entirely — it does not append. This is
why always-on lists (like the security gate’s built-in token list) live at
their consuming site, not in defaults.json.
An explicit null is treated as “unset” — indistinguishable from an
absent key downstream.
Some values are also environment-overridable at runtime via ECHO_*
variables, which beat both the config and the defaults — see
Environment overrides at the end.
Whether the GitHub Projects v2 board is owned by an org or a user. Affects the OAuth scope preflight checks; user-owned boards need the project scope specifically.
The board’s owner, repo, and projectNumber are written into the project’s
own config by echo:setup (the project number can’t be known until the board is
provisioned) — they are not in defaults.json.
Dispatcher-side merge behavior. auto = gated_merge.sh merges a clean, echo:merge-ready-labeled PR after re-running the deterministic gates; any gate uncertainty holds for a human instead.
Post-merge deploy verification — how Echo confirms a merged PR actually reached
production before flipping an item to Done.
Key
Type
Default
Description
verify.type
string
"none"
none (trust CI — flip straight to Done), command (run a stored read-only health check), or k8s-argocd (follow build → image-tag bump → ArgoCD sync → rollout).
verify.timeout
number
1200
Seconds the deploy-watch waits before giving up and finalizing to Needs Human.
verify.watchModel
string
"sonnet"
Model that drives the detached deploy-watch session.
For command type, a verify.command key holds the read-only health-check
command; for k8s-argocd, the coordinate set (context, namespace,
deployment, argocdApp, imageRepo, configRepo) is added by echo:setup.
These live in the project config, not defaults.json.
The optional echo-vault — Obsidian-native run notes. Entirely
best-effort: an unset/unresolved vault makes all of this a silent no-op.
Key
Type
Default
Description
vault.distill
boolean
true
Whether to run the LLM enrichment pass that distills a worker’s transcript into a Decisions/Patterns/Solutions/Blockers note. false leaves only the deterministic note.
vault.distillMaxBytes
number
40000
Max transcript bytes fed to the distill model.
vault.distillModel
string
"claude-haiku-4-5"
Model used for the distill pass. Env override ECHO_DISTILL_MODEL.
memoryPath (a top-level key, below) points at the vault root.
How many recent channel messages /echo:tick fetches to scan for operator quote-replies.
discord.replyAuthors
string[]
[]
Allowlist of Discord author IDs whose replies may re-queue or approve-merge an item. Fail-closed: empty/unset disables inbound reply routing entirely (no author can act) — set your operator ID(s) to enable it.
discord.approveKeywords
string[]
["approve", "merge"]
Keywords in a reply that count as an approval/merge instruction.
The per-project Discord channel ID lives in the registry entry (set by
echo:setup), not in defaults.json.
The adversarial-review + dispatcher-side merge gate.
Key
Type
Default
Description
review.holdForHuman
boolean
true
Whether a confirmed high-severity review finding holds the merge for a human instead of auto-merging. Its write-time default can be forced via ECHO_SETUP_HOLD_FOR_HUMAN.
review.diffMaxBytes
number
60000
Max PR diff bytes handed to the review model.
review.gateLabel
string
"review-block"
Label that forces the review gate into block mode for an item.
review.strongModel
string
"opus"
The strong model the review/verify agents are pinned to, regardless of the (possibly cheaper) implementation model. Review rigor can never be silently downgraded.
review.securityLens.model
string
""
Model for the dedicated security lens; empty = fall back to the strong model (or Fable when ECHO_FABLE_AVAILABLE=1).
review.securityLens.enabled
boolean
false
Whether the dedicated security-lens review pass runs.
The auth / tenant-isolation human-merge gate — the single source of truth for
“does this changed-file set touch auth/isolation code?”. A path match forces the
worker to hold auto-merge and route to human review. The list deliberately
over-matches (a false hold is cheap; a false auto-merge of an auth change is
exactly what this prevents).
The mechanical PR test-coverage gate: a feature/fix PR adding more than the
threshold of non-docs source lines must touch a test file or carry a
## Test justification section.
Key
Type
Default
Description
testCoverage.threshold
number
150
Added non-docs source-line ceiling before the gate trips. Env override ECHO_TEST_COVERAGE_THRESHOLD.
testCoverage.label
string
"needs-tests"
Label applied when the gate trips (non-blocking by default).
testCoverage.excludeRe
string
""
Regex of paths excluded from the added-lines count (empty = built-in excludes: *.md, docs/**, lockfiles, generated/vendored). Env ECHO_TEST_COVERAGE_EXCLUDE_RE.
testCoverage.testFileRe
string
""
Regex identifying test files (empty = built-in patterns like *_test.go, *.spec.ts). Env ECHO_TEST_COVERAGE_TEST_FILE_RE.
testCoverage.blockPathRe
string
""
Extra paths that escalate a tripped gate from non-blocking to a hard hold. Env ECHO_TEST_COVERAGE_BLOCK_PATH_RE.
Declared configuration/infra repos (ArgoCD/Helm, Terraform, etc.) that need cross-repo edits during a deploy. Each entry carries name, repo (owner/slug), type, optional path, and purpose. checkout_supporting_repo.sh / resolve_supporting_repo.sh look these up by name; a k8s-argocdverify.configRepo must reference one of them.
Glob(s) a worker may never edit unaided — Echo’s own config is off-limits by default. Add an auth/billing-sensitive area here to gate it. (Array replaces on override — list every glob you want.)
Extra project-specific phrases that make pre-validate bail an item (on top of the built-in firmware/HIL/destructive/PII/credentials patterns). Empty = built-ins only.
The branch workers cut worktrees from and target PRs against.
branchPrefix
string
"agent/issue-"
Prefix for a worker’s branch — Echo’s deterministic <branchPrefix><issue#> convention that reconcile.sh matches merged PRs by.
deferMarker
string
"<!-- echo-defer -->"
HTML-comment sentinel written into a deferred item so pre-validate can recognize (and count toward workers.deferCap) its own prior defers.
memoryPath
string
""
Path to the echo-vault root; empty = no vault, all vault writes no-op.
lessons
string
"LESSONS.md"
Path to the project’s LESSONS file — house-style learnings a worker reads at spawn and appends to. Auto-seeded from the plugin template on a project’s first spawn if absent.
These ECHO_* variables override config (and defaults) at runtime. They exist
for kill-switches, CI, and quick experiments — the durable place for a value is
still .claude/echo/config.json.
Env var
Overrides / effect
Fallback
ECHO_PROJECT_ROOT
The registered repo root the invoked script operates on. Required (exported by the caller) for most worker/backlog scripts.
—
ECHO_REGISTRY
Path to the registry file.
~/.config/echo/registry.json
ECHO_WORKER_SLOTS
workers.slots. 0 is a soft kill-switch (blocks new spawns without the hard switch).
config workers.slots
ECHO_DISPATCHER_DISABLED
Hard kill-switch: =1 makes lock.sh exit immediately and chain_next.sh skip.
unset (enabled)
ECHO_DISPATCHER_USE_BG
=1 spawns the worker via a background mechanism instead of the default detach.
0
ECHO_WORKER_TOKEN_BUDGET
workers.tokenBudget.
config value
ECHO_PREVALIDATE_MODEL
workers.prevalidateModel.
config value
ECHO_PREVALIDATE_DISABLED
=1 skips the pre-validate Claude call entirely (worker’s own bail gate is the net).
unset (enabled)
ECHO_WORKER_STARTED_COMMENT
=0 disables the “worker started” GitHub issue comment.
1
ECHO_DISTILL_MODEL
vault.distillModel.
config value → claude-haiku-4-5
ECHO_DISTILL_TIMEOUT
Seconds the vault distill Claude call may run.
120
ECHO_REVIEW_GATE_MODE
Force the review gate mode (e.g. block) regardless of the review-block label.
unset
ECHO_STALE_REVIEW_GRACE
Seconds gated_merge.sh waits before treating a review as stale.
1800
ECHO_PR_CI_TIMEOUT_SECONDS
Seconds a worker waits for PR CI to finish.
900
ECHO_SETUP_HOLD_FOR_HUMAN
Write-time default for review.holdForHuman during echo:setup.
true
ECHO_FABLE_AVAILABLE
=1 lets the security lens resolve to Fable (only after a live probe confirms Fable dispatch works); =0 keeps it on Opus.
0
ECHO_FABLE_MODEL
The model name Fable resolves to when available.
fable
ECHO_RISK_CATEGORY_RE
risk.categoryRegex.
config value
ECHO_RISK_LABEL_RE
risk.labelRegex.
config value
ECHO_AUTH_ISOLATION_PATH_RE
Full override of authIsolation.pathRe.
config value
ECHO_AUTH_ISOLATION_SSO_RE
Full override of authIsolation.ssoRe.
config value
ECHO_AUTH_ISOLATION_EXCLUDE_RE
Full override of authIsolation.excludeRe.
config value
ECHO_SECURITY_PATH_RE
Override of the security-path regex the worker gate reads.
config-derived
ECHO_TEST_COVERAGE_THRESHOLD
testCoverage.threshold.
config value → 150
ECHO_TEST_COVERAGE_EXCLUDE_RE
testCoverage.excludeRe.
config value
ECHO_TEST_COVERAGE_TEST_FILE_RE
testCoverage.testFileRe.
config value
ECHO_TEST_COVERAGE_BLOCK_PATH_RE
testCoverage.blockPathRe.
config value
Echo also reads a set of ECHO_*_BIN path variables (ECHO_CONFIG_BIN,
ECHO_PATHS_BIN, ECHO_SPAWN_BIN, and similar) to locate its own helper
scripts; those are internal wiring, not configuration you set.