Skip to content

Configuration

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.

A project’s effective config is a three-way deep merge, rightmost wins:

plugin defaults.json ← registry entry ← per-project .claude/echo/config.json
  • 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.


The board’s single-select Status column names. Every script matches against these names (never hardcoded strings), so a board can rename any column.

KeyTypeDefaultDescription
statuses.backlogstring"Backlog"Un-triaged items; not yet dispatchable.
statuses.readystring"Ready"The dispatchable column — select_next_item.sh only picks from here.
statuses.planningstring"Planning"Set by the dispatcher the instant a worker is spawned, before the worker writes its plan.
statuses.inProgressstring"In progress"The worker flips here once it has a plan (Step 4a).
statuses.aiReviewingstring"AI reviewing"PR open, adversarial review done, awaiting the dispatcher-side gated merge.
statuses.needsHumanstring"Needs Human"Escalated — a human decision is required; drives the Discord outbox.
statuses.buildingDeployingstring"Building & Deploying"Merged; a deploy-watch is following it to production.
statuses.donestring"Done"Terminal; reached after deploy verification (or straight from merge when verify.type is none).
KeyTypeDefaultDescription
board.ownerTypestring"org"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.

Worker-pool sizing, the pre-validate model, per-item token budget, and the model-by-size routing table.

KeyTypeDefaultDescription
workers.slotsnumber2Max concurrent detached workers per project. Bounded-N concurrency; env override ECHO_WORKER_SLOTS (0 = soft kill-switch).
workers.prevalidateModelstring"haiku"Small model for the pre-validate classify call (proceed / needs_human / defer). Env override ECHO_PREVALIDATE_MODEL.
workers.tokenBudgetnumber200000Per-worker token budget passed into the spawned session. Env override ECHO_WORKER_TOKEN_BUDGET.
workers.deferCapnumber3Max times an item may be deferred by pre-validate before it stops being auto-retried.
workers.modelBySize.xsstring"sonnet"Implementation model for an XS-sized item.
workers.modelBySize.sstring"sonnet"Implementation model for an S-sized item.
workers.modelBySize.mstring"opus"Implementation model for an M-sized item.
workers.modelBySize.lstring"opus"Implementation model for an L-sized item.
workers.modelBySize.xlstring"opus"Implementation model for an XL-sized item.
workers.modelBySize.defaultstring"opus"Fallback implementation model when an item has no recognized Size.

Patterns that mark an item as high-risk (surfaced to the worker so it treats auth/billing/money-path work with extra care).

KeyTypeDefaultDescription
risk.categoryRegexstring"auth|billing|security|money|payment|contract"Case-insensitive regex over the item’s Category that flags it as risky. Env override ECHO_RISK_CATEGORY_RE.
risk.labelRegexstring"^(security|auth|billing|money-path)$"Case-insensitive regex over the item’s labels that flags it as risky. Env override ECHO_RISK_LABEL_RE.
KeyTypeDefaultDescription
prioritiesstring[]["P0", "P1", "P2"]The Priority values, in rank order — index 0 is highest. select_next_item.sh sorts Ready items by this rank; anything not listed ranks last.
KeyTypeDefaultDescription
merge.modestring"auto"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.

KeyTypeDefaultDescription
verify.typestring"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.timeoutnumber1200Seconds the deploy-watch waits before giving up and finalizing to Needs Human.
verify.watchModelstring"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.

KeyTypeDefaultDescription
vault.distillbooleantrueWhether 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.distillMaxBytesnumber40000Max transcript bytes fed to the distill model.
vault.distillModelstring"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.

Escalation delivery and inbound reply routing.

KeyTypeDefaultDescription
discord.replyLookbacknumber50How many recent channel messages /echo:tick fetches to scan for operator quote-replies.
discord.replyAuthorsstring[][]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.approveKeywordsstring[]["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.

KeyTypeDefaultDescription
review.holdForHumanbooleantrueWhether 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.diffMaxBytesnumber60000Max PR diff bytes handed to the review model.
review.gateLabelstring"review-block"Label that forces the review gate into block mode for an item.
review.strongModelstring"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.modelstring""Model for the dedicated security lens; empty = fall back to the strong model (or Fable when ECHO_FABLE_AVAILABLE=1).
review.securityLens.enabledbooleanfalseWhether the dedicated security-lens review pass runs.
review.caps.maxReviewersnumber4Max parallel reviewer agents.
review.caps.maxVerifiersnumber3Max parallel verifier agents.
review.caps.maxFindingsnumber12Max findings carried through a review round.

Maps a review finding’s severity to the board Priority of the follow-up item file_review_findings.sh files for confirmed-and-unfixed findings.

KeyTypeDefaultDescription
severityToPriority.highstring"P1"Priority for a high-severity finding.
severityToPriority.mediumstring"P2"Priority for a medium-severity finding.
severityToPriority.lowstring"P3"Priority for a low-severity finding.

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

KeyTypeDefaultDescription
authIsolation.pathRestring"auth|jwt|token|session|acl|permission|rbac|secret|credential|vault|tenant|isolation|password|login|logout|identity|role"Regex over changed file paths that trips the gate. Full-override env ECHO_AUTH_ISOLATION_PATH_RE.
authIsolation.ssoRestring"(^|/)sso(/|$)|oauth|oidc|saml"Additional SSO-specific path regex. Env ECHO_AUTH_ISOLATION_SSO_RE.
authIsolation.excludeRestring"\\.md$|^docs/"Paths excluded from the gate (docs/markdown don’t count as auth changes). Env ECHO_AUTH_ISOLATION_EXCLUDE_RE.

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.

KeyTypeDefaultDescription
testCoverage.thresholdnumber150Added non-docs source-line ceiling before the gate trips. Env override ECHO_TEST_COVERAGE_THRESHOLD.
testCoverage.labelstring"needs-tests"Label applied when the gate trips (non-blocking by default).
testCoverage.excludeRestring""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.testFileRestring""Regex identifying test files (empty = built-in patterns like *_test.go, *.spec.ts). Env ECHO_TEST_COVERAGE_TEST_FILE_RE.
testCoverage.blockPathRestring""Extra paths that escalate a tripped gate from non-blocking to a hard hold. Env ECHO_TEST_COVERAGE_BLOCK_PATH_RE.

Defaults for create_feature.sh (the backlog write path).

KeyTypeDefaultDescription
createFeature.defaultLabelstring"enhancement"Label applied to a newly filed item when none is given.
createFeature.defaultStatusstring"Backlog"Starting Status column for a newly filed item.
KeyTypeDefaultDescription
supportingReposobject[][]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-argocd verify.configRepo must reference one of them.
KeyTypeDefaultDescription
fieldNamesstring[]["Status", "Category", "Priority", "Size"]The single-select board field names fields.sh resolves live via GraphQL each run (option ids rotate, so they’re never pinned).
KeyTypeDefaultDescription
gatedPathsstring[][".claude/echo/**"]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.)
KeyTypeDefaultDescription
categoryMapobject{}Path-prefix → board Category map (e.g. {"skills/": "Engine"}) used by board provisioning/filing. Empty = fall back to a single category.
fallbackCategorystring""Category used when no categoryMap prefix matches; empty = provisioning’s built-in “General”.
KeyTypeDefaultDescription
areaSkillsobject{}Map of path/area → skill name(s) to surface to a worker touching that area. Empty = no area-specific skills injected.
KeyTypeDefaultDescription
bailVocabularystring[][]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.
KeyTypeDefaultDescription
baseBranchstring"main"The branch workers cut worktrees from and target PRs against.
branchPrefixstring"agent/issue-"Prefix for a worker’s branch — Echo’s deterministic <branchPrefix><issue#> convention that reconcile.sh matches merged PRs by.
deferMarkerstring"<!-- echo-defer -->"HTML-comment sentinel written into a deferred item so pre-validate can recognize (and count toward workers.deferCap) its own prior defers.
memoryPathstring""Path to the echo-vault root; empty = no vault, all vault writes no-op.
lessonsstring"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 varOverrides / effectFallback
ECHO_PROJECT_ROOTThe registered repo root the invoked script operates on. Required (exported by the caller) for most worker/backlog scripts.
ECHO_REGISTRYPath to the registry file.~/.config/echo/registry.json
ECHO_WORKER_SLOTSworkers.slots. 0 is a soft kill-switch (blocks new spawns without the hard switch).config workers.slots
ECHO_DISPATCHER_DISABLEDHard 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_BUDGETworkers.tokenBudget.config value
ECHO_PREVALIDATE_MODELworkers.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_MODELvault.distillModel.config value → claude-haiku-4-5
ECHO_DISTILL_TIMEOUTSeconds the vault distill Claude call may run.120
ECHO_REVIEW_GATE_MODEForce the review gate mode (e.g. block) regardless of the review-block label.unset
ECHO_STALE_REVIEW_GRACESeconds gated_merge.sh waits before treating a review as stale.1800
ECHO_PR_CI_TIMEOUT_SECONDSSeconds a worker waits for PR CI to finish.900
ECHO_SETUP_HOLD_FOR_HUMANWrite-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_MODELThe model name Fable resolves to when available.fable
ECHO_RISK_CATEGORY_RErisk.categoryRegex.config value
ECHO_RISK_LABEL_RErisk.labelRegex.config value
ECHO_AUTH_ISOLATION_PATH_REFull override of authIsolation.pathRe.config value
ECHO_AUTH_ISOLATION_SSO_REFull override of authIsolation.ssoRe.config value
ECHO_AUTH_ISOLATION_EXCLUDE_REFull override of authIsolation.excludeRe.config value
ECHO_SECURITY_PATH_REOverride of the security-path regex the worker gate reads.config-derived
ECHO_TEST_COVERAGE_THRESHOLDtestCoverage.threshold.config value → 150
ECHO_TEST_COVERAGE_EXCLUDE_REtestCoverage.excludeRe.config value
ECHO_TEST_COVERAGE_TEST_FILE_REtestCoverage.testFileRe.config value
ECHO_TEST_COVERAGE_BLOCK_PATH_REtestCoverage.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.