Settings reference¶
Generated page
This page is built from settings-schema.js, the registry the plugin itself
reads, every time the site is built, so it always matches the code on main.
How to change a setting: Changing settings.
Settings live in ~/.anti-hall/settings.json. Change them with /anti-hall:settings
("set autoHandover.pct to 80") or the CLI:
How to read the tables
| Mark | Meaning |
|---|---|
| config | Also shown in Claude Code's native /config panel. |
| safety | Changing it in the risky direction needs --confirmed (or a direct request from you). |
| advanced | A tuning knob. settings.js show hides it unless you pass --all. |
Env is an ANTIHALL_* environment variable that overrides the file value.
Precedence, highest first: environment variable, settings.json, the /config panel,
a legacy per-feature file, the default.
330 settings in 20 sections: Auto Handover, Guards, Safety Guards, Context Injections, Maintenance, Agent tracker, Version Alerts, Updates / Maintenance, Limit Conservation, Jev (semantic decision engine), Jev cascade, Jev integration, DevSwarm, Statusline, Codex Nudge, Process watch, Resource watch, Disk watch, Engine, Defects.
Auto Handover¶
Automatic session-handover writing as context fills up.
| Setting | Default | Type | Env | What it does |
|---|---|---|---|---|
autoHandover.enabled |
true |
boolean | Write an automatic handover before context runs out. | |
autoHandover.pct |
85 |
number, 1 to 99 | ANTIHALL_AUTO_HANDOVER_PCT |
Context-usage percent that triggers an automatic handover. |
autoHandover.maxTokens |
0 |
number, at least 0 | ANTIHALL_AUTO_HANDOVER_MAX_TOKENS |
Opt-in absolute context-token ceiling that also triggers the handover, whichever of pct/maxTokens fires first; 0 (the default) = no ceiling — the real per-session context window size (85% of it) is the only trigger unless a user explicitly sets this. |
autoHandover.nag |
true |
boolean | Nag (remind) the user when a handover is due but not yet written. | |
autoHandover.nagStepPct |
5 |
number, 1 to 100 | Percent increments between successive handover nags. | |
autoHandover.nagQuietMin |
15 |
number, at least 1 | Minutes to wait before repeating a handover nag. | |
autoHandover.gateNewWork |
true |
boolean | Post-handover new-work gate: once context is past the threshold and this session's handover is written, the agent judges each new request's size before starting it and, if it needs more than gateBudgetPct of the context window, offers to park it in the task list + handover (start after /compact or /clear) or proceed if you insist. Quick questions, finishing the in-flight task, and spawning a DevSwarm workspace pass straight through. | |
autoHandover.gateBudgetPct |
5 |
number, 1 to 50 | Context-window points a new request may use after the handover before the gate applies; also the measured backstop — one capped reminder to refresh the handover and offer to park the rest once usage grows this many points past where the handover was saved. | |
autoHandover.decisivePrompt |
true |
boolean | At a Stop (turn-ending) point once this session's handover exists and is fresh, tell the agent to end its reply with one prominent line naming the exact /compact (or /clear, or Codex /new) command — or, if the handover has gone stale since it was written, to refresh it first. Off reverts to the plain fire/pause-nag wording. | |
autoHandover.gateHousekeepingMarkers |
"" |
comma-separated list | Extra comma-separated markers (case-insensitive substrings) that identify a scheduled/cron housekeeping prompt (e.g. a mailbox-wake or DevSwarm peer-check tick), on top of the built-in defaults ("inbox tick", "peer check", "BROADCAST bug sweep") — a matching prompt never gets the POST-HANDOVER NEW-WORK GATE nudge. |
Guards¶
On/off switches and tuning for the always-on safety guard hooks.
| Setting | Default | Type | Env | What it does |
|---|---|---|---|---|
guards.mergeGate |
false |
boolean | ANTIHALL_MERGE_GATE |
Enable merge-readiness gate checks before merging. |
guards.shipitGate |
false |
boolean | ANTIHALL_SHIPIT_GATE |
Enable the ship-it workflow gate. |
guards.outputVerifyGuard |
true |
boolean | ANTIHALL_OUTPUT_VERIFY_GUARD |
Output-verification guard (blocks unverified completion claims). |
guards.outputVerifyOncePerTurn |
true |
boolean | ANTIHALL_OUTPUT_VERIFY_ONCE_PER_TURN |
Show the output-verify advisory once per turn per distinct pass/fail signal set instead of after every identical test re-run. false = every mixed result. |
guards.failureRootCauseNudge |
true |
boolean | ANTIHALL_FAILURE_ROOT_CAUSE_NUDGE |
Nudge toward root-cause analysis after a failure. |
guards.failureNudgeFilter |
true |
boolean | ANTIHALL_FAILURE_NUDGE_FILTER |
Cut root-cause-nudge noise: stay silent on expected exit-1 predicates (grep no-match, test, diff, git diff --quiet), on harness refusals, and after the first nudge in a turn. false = nudge on every failure again. |
guards.repoSelfDrift |
true |
boolean | ANTIHALL_REPO_SELF_DRIFT |
anti-hall's own repo-drift self-check hook. |
guards.stashGuard |
false |
boolean | ANTIHALL_STASH_GUARD |
SAFETY (confirm to change — see settings.js set/reset). Arm the git-stash guard in command-guard: block mutating git stash (also armed per-repo via .anti-hall/protected-stashes). If changed: git stash commands that can silently drop uncommitted work will no longer be blocked. |
guards.handoverCommitGuard |
true |
boolean | ANTIHALL_HANDOVER_COMMIT_GUARD |
git-guard: block a git commit whose paths include a session handover (.anti-hall/handovers/* at any depth, or HANDOVER.md, CONTINUE-HERE.md, *.continue-here.md at the repo root). Handovers are local session state and are never committed; git add is never blocked. |
guards.gitAliasResolve |
true |
boolean | ANTIHALL_GIT_ALIAS_RESOLVE |
SAFETY (confirm to change). git-guard: resolve git <alias> through the repo/global git config (chains and !shell aliases included) and scan what it really runs; block defining a git alias (git config alias.x, -c alias.x=, GIT_CONFIG_VALUE_<n>) or shell alias whose body is a blocked git command, and scan a call to a shell alias/function defined in the same command as the git command it forwards to. Builtin subcommands never spawn git. If changed: a git alias (or a shell alias) that runs a force push or an AI-credited commit will no longer be seen through. |
guards.gitReusedMessageCheck |
true |
boolean | ANTIHALL_GIT_REUSED_MESSAGE_CHECK |
SAFETY (confirm to change). git-guard: for a git commit with no -m/-F, read the message it would reuse (-C/-c/--reuse-message/--reedit-message <rev>, HEAD for --amend, -t/commit.template) and block it when it carries an AI self-credit trailer. A reused-verbatim message (-C, --no-edit, a no-op editor) always blocks; an editor-path commit blocks only when the command sets no real editor of its own. What an editor or commit hook writes is caught afterwards by the PostToolUse audit. If changed: a commit that reuses an AI-credited message (-C/-c <rev>, --amend, a commit template) will no longer be blocked before it runs. |
guards.gitGuardHeredocData |
true |
boolean | ANTIHALL_GIT_GUARD_HEREDOC_DATA |
git-guard: a heredoc whose consumer is not a shell is data, so its body is not scanned as commands. Applies only when every heredoc in the command ends in a prose/data file (.md, .txt, .rst, .log, ...) via cat/tee, a git commit/tag/notes/merge message (-F - or $(cat <<EOF)), or a gh pr/issue/release body; every other command in the line is on a short allowlist (cat, tee, git, gh, echo, printf, cd, mkdir, wc, ...); and no target is a script, dotfile, git hook or .git/.husky/.ssh/.config path. Bodies fed to bash/sh/eval/source/xargs/python/..., piped into a shell, or written to a file the same line runs stay scanned. A git beside it may use only commit/tag/notes/merge, status/log/diff/show/add or rev-parse with listed flags (no fetch/push/pull/clone/remote/submodule/config), and a gh only pr/issue/release create/edit/comment with listed flags and no --; any other flag keeps the bodies scanned. Commit/PR credit trailers are checked either way. false = scan every heredoc body as shell (the old behaviour). |
guards.gitignoreHint |
true |
boolean | ANTIHALL_GITIGNORE_HINT |
One-time (per project, every 7 days) SessionStart reminder to git-ignore .anti-hall/ when it exists in a git repo and is not ignored; doctor always reports it. |
guards.allowAnthropicEnvKey |
false |
boolean | SAFETY, home-settings only (no env / project override). Opt-in: let the speculation judge and Jev triage read ANTHROPIC_API_KEY from the environment (Codex has no plugin options). Default off. If changed: anti-hall would read ANTHROPIC_API_KEY from the environment for the speculation judge and Jev triage fallback instead of only the anthropic_api_key plugin option. |
|
guards.emitDedupe |
true |
boolean | ANTIHALL_EMIT_DEDUPE |
Deduplicate repeated hook-emit output. |
guards.injectionRepeatEvery |
10 |
number, at least 0 | ANTIHALL_INJECTION_REPEAT_EVERY |
Turns between full re-injections of a static UserPromptSubmit reminder block (VERIFY-FIRST, the task-tracker SHORT line, the DEVSWARM PRIMARY dispatch-tier/top-fan-out-tier suffixes, the DEVSWARM WORKSPACES table when unchanged) once its first-turn/post-compact copy has been consumed; set 0 to restore every-turn injection. |
guards.codexQuotaDetect |
true |
boolean | ANTIHALL_CODEX_QUOTA_DETECT |
Detect a Codex-CLI quota/rate-limit exhaustion message in a codex:codex-rescue Agent result and record it to ~/.anti-hall/codex-availability.json so other lanes/sessions stop rediscovering the outage independently. |
guards.editGuardAllow |
"" |
comma-separated list | ANTIHALL_EDIT_GUARD_ALLOW |
SAFETY (confirm to change — see settings.js set/reset). Extra allowed file globs for edit-guard (comma/colon separated). If changed: those files can be edited without edit-guard's protection. |
guards.allowSubagentMailbox |
false |
boolean | ANTIHALL_ALLOW_SUBAGENT_MAILBOX |
SAFETY (confirm to change — see settings.js set/reset). One-off allow for the subagent-mailbox command pattern. If changed: subagents can read/ack the Primary's mailbox, which is normally blocked. |
guards.allowReadOnlyVerify |
true |
boolean | ANTIHALL_ALLOW_READ_ONLY_VERIFY |
command-guard "narrow allow": lets the coordinator run ONLY these shapes inline: a non-heavy command with --check/--dry-run/--list, <cc> -fsyntax-only, python3 -m pytest -q <one file>, node --test <1-2 files>, ctest -R <name>, or git clone --depth 1 <https-url> <scratchpad/tmp dir>, only when piped to tail/head/grep -c/grep -m N/wc with no other segment left unaccounted for and no write redirect outside the scratchpad/tmp. |
guards.allowReadOnlyVerifyScripts |
true |
boolean | ANTIHALL_ALLOW_READ_ONLY_VERIFY_SCRIPTS |
command-guard read-only verify, script form: lets the coordinator run <python*\|node\|ruby\|perl\|php> <existing script file> --check\|--dry-run\|--list piped to a bounded sink inline. No inline-code flag (-c/-e/-m/--eval), no stdin/heredoc script, no wrapper verb, and the remaining arguments must be non-heavy. Requires guards.allowReadOnlyVerify. |
guards.projectCommandAllow |
true |
boolean | ANTIHALL_PROJECT_COMMAND_ALLOW |
command-guard per-project allowlist: a repo may declare its own sanctioned exact commands (e.g. a deploy script that must never be delegated) in <repo-toplevel>/.anti-hall/command-allow.json, run inline in the MAIN THREAD ONLY. Default empty config means no behavior change. Kill-switch: false disables the carve-out entirely. |
guards.projectEditAllow |
true |
boolean | ANTIHALL_PROJECT_EDIT_ALLOW |
edit-guard per-project doc-edit allowlist: a repo may list repo-relative globs in <repo-toplevel>/.anti-hall/edit-allow.json ({"paths":["docs/**"]}) that the MAIN THREAD may Edit/Write directly. Applies only after the user trusts that exact file content (settings.js trust-edit-allow <repo> --confirmed); an edit revokes trust. Never matches outside the repo, .git, .anti-hall, .claude, .codex, hooks config or ~/.claude. Kill-switch: false disables it. |
guards.allowPlainPush |
true |
boolean | ANTIHALL_ALLOW_PLAIN_PUSH |
command-guard "allow plain push": in the MAIN THREAD ONLY, lets git add/git commit/a plain git push [-q\|--quiet\|-u\|--set-upstream] [remote] [ref] (ref omitted, HEAD, or the current branch only; -u/--set-upstream needs an explicit remote AND ref and is never combined with another flag), and &&/; chains made up only of those three, run inline instead of being delegated. Also allows ONE optional leading cd <path> (only when it realpaths to the payload cwd's own repo toplevel or a directory inside it) and optional trailing read-only segments (git log --oneline [-N], git status [--short\|-s], git show --stat [-N\|HEAD]) ONLY after a push segment. --force/-f/--force-with-lease/--force-if-includes/--mirror/--delete/-d/--all/--tags/+refspec/src:dst-to-another-branch/any other flag combined with -q stay exactly as blocked as before; git-guard.js keeps its own independent force-push/AI-credit checks. |
guards.allowGcloudReads |
true |
boolean | ANTIHALL_ALLOW_GCLOUD_READS |
command-guard narrow read-only Google Cloud access: in the MAIN THREAD ONLY, lets the cloud CLI token-printing command, read-style verbs (describe, list, get-iam-policy, read) with JSON, YAML or value output into a bounded sink or jq, and a token-authorized silent HTTP GET with an optional Bearer header to an https URL on googleapis.com or a subdomain run inline. Mutating verbs, other HTTP methods, request bodies, uploads, output-to-file flags, redirects, proxies, IP literals, @file arguments and chained commands stay blocked. |
guards.allowBackgroundScratchScripts |
true |
boolean | ANTIHALL_ALLOW_BACKGROUND_SCRATCH_SCRIPTS |
command-guard background scratch scripts: in the MAIN THREAD ONLY, a Bash call with run_in_background: true may run ONE segment <interpreter> <script file> [args…] (python3, node, sh or bash) when the file is an existing regular file inside the session scratchpad or a tmp root (os.tmpdir(), /tmp, /private/tmp; realpath-checked). No interpreter option before the file (-c/-e/…), no env prefix or wrapper, no chaining/pipes, no substitution or expansion, no stdin redirect, no write redirect outside the scratchpad/tmp. Foreground runs keep the normal rules. Each such run counts toward the main-thread work window (guards.coordinatorWorkWindowMinutes). |
guards.coordinatorWorkWindowMinutes |
10 |
number, at least 0 | ANTIHALL_COORDINATOR_WORK_WINDOW_MINUTES |
Main thread only: successful state-changing Bash calls (WORK: state-changing git, gh mutations, Bash writes into non-notes repo files, scratch/tmp/.anti-hall script runs, other text-script runs except tracked-and-clean project scripts, package-manager/system tools and old ~/.local/bin, ~/Library or ~/.claude/plugins tools, inline -c/-e code that writes files or runs state-changing git/gh) are counted over this many minutes. In a non-git project only coordinator-writable or fresh scripts count; an old script there is not WORK. Set nudgeAt and blockAt to 0 to record only. 0 = feature off. |
guards.coordinatorWorkNudgeAt |
4 |
number, at least 0 | ANTIHALL_COORDINATOR_WORK_NUDGE_AT |
One advisory note each time the window's WORK count reaches this. 0 = no nudge. |
guards.coordinatorWorkBlockAt |
7 |
number, at least 0 | ANTIHALL_COORDINATOR_WORK_BLOCK_AT |
The Nth WORK Bash call within the window is blocked. Recovery commands (git am/rebase/cherry-pick/revert --abort|--quit, merge --abort, stash pop/apply) and loosely matched inline code are counted but never blocked. Skip key coordinator-work-guard. 0 = no block. |
guards.coordinatorWorkMaxEntries |
50 |
number, at least 1 | ANTIHALL_COORDINATOR_WORK_MAX_ENTRIES |
Safety cap on stored window timestamps per session. |
guards.bashEditParity |
true |
boolean | ANTIHALL_BASH_EDIT_PARITY |
command-guard applies edit-guard's verdict to Bash writes (sed -i, perl -i, tee, cp, mv, >/>> redirects, literal python -c / node -e open-for-write paths) into repo files in the main thread. git verbs and trusted (redirect-free) project command-allow matches are never blocked by it. Also off when safety.editGuard is off or edit-guard is skipped. Both hosts (tested with Claude and Codex payload shapes). |
guards.shellWriteChecks |
true |
boolean | ANTIHALL_SHELL_WRITE_CHECKS |
api-guard and ship-it-guard also run on Bash (both hosts): the files a shell write targets (>/>> redirects, heredoc into cat/tee, echo/printf, tee, sed -i, perl -i, cp/mv, python -c open(..., 'w')) go through ship-it-guard's existence gate, and the visible text of a heredoc/echo/printf write into a .py/.js/.ts file through api-guard. Writes into the session scratchpad or a tmp root outside a repo are not gated by ship-it-guard. Unparseable forms are allowed. Each guard's own switch (guards.apiGuard, guards.shipitGate) still applies; edit-guard's Bash parity is guards.bashEditParity. |
guards.reaperMatch |
"" |
string | ANTIHALL_REAPER_MATCH |
Extra process-name pattern for the MCP session-end reaper. |
guards.reaperExclude |
"" |
string | ANTIHALL_REAPER_EXCLUDE |
Excludes matching processes from the MCP reaper. |
guards.reaperCodexBroker |
true |
boolean | ANTIHALL_REAPER_CODEX_BROKER |
companion/mcp-reaper.js: REPORT (list in the reaper log, both dry-run and real runs) abandoned openai-codex plugin app-server-broker.mjs helper processes. REPORT-ONLY — this class is NEVER killed (a 2026-09-25 safety review found its detection could not be made reliable enough to act on automatically: unquoted --cwd paths with spaces, symlinked /tmp-vs-/private/tmp cwd mismatches, and the broker's own app-server child always looking like an "owner"). It is spawned detached+unref ON PURPOSE (PPID 1 is normal for a LIVE broker, not evidence of death), so a candidate is listed only when its --cwd directory no longer exists, or no live claude/codex process (excluding the broker's own descendants) has a realpath'd cwd equal to/an ancestor of/a descendant of the realpath'd --cwd, AND it is older than guards.reaperCodexBrokerMinAgeS. Matched by an exact script-name + codex-plugin-path signature, kept fully separate so this never loosens the MCP matcher (which alone can still trigger a real kill). Only takes effect when the opt-in companion reaper is installed and running. |
guards.reaperCodexBrokerMinAgeS |
1800 |
number, at least 0 | ANTIHALL_REAPER_CODEX_BROKER_MIN_AGE_S |
Minimum age in seconds an app-server-broker.mjs whose owner cannot be found must have before the report-only class above will list it (30 minutes by default — deliberately conservative since PPID gives no death signal for this class); an unresolvable age is always skipped, never listed. |
guards.tasklistWorkThreshold |
3 |
number, at least 1 | ANTIHALL_TASKLIST_WORK_THRESHOLD |
Minimum work items before tasklist-guard fires. |
guards.pruneCompletedTasksAfter |
10 |
number, at least 1 | ANTIHALL_PRUNE_COMPLETED_TASKS_AFTER |
TOKEN SAVINGS (0.117.0): once completed/cancelled tasks in the list exceed this count, task-guard emits a one-line advisory (never a block) to prune them via TaskUpdate status=deleted after recording them in the history ledger — Claude Code's own TaskCreate reminder re-prints the whole list, completed tasks included, every few turns. |
guards.progressFreshMs |
1800000 |
number, at least 0 | ANTIHALL_PROGRESS_FRESH_MS |
Freshness window (ms) for the progress file in tasklist-guard. |
guards.apiGuardThirdparty |
false |
boolean | ANTIHALL_API_GUARD_THIRDPARTY |
Also verify installed 3rd-party package APIs, not just stdlib/builtins. |
guards.modelRouting |
strict |
one of strict, advisory, off |
ANTIHALL_MODEL_ROUTING |
model-routing-guard (PreToolUse Agent/Task): strict blocks a mis-tiered spawn, advisory only warns, off disables the hook. |
guards.noBlockingQuestions |
off |
one of off, advise, block |
ANTIHALL_NO_BLOCKING_QUESTIONS |
ask-guard (PreToolUse AskUserQuestion, optional): advise adds a standing-rule reminder to the question call, block refuses it unless the first question starts with DESTRUCTIVE: or CREDENTIAL:. off (default) does nothing. |
guards.questionAgentsNote |
true |
boolean | ANTIHALL_QUESTION_AGENTS_NOTE |
ask-guard (PreToolUse AskUserQuestion): when a question is asked while background agents are in flight, add one advisory line naming them and saying they may act on an option before the answer arrives (pause them or tell them to wait). Silent when no agent is running or the count cannot be proven. Never blocks; independent of guards.noBlockingQuestions. |
guards.sharedTreeAgentNote |
true |
boolean | ANTIHALL_SHARED_TREE_AGENT_NOTE |
swarm-guard (PreToolUse Agent/Task): when a write-capable subagent is spawned without isolation:"worktree" while another write-capable agent is still running in the same working tree, add one advisory sentence (two such agents can commit each other's uncommitted changes: isolate, serialize, or use separate scratch clones; in a repo whose CLAUDE.md/AGENTS.md says no worktrees: serialize or use scratch clones). Read-only agent types, isolated spawns, and unknown agent state are silent. Never blocks. |
guards.mergeSidePickAdvisory |
true |
boolean | ANTIHALL_MERGE_SIDE_PICK_ADVISORY |
merge-side-pick (PostToolUse + PreToolUse Bash, Claude and Codex): records a conflict resolved by taking one side wholesale (git checkout/restore --ours|--theirs, git merge/pull/rebase -X ours|theirs, git merge -s ours) and test runs (npm/pnpm/yarn test, node --test, pytest, go/cargo/flutter/dart test, mvn/gradle test, make test, ...) per session; a push while a side-pick has no test run after it adds one advisory (run the tests, review the discarded side). Never blocks; silent when tests ran after the side-pick or state is unknown. On Codex it shows only on builds that support PreToolUse additionalContext (rust-v0.129.0+). |
guards.updateInSession |
true |
boolean | ANTIHALL_UPDATE_IN_SESSION |
model-routing-guard blocks a subagent spawn that runs anti-hall's own update (skills/update/scripts/update.js, or run /anti-hall:update): update.js runs migrations, so it runs in the main session, which judges the result. off allows delegating it. |
guards.modelRoutingDeployFloor |
sonnet |
one of sonnet, opus, off |
ANTIHALL_MODEL_ROUTING_DEPLOY_FLOOR |
model-routing-guard floor for deploy/migration/rollback/production/secret/credential-shaped spawns: such a spawn at or above the floor is never blocked, one below it (or with no explicit model) gets an advisory to use at least the floor. off restores the plain routing table. |
guards.apiGuard |
true |
boolean | api-guard (PreToolUse Write/Edit): block fabricated stdlib/builtin APIs in written code. | |
guards.speculationGuard |
true |
boolean | speculation-guard (Stop): block a turn that ends on unverified hedged claims. | |
guards.inferenceCheck |
false |
boolean | ANTIHALL_INFERENCE_CHECK |
speculation-guard (Stop): also block, once per reply, a confident causal claim with no hedge word ("caused by", "the root cause is", "because", "is due to", "stems from", "this means", "the culprit is", "that's why") when no tool output, observation-tool input, pasted fenced block or task notification in the transcript window mentions the stated cause. Default off: 100% precision on the 84-case synthetic corpus (tools/eval/inference-bench.js) but it flagged 3.8% of 3,276 real final replies, mostly design rationale ("X because Y"), so field precision is far below 0.9. |
guards.claimLedger |
true |
boolean | claim-ledger (Stop, never blocks): record claims in the last reply that nothing in the session backs. | |
guards.taskGuard |
true |
boolean | task-guard (Stop): block stopping while tracked tasks are still open. | |
guards.taskGuardOwnerBlockedMarker |
true |
boolean | ANTIHALL_TASK_GUARD_OWNER_BLOCKED_MARKER |
task-guard IDLE NEGLECT: honor an explicit owner-blocked marker (metadata.blockedOn / blockedOn === 'owner'|'user'|'human'|'external', or an "OWNER:" / "OWNER DECISION" subject prefix) as non-dispatchable, instead of requiring a fake blockedBy dependency to silence the nag. Off reverts to pre-marker behavior (only a real blockedBy id suppresses idle-neglect). |
guards.dispatchDemand |
true |
boolean | ANTIHALL_DISPATCH_DEMAND |
Per-turn "DISPATCH NOW in parallel: #id subject, …" demand (task-tracker, UserPromptSubmit) and task-guard IDLE NEGLECT count in-flight agents PER TASK from this session's transcript (an agent whose description names #id covers it; unmapped agents cover one task each; running < min(16, cores-2)). Off removes the per-turn DISPATCH NOW line and restores task-guard's legacy blanket rule (any fresh ~/.anti-hall/agents heartbeat suppresses IDLE NEGLECT). Metrics: node scripts/dispatch-report.js. |
guards.idleNeglectMinPriority |
p1 |
one of p0, p1, p2, p3 |
ANTIHALL_IDLE_NEGLECT_MIN_PRIORITY |
task-guard IDLE NEGLECT urgency floor: a pending/unowned/unblocked task nags only when its priority is at or above (numerically ≤) this rank; anything below it (e.g. P2/P3 when the floor is P1, or "low"/"deferred") is non-nagging backlog. Missing/unrecognized priority is always treated as P1 (fail-open). Consistent across every rank — was previously hard-coded to skip only the literal "P2". |
guards.maxParallelDispatch |
0 |
number, at least 0 | ANTIHALL_MAX_PARALLEL_DISPATCH |
Hard cap on concurrently-running background agents the DISPATCH NOW line / task-guard IDLE NEGLECT will demand up to. 0 (default) = the existing dynamic cap (min(16, cores-2)). Some owners run exactly ONE implementation agent per workspace at a time — setting this to 1 makes the demand ask for the next task only once nothing is running, instead of piling on parallel dispatch pressure. |
guards.idleNeglectProvenOnly |
true |
boolean | ANTIHALL_IDLE_NEGLECT_PROVEN_ONLY |
task-guard IDLE NEGLECT blocks only when a dispatchable task is uncovered under every placement of the running agents that name no task (dispatchable > unmapped agents). The per-turn DISPATCH NOW line is unchanged. false = also block on the in_progress-first estimate. |
guards.idleNeglectAgentMaxAgeMin |
30 |
number, at least 0 | ANTIHALL_IDLE_NEGLECT_AGENT_MAX_AGE_MIN |
task-guard IDLE NEGLECT, proven count only: a running agent that names no task stops counting as cover once its newest sign of life (launch, SendMessage resume, pending teammate message, output-file write) is older than this many minutes, or when it was launched before the earliest uncovered task was created / last set pending or in_progress (it cannot be working on a task that did not exist). Unknown age or unknown task time = the agent still counts. 0 = never age out. |
guards.tasklistGuard |
true |
boolean | tasklist-guard (Stop): require a task list / progress file for multi-step work. | |
guards.tasklistNoTaskTools |
reduced |
one of reduced, full, skip |
ANTIHALL_TASKLIST_NO_TASK_TOOLS |
tasklist-guard (Stop): the nag form for a session that is positively known to lack task tools (today: a Codex session with no task-tool evidence in its transcript). reduced (default) = no TaskCreate demand, list the tasks in the reply, blocks at most once per session; full = today's TaskCreate/TaskUpdate demand; skip = no nag. A Claude session with no evidence keeps the full demand (task tools exist before they leave a trace). When not set explicitly, context.protocolLevel=full makes the default full. |
guards.stopNagBudgetPerPrompt |
0 |
number, at least 0 | ANTIHALL_STOP_NAG_BUDGET |
Per-prompt cap on Stop blocks from task-guard and tasklist-guard (each counted separately), inside their existing session caps (5 and 3). 0 (default) = off = today's behaviour; N = at most N blocks per user prompt, keyed by the Stop payload prompt_id or the last user entry uuid (no key = not applied). speculation-guard, claim-ledger and the PreToolUse guards are not affected. |
guards.scanThrottle |
true |
boolean | ANTIHALL_SCAN_THROTTLE |
scan-throttle (PreToolUse Bash): advise running heavy repo-wide scans at background priority (nice/taskpolicy); never rewrites the command. |
guards.silentAgentNudge |
true |
boolean | ANTIHALL_SILENT_AGENT_NUDGE |
silent-agent-nudge (Stop): nudge once, advisory-only, when a background Agent launch in the transcript has no terminal notification and a stale/missing output_file (plus the ~/.anti-hall/agents/<id>.json heartbeat as an extra signal), past silentAgentNudgeMin. Never kills anything. |
guards.silentAgentNudgeMin |
20 |
number, at least 1 | ANTIHALL_SILENT_AGENT_NUDGE_MIN |
Minutes of silence (no terminal task-notification + stale/missing output_file, or a stale heartbeat) before silent-agent-nudge fires. |
guards.staleAgentStopNote |
true |
boolean | ANTIHALL_STALE_AGENT_STOP_NOTE |
stale-agent-stop-note (PreToolUse TaskStop, never blocks): one advisory line when TaskStop names an agent that was sent a message, or resumed, after its last report and has not reported since (it may be working). Settings file + env only; no plugin option. |
guards.idleAgentSweep |
true |
boolean | ANTIHALL_IDLE_AGENT_SWEEP |
idle-agent-sweep (UserPromptSubmit, Claude + Codex, never blocks): once per user prompt, lists agents that finished but were never stopped (Claude: named teammates whose last report is an idle_notification with idleReason available/failed and no later SendMessage or TaskStop; Codex: multi_agent_v1 agents whose wait_agent result is completed/errored and that were never closed) and gives the exact TaskStop / close_agent call. Fires when guards.idleAgentSweepCount are idle or one has been idle guards.idleAgentSweepMin minutes |
guards.idleAgentSweepCount |
3 |
number, at least 1 | ANTIHALL_IDLE_AGENT_SWEEP_COUNT |
idle-agent-sweep fires when at least this many finished agents are idle and not stopped |
guards.idleAgentSweepMin |
15 |
number, at least 1 | ANTIHALL_IDLE_AGENT_SWEEP_MIN |
idle-agent-sweep also fires when any one finished agent has been idle at least this many minutes |
guards.compactAdviceGuard |
true |
boolean | compact-advice-guard (Stop): block once when the reply recommends /compact ("SAFE TO COMPACT", "good point to /compact") while context is below the auto-handover threshold minus compactAdviceMarginPct, or within compactAdviceRecentTurns turns of a compact. The threshold-fired auto-handover path stays allowed. | |
guards.compactAdviceRecentTurns |
10 |
number, 0 to 100 | compact-advice-guard: a compact boundary within this many turns makes a new /compact recommendation a block; 0 turns the recent-compact rule off. | |
guards.compactAdviceMarginPct |
10 |
number, 0 to 50 | compact-advice-guard: context-% points below autoHandover.pct at which a /compact recommendation counts as low-context. | |
guards.compactDeclarationGuard |
true |
boolean | compact-declaration-guard (PreToolUse — opt-in in 0.116.0 pending false-positive fixes on quoted/question/negated "safe to compact" phrasing; RE-ENABLED by default in 0.117.0 now that findAdvice ignores quoted/question/negated/conditional mentions, see hooks/lib/compact-advice.js): after declaring SAFE TO COMPACT in a turn, block new work (Agent/Task spawns, Write/Edit/NotebookEdit, state-changing Bash) until the next user message or an explicit "RETRACT SAFE TO COMPACT" line. Read-only tools stay allowed. | |
guards.stopHookVersionDowngrade |
true |
boolean | ANTIHALL_STOP_HOOK_VERSION_DOWNGRADE |
When installed_plugins.json (harness-owned) has re-registered a newer anti-hall version than this running session, downgrade nudge-class Stop-hook blocks (silent-agent-nudge, tasklist-guard, devswarm-parent-gate NEGLECT) to advisory (skip the block) until restart — a stale already-fixed nudge should not keep blocking. Never applied to safety guards. |
guards.stopAck |
true |
boolean | ANTIHALL_STOP_ACK |
Nudge-class Stop hooks (silent-agent-nudge, tasklist-guard) honor a per-signature session ack the agent writes to ~/.anti-hall/stop-ack/<session>.json once the user has explicitly confirmed a condition is a false positive — the same signature then stays advisory (never blocks again) for the rest of the session. Off disables the mechanism entirely (hooks block exactly as before it existed). Never applies to safety guards. |
Safety Guards¶
Switches for the safety-critical guards (force-push / AI self-credit / heavy-command and edit delegation / runaway spawns). Normal precedence applies (env > ~/.anti-hall/settings.json > /config > default): a value in settings.json counts like any other. settings.js set to the risky value, and a reset whose fallback value is risky, need --confirmed (a human direct command, or the user saying yes after a one-line factual warning); re-arming a guard never does. Off = the guard's core check no-ops; the per-guard skip.json escape hatch is unchanged.
| Setting | Default | Type | Env | What it does |
|---|---|---|---|---|
safety.gitGuard |
true |
boolean | ANTIHALL_GIT_GUARD |
git-guard: block force-push and AI self-credit in commits and gh pr/issue/release bodies. If changed: force-pushes and AI credit lines in commits will no longer be stopped. |
safety.commandGuard |
true |
boolean | ANTIHALL_COMMAND_GUARD |
command-guard core: make the coordinator delegate heavy commands (build/test/deploy/push). Its data-safety sub-guards (DevSwarm read/send/mailbox, armed stash guard) stay on. If changed: heavy commands (builds, tests, deploys, pushes) will run directly in the main session instead of being handed to a helper. |
safety.editGuard |
true |
boolean | ANTIHALL_EDIT_GUARD |
edit-guard core: make the coordinator delegate file edits outside its own plan/state/handover files. If changed: edits to protected files like plugin config and secrets will no longer be stopped. |
safety.swarmGuard |
true |
boolean | ANTIHALL_SWARM_GUARD |
swarm-guard: block agent spawns past the spawn-rate cap or under critical memory pressure. If changed: nothing will stop runaway agent spawning that can overload the machine. |
Context Injections¶
The verify-first protocol and the other text anti-hall injects at session start, per turn, and into subagents.
| Setting | Default | Type | Env | What it does |
|---|---|---|---|---|
context.verifyFirstSession |
true |
boolean | verify-first-full (SessionStart): inject the full verify-first protocol (also re-injected after compaction). | |
context.verifyFirstOrchestration |
true |
boolean | verify-first-orch (SessionStart): inject the orchestration discipline for the main thread. | |
context.protocolLevel |
compact |
one of compact, full |
ANTIHALL_PROTOCOL_LEVEL |
Size of the verify-first and orchestration text anti-hall injects. compact (default) sends a short core with every load-bearing clause inline and points at PROTOCOL.md for the rest; full sends today's complete text on every channel, byte for byte (the one-key rollback). |
context.codexOrchFullOn |
session |
one of session, spawn |
ANTIHALL_CODEX_ORCH_FULL_ON |
EXPERIMENTAL, Codex only (>= 0.129, hooks trusted). session (default) = the full orchestration rules (A-N) arrive at SessionStart, as today. spawn = SessionStart sends the compact core + compact orchestration lines and the full rules arrive once per context epoch on the first spawn_agent call (a PreToolUse additionalContext, which Codex 0.160 surfaces to the model). Needs positive Codex evidence, otherwise session; ignored under protocolLevel=full, and a DevSwarm Primary always gets the full text at SessionStart. |
context.orchFullOn |
auto |
one of auto, spawn, session, off |
ANTIHALL_ORCH_FULL_ON |
When the full orchestration rules (A-N) are sent under protocolLevel=compact. auto (default) = session = inline at SessionStart next to the compact core; spawn = EXPERIMENTAL: compact lines at SessionStart and the full text once on the first Agent/Task/Workflow spawn of each context epoch (Claude Code only, needs positive platform evidence, otherwise coerced to session; unproven live, so opt-in); off = the compact lines only (also on Codex). Ignored under protocolLevel=full; a DevSwarm Primary always gets the full text at SessionStart. |
context.verifyFirstTurn |
true |
boolean | verify-first (UserPromptSubmit): the short per-turn verify-first nudge. | |
context.verifyFirstSubagent |
true |
boolean | verify-first-subagent (SubagentStart): inject the protocol into every subagent. | |
context.taskTracker |
true |
boolean | task-tracker (UserPromptSubmit): the task-list discipline directive and per-turn reminder. | |
context.handoverResume |
true |
boolean | handover-resume (SessionStart): point a fresh or compacted session at the newest handover. | |
context.defectNudge |
true |
boolean | defect-nudge (SessionStart): the once-a-day note about the defect channel. | |
context.dedupeWindowMin |
20 |
number, at least 0 | ANTIHALL_DEDUPE_WINDOW_MIN |
Fallback per-session suppression window (minutes) for repeated UserPromptSubmit injection blocks (LIMIT CONSERVATION, TASK-LIST, DEVSWARM COMMS OVERRIDE, DEVSWARM WORKSPACES) when a burst of queued prompts is delivered together and the transcript cannot confirm the earlier copy was already read — used only as the last-resort guard; content that changed always re-emits. 0 disables emit-dedupe entirely (same as guards.emitDedupe=false). |
context.roleGuard |
true |
boolean | ANTIHALL_ROLE_GUARD |
engine-role-guard (PreToolUse Bash) and the ah-engine command line: refuse an engine verb the caller's role may not run (roles.matrix: owner-level verbs are main-session only, a workspace child acts on itself only). |
context.roleNote |
true |
boolean | ANTIHALL_ROLE_NOTE |
engine-role-note (SessionStart, SubagentStart): tell the session its role, the engine verbs it may use and where the guide is (the anti-hall:engine skill). |
context.injectGate |
true |
boolean | ANTIHALL_INJECT_GATE |
Token cuts, master switch (engine dispatcher): the hooks that re-send the same context every turn (limit-conserve-inject, task-tracker, the DevSwarm comms-override line, swarm-guard's shared-tree advisory) are passed on only when the model does not already hold it; off hands every hook's output through unchanged. The state is per session and per agent, bounded in the daemon, cleared by SessionStart (compaction), and fails open. |
context.injectGateLimit |
true |
boolean | ANTIHALL_INJECT_GATE_LIMIT |
Token cut 1: the limit-conservation directive is injected when the usage band or reset window changes, else as a short keepalive every context.injectGateLimitEvery turns (its reset time jitters by milliseconds, which defeated the Node dedupe). |
context.injectGateLimitEvery |
10 |
number, at least 1 | ANTIHALL_INJECT_GATE_LIMIT_EVERY |
Turns between keepalives of an unchanged limit-conservation directive. |
context.injectGateTask |
true |
boolean | ANTIHALL_INJECT_GATE_TASK |
Token cut 2: task-tracker's long directive always passes; its short reminder passes every context.injectGateTaskEvery turns, its freshness note when it changed. |
context.injectGateTaskEvery |
10 |
number, at least 1 | ANTIHALL_INJECT_GATE_TASK_EVERY |
Turns between short task-tracker reminders and unchanged freshness notes. |
context.injectGateComms |
true |
boolean | ANTIHALL_INJECT_GATE_COMMS |
Token cut 3: the DevSwarm comms-override line and the workspace-title instruction are injected once per session, when changed, and as a keepalive every context.injectGateCommsEvery turns. |
context.injectGateCommsEvery |
30 |
number, at least 1 | ANTIHALL_INJECT_GATE_COMMS_EVERY |
Turns between keepalives of the unchanged comms-override line. |
context.injectGateSwarm |
true |
boolean | ANTIHALL_INJECT_GATE_SWARM |
Token cut 4: swarm-guard's shared-tree advisory is injected when new or changed, and again only after context.injectGateSwarmEvery turns. |
context.injectGateSwarmEvery |
20 |
number, at least 1 | ANTIHALL_INJECT_GATE_SWARM_EVERY |
Turns between repeats of an unchanged shared-tree advisory. |
Maintenance¶
Background housekeeping hooks: self-repair, pruning, snapshots, logs, and the session-end MCP sweep.
| Setting | Default | Type | Env | What it does |
|---|---|---|---|---|
maintenance.repairOnReload |
true |
boolean | ANTIHALL_REPAIR_ON_RELOAD |
repair-on-reload (SessionStart/UserPromptSubmit): re-apply safe doctor repairs after a plugin update. |
maintenance.progressPrune |
true |
boolean | progress-prune (SessionStart): archive stale per-session progress files into the history ledger. | |
maintenance.precompactSnapshot |
true |
boolean | precompact-snapshot (PreCompact): write a mechanical continuation snapshot before compaction. | |
maintenance.taskLifecycleLog |
true |
boolean | task-lifecycle-log (TaskCreated/TaskCompleted): append task events to the per-session history ledger. | |
maintenance.sessionEndReaper |
true |
boolean | ANTIHALL_SESSION_END_REAPER |
session-end-mcp-reaper (SessionEnd): kill orphaned MCP-server processes this session left behind. |
Agent tracker¶
The engine-side agent tracker: follows every agent, raises hung / looping / token-waste / drift / stale-heartbeat / no-wake-path signals, and reminds. It never stops an agent.
| Setting | Default | Type | Env | What it does |
|---|---|---|---|---|
agents.tracker |
true |
boolean | ANTIHALL_AGENT_TRACKER |
agent tracker (engine job agent_tick): track agents and raise signals; off, a tick does nothing. |
agents.reminders |
true |
boolean | ANTIHALL_AGENT_REMINDERS |
agent-reminders (UserPromptSubmit, PostToolUse): deliver the tracker's queued reminders to the agent that owns them; off, signals are recorded but nothing is queued. |
agents.ownerNotify |
false |
boolean | ANTIHALL_AGENT_OWNER_NOTIFY |
agent tracker owner notices: also append hung / looping / token-waste advisories to the owner notices file. |
Version Alerts¶
Update-available nudges for anti-hall, Claude CLI, and DevSwarm.
| Setting | Default | Type | Env | What it does |
|---|---|---|---|---|
versionAlerts.antiHall |
true |
boolean | ANTIHALL_VERSION_ALERT |
Alert when a newer anti-hall version is available. |
versionAlerts.claudeCli |
true |
boolean | ANTIHALL_CLAUDE_CLI_VERSION_ALERT |
Alert when a newer Claude CLI version is available. |
versionAlerts.devswarm |
true |
boolean | ANTIHALL_DEVSWARM_VERSION_ALERT |
Alert when a newer DevSwarm/hivecontrol version is available. |
Updates / Maintenance¶
/anti-hall:update and its background sweep.
| Setting | Default | Type | Env | What it does |
|---|---|---|---|---|
updates.quiet |
false |
boolean | ANTIHALL_UPDATE_QUIET |
Suppress update output (for scripted capture). |
updates.reconcileBudgetMs |
60000 |
number, at least 0 | ANTIHALL_RECONCILE_BUDGET_MS |
Time budget (ms) for the reconcile step during update; 0 = unlimited. |
updates.postpullBudgetMs |
90000 |
number, at least 0 | ANTIHALL_UPDATE_POSTPULL_BUDGET_MS |
Time budget (ms) for the post-pull update sweep; 0 = unlimited. |
updates.sweepBudgetMs |
20000 |
number, at least 0 | ANTIHALL_UPDATE_SWEEP_BUDGET_MS |
Overall time budget (ms) for the update sweep. |
updates.allowCachePrune |
true |
boolean | ANTIHALL_ALLOW_CACHE_PRUNE |
Enables the opt-in doctor --prune-cache verb (never automatic: not run by update.js, the supervisor, a cron, SessionStart or any hook). Without --confirmed it only lists the old ~/.claude/plugins/cache/anti-hall/anti-hall/<semver>/ dirs it would remove and their total size; --confirmed removes them and logs each removal. Always keeps the newest 3, the installPath registered in installed_plugins.json, every version a live process runs from, the running version, and anything unparseable; symlinks and paths outside that root are refused. false disables the verb. |
Limit Conservation¶
Auto-downshift behavior as usage approaches plan limits.
| Setting | Default | Type | Env | What it does |
|---|---|---|---|---|
limitConserve.mode |
auto |
one of auto, on, off |
ANTIHALL_LIMIT_CONSERVE |
Force conservation mode on/off, or auto-detect from the OMC usage cache. |
limitConserve.threshold |
85 |
number, 1 to 99 | ANTIHALL_LIMIT_THRESHOLD |
Usage percent that triggers conservation mode. |
limitConserve.accountCheck |
true |
boolean | ANTIHALL_LIMIT_ACCOUNT_CHECK |
Guard against stale usage-cache readings after an account switch. |
Jev (semantic decision engine)¶
Opt-in Jev "System One" classifier for triage/decisions.
| Setting | Default | Type | Env | What it does |
|---|---|---|---|---|
jev.enabled |
false |
boolean | ANTIHALL_JEV |
Enable Jev (ANTIHALL_JEV=0 always force-disables regardless of this). |
jev.transport |
vercel |
one of vercel, typesafe |
Vercel AI Gateway passthrough (default) or a direct TypeSafe API call. | |
jev.fallbackTransport |
none |
one of none, vercel, typesafe |
Automatic backup transport: when the primary vendor times out, errors (5xx) or is out of balance (402/429), retry ONCE on this one, inside the same time budget. none = off; equal to jev.transport = off. Needs its own vendor-bound key (jev_vercel_api_key / jev_typesafe_api_key, or the key file of that vendor with jev.allowLegacyKeyRead). Text then reaches the second vendor. | |
jev.judgeModel |
haiku |
string | ANTIHALL_JUDGE_MODEL |
Model used for speculation-judge / jev-triage LLM calls. |
jev.judgeBackend |
api |
one of api, cli, auto |
ANTIHALL_JUDGE_BACKEND |
How speculation-judge (jev.semanticJudge) reaches the model. api (default) = Anthropic API with the anthropic_api_key plugin option; cli = the local claude -p CLI on your own Claude login, no API key (isolated: no tools, no MCP, no settings files, all hooks disabled; about 5-6 s per turn end, measured); auto = api when a key is visible, else cli. Fail-open in every mode. |
jev.speculationBackend |
haiku |
one of haiku, jev, cascade |
ANTIHALL_JEV_SPECULATION_BACKEND |
Which backend answers the semantic speculation question when jev.semanticJudge is on. haiku (default) = the judge asks the model (jev.judgeModel through jev.judgeBackend), except while Jev's own speculation integration is on; jev = the judge never asks the model and speculation-guard's Jev path is the only semantic check; cascade = as jev, and the Jev-first cascade (jevCascade.speculation) is switched on. Answered by the engine (engine/defaults/judge.toml); the Node fallback keeps the haiku behaviour. |
jev.triageBackend |
jev |
one of jev, haiku, cascade |
ANTIHALL_JEV_TRIAGE_BACKEND |
Which backend labels mesh messages in ah-engine jev triage. jev (default) = Jev first, the Anthropic API fills a missing label when a key is visible (the hooks/lib/jev-triage-worker.js behaviour); haiku = the model alone, through jev.judgeBackend (the local claude -p CLI about 3-4 s per message, so raise the triage budget to use it). |
jev.cascade |
true |
boolean | ANTIHALL_JEV_CASCADE |
Global kill switch of the Jev-first cascade. false = no Jev answer is ever re-judged by the model, whatever the per-integration jevCascade switches say. |
jev.cascadeShowJevAnswer |
true |
boolean | ANTIHALL_JEV_CASCADE_SHOW_JEV_ANSWER |
Whether the model that re-judges an unsure Jev answer is shown Jev's answer and confidence (true) or only the evidence (false), so anchoring can be A/B tested. |
jev.semanticJudge |
false |
boolean | ANTIHALL_SEMANTIC_JUDGE |
Enable the semantic speculation-judge hook (off = hook no-ops). |
jev.allowLegacyKeyRead |
false |
boolean | SAFETY, home-settings only (no env / project override). Opt-in: let anti-hall read the Jev key from AI_GATEWAY_API_KEY / TYPESAFE_API_KEY env vars and the key file (jev.keyFile or the default path) — the only way background tools (CLI, finding-dedup, jev-report) and Codex (no plugin options) see a key. Default off: only the jev_api_key plugin option is used. If changed: anti-hall would read the Jev gateway key (AI_GATEWAY_API_KEY / TYPESAFE_API_KEY env vars and the key file) from this machine instead of only the jev_api_key plugin option. |
|
jev.genericKeyVendor |
vercel |
one of vercel, typesafe |
SAFETY, home-settings only (no env / plugin option / legacy file). The ONE vendor the legacy generic jev_api_key and jev.keyFile are bound to; they are never sent to any other vendor, whatever jev.transport or jev.fallbackTransport say. Vendor-named keys (jev_vercel_api_key / jev_typesafe_api_key) need no binding. Change it only with jev-setup.js bind-generic-key --vendor <v>. If changed: the generic jev_api_key plugin option and jev.keyFile would be sent to the other vendor (they carry no vendor name, so anti-hall sends them only to this one). |
|
jev.keyFile |
"" |
string | Credential key-file path (default depends on transport). | |
jev.timeoutMs |
1500 |
number, 1 to 3000 | Per-call timeout (ms), capped at 3000. | |
jev.confidenceThreshold |
0.85 |
number, 0 to 1 | Minimum confidence for a Jev answer to be trusted by callers. | |
jev.triage |
true |
boolean | Message-triage labeling once Jev is enabled. | |
jev.triageUrgentThreshold |
0.9 |
number, 0 to 1 | Confidence threshold for the urgent triage label. | |
jev.budget.mode |
unlimited |
one of unlimited, watch |
Jev spend: no limit, or warn when over budget (never auto-disables). | |
jev.budget.usdPerDay |
(none) | number | optional: daily USD spend threshold, used only when budget.mode=watch. | |
jev.budget.usdPerWeek |
(none) | number | optional: weekly USD spend threshold, used only when budget.mode=watch. | |
jev.weeklyNotice |
true |
boolean | Once-a-week SessionStart scorecard notice naming one integration worth promoting or turning off (Jev enabled only). | |
jev.audit.snippets |
false |
boolean | ANTIHALL_JEV_AUDIT_SNIPPETS |
Store a redacted ~200-char snippet for decisions Jev changed or would change in shadow mode (off by default: privacy). |
jev.logRotatedFiles |
10 |
number, 1 to 100 | Rotated generations kept for jev-assist.ndjson (2MB each) and jev-triage.ndjson (1MB each): .1 .. .N. 10 keeps roughly 20 days of decision rows at ~1MB/day. | |
jev.rollupRetentionDays |
0 |
number, 0 to 3650 | Days of daily rollups (~/.anti-hall/logs/jev-daily/<day>.json) to keep. 0 (default) keeps every rollup; only an explicit N > 0 removes rollups older than N days. | |
jev.budget.minCreditUsd |
(none) | number | optional: warn (once a day, budget.mode=watch only) when the gateway credit balance drops below this USD amount. | |
jev.prices |
(none) | object | computed: per-model USD price table {model: {inPerMTok, outPerMTok}} (or a "default" entry), used only when the gateway reports tokens but no cost. File-only (no env, no CLI set) — edit ~/.anti-hall/settings.json directly. | |
jev.priceUsdPerMInput |
0.042 |
number, at least 0 | ANTIHALL_JEV_PRICE_USD_PER_M_INPUT |
USD per 1M input tokens for the Jev judge call, used to compute costUsd when the gateway reports tokens but no cost and prices has no matching entry. Default is Jev's own published rate (verified: typesafe.ai, vercel.com/ai-gateway/models/jev, openrouter.ai/typesafe). |
jev.priceUsdPerMOutput |
0 |
number, at least 0 | ANTIHALL_JEV_PRICE_USD_PER_M_OUTPUT |
USD per 1M output tokens for the Jev judge call (default 0 — output is free on the verified rate). |
jev.dispatchTierNoWorkspaceRepos |
"" |
comma-separated list | ANTIHALL_JEV_DISPATCH_TIER_NO_WORKSPACE_REPOS |
Repos (directory basenames or absolute paths; "*" = all) where the Jev dispatchTier recommendation never says workspace (shown as subagent, logged as repo-override). |
jev.dispatchTierDetectNoWorkspaces |
true |
boolean | ANTIHALL_JEV_DISPATCH_TIER_DETECT_NO_WORKSPACES |
Also treat a repo as no-workspace when its CLAUDE.md / AGENTS.md says "no workspaces for real work". |
jev.reviewAfterDays |
7 |
number, 1 to 365 | ANTIHALL_JEV_REVIEW_AFTER_DAYS |
Minimum days an integration must have sat in shadow mode before its shadow numbers are DUE for owner review (also the re-review cadence once reviewed). |
jev.reviewMinDecisions |
30 |
number, at least 0 | ANTIHALL_JEV_REVIEW_MIN_DECISIONS |
Minimum decisions logged for a shadow integration before its review is due — avoids nagging about a barely-used integration with too little data to judge. |
jev.reviewReminder |
true |
boolean | ANTIHALL_JEV_REVIEW_REMINDER |
Durable "time to review the Jev shadow numbers" SessionStart/doctor nudge (on by default — owner opt-out only). |
jev.recommendNotice |
true |
boolean | ANTIHALL_JEV_RECOMMEND_NOTICE |
Bold "Recommended: enable Jev" notice at SessionStart (once on first install, then at most every 30 days) and in doctor, shown only while Jev is NOT enabled. Set false to silence it. |
jev.recommendNoticeHeadless |
false |
boolean | ANTIHALL_JEV_NOTICE_HEADLESS |
Allow the "Recommended: enable Jev" notice in non-interactive runs (claude -p / SDK, detected by CLAUDE_CODE_ENTRYPOINT=sdk-*). Default false: nobody reads it there, and a headless run no longer uses up the notice's once-per-30-days slot. JEV REVIEW DUE is not affected; Codex is unchanged. When not set explicitly, context.protocolLevel=full makes the default true. |
Jev cascade¶
Per-integration switch of the Jev-first cascade: when Jev answers below the integration's escalation threshold (engine/defaults/judge.toml, default = its act threshold) the answer is re-judged by the model, shown Jev's answer unless jev.cascadeShowJevAnswer is off. A hook-blocking decision never waits: Jev's answer applies now and the re-judged one from the next turn. Off by default = today's behaviour.
| Setting | Default | Type | Env | What it does |
|---|---|---|---|---|
jevCascade.speculation |
off |
one of on, off |
ANTIHALL_JEV_CASCADE_SPECULATION |
Re-judge a speculation Jev answer the model when Jev is unsure (see the section note). on | off. |
jevCascade.triage |
off |
one of on, off |
ANTIHALL_JEV_CASCADE_TRIAGE |
Re-judge a triage Jev answer the model when Jev is unsure (see the section note). on | off. |
jevCascade.newRequest |
off |
one of on, off |
ANTIHALL_JEV_CASCADE_NEW_REQUEST |
Re-judge a newRequest Jev answer the model when Jev is unsure (see the section note). on | off. |
jevCascade.claimLedger |
off |
one of on, off |
ANTIHALL_JEV_CASCADE_CLAIM_LEDGER |
Re-judge a claimLedger Jev answer the model when Jev is unsure (see the section note). on | off. |
jevCascade.outputVerifyGuard |
off |
one of on, off |
ANTIHALL_JEV_CASCADE_OUTPUT_VERIFY_GUARD |
Re-judge a outputVerifyGuard Jev answer the model when Jev is unsure (see the section note). on | off. |
jevCascade.gitGuardSelfCredit |
off |
one of on, off |
ANTIHALL_JEV_CASCADE_GIT_GUARD_SELF_CREDIT |
Re-judge a gitGuardSelfCredit Jev answer the model when Jev is unsure (see the section note). on | off. |
jevCascade.modelRouting |
off |
one of on, off |
ANTIHALL_JEV_CASCADE_MODEL_ROUTING |
Re-judge a modelRouting Jev answer the model when Jev is unsure (see the section note). on | off. |
jevCascade.tasklistTrivial |
off |
one of on, off |
ANTIHALL_JEV_CASCADE_TASKLIST_TRIVIAL |
Re-judge a tasklistTrivial Jev answer the model when Jev is unsure (see the section note). on | off. |
jevCascade.codexNudgeSubstantial |
off |
one of on, off |
ANTIHALL_JEV_CASCADE_CODEX_NUDGE_SUBSTANTIAL |
Re-judge a codexNudgeSubstantial Jev answer the model when Jev is unsure (see the section note). on | off. |
jevCascade.mergeGateHedge |
off |
one of on, off |
ANTIHALL_JEV_CASCADE_MERGE_GATE_HEDGE |
Re-judge a mergeGateHedge Jev answer the model when Jev is unsure (see the section note). on | off. |
jevCascade.parentGateQuestion |
off |
one of on, off |
ANTIHALL_JEV_CASCADE_PARENT_GATE_QUESTION |
Re-judge a parentGateQuestion Jev answer the model when Jev is unsure (see the section note). on | off. |
jevCascade.supervisorBlockerLabel |
off |
one of on, off |
ANTIHALL_JEV_CASCADE_SUPERVISOR_BLOCKER_LABEL |
Re-judge a supervisorBlockerLabel Jev answer the model when Jev is unsure (see the section note). on | off. |
jevCascade.findingDedup |
off |
one of on, off |
ANTIHALL_JEV_CASCADE_FINDING_DEDUP |
Re-judge a findingDedup Jev answer the model when Jev is unsure (see the section note). on | off. |
jevCascade.postHandoverGate |
off |
one of on, off |
ANTIHALL_JEV_CASCADE_POST_HANDOVER_GATE |
Re-judge a postHandoverGate Jev answer the model when Jev is unsure (see the section note). on | off. |
jevCascade.speculationFramed |
off |
one of on, off |
ANTIHALL_JEV_CASCADE_SPECULATION_FRAMED |
Re-judge a speculationFramed Jev answer the model when Jev is unsure (see the section note). on | off. |
jevCascade.dispatchTier |
off |
one of on, off |
ANTIHALL_JEV_CASCADE_DISPATCH_TIER |
Re-judge a dispatchTier Jev answer the model when Jev is unsure (see the section note). on | off. |
jevCascade.devswarmOnBrief |
off |
one of on, off |
ANTIHALL_JEV_CASCADE_DEVSWARM_ON_BRIEF |
Re-judge a devswarmOnBrief Jev answer the model when Jev is unsure (see the section note). on | off. |
jevCascade.devswarmExtraSanctioned |
off |
one of on, off |
ANTIHALL_JEV_CASCADE_DEVSWARM_EXTRA_SANCTIONED |
Re-judge a devswarmExtraSanctioned Jev answer the model when Jev is unsure (see the section note). on | off. |
jevCascade.devswarmWaitKind |
off |
one of on, off |
ANTIHALL_JEV_CASCADE_DEVSWARM_WAIT_KIND |
Re-judge a devswarmWaitKind Jev answer the model when Jev is unsure (see the section note). on | off. |
jevCascade.devswarmLoop |
off |
one of on, off |
ANTIHALL_JEV_CASCADE_DEVSWARM_LOOP |
Re-judge a devswarmLoop Jev answer the model when Jev is unsure (see the section note). on | off. |
jevCascade.devswarmStepMap |
off |
one of on, off |
ANTIHALL_JEV_CASCADE_DEVSWARM_STEP_MAP |
Re-judge a devswarmStepMap Jev answer the model when Jev is unsure (see the section note). on | off. |
Jev integration¶
Per-integration trust mode for every Jev-assisted decision point (v0.108.4 — each of the 13 integrations gets its own row/setting; postHandoverGate added in 0.109.0; the five devswarm* supervision integrations in 0.117.0; legacy home: jev.json "integrations.<id>" and, pre-0.108.4, settings.json jev["integrations.<id>"]). on = Jev may change the outcome (bounded by its own trust rule below), shadow = consulted + logged only, off = not consulted.
| Setting | Default | Type | Env | What it does |
|---|---|---|---|---|
jevIntegrations.speculation |
on |
one of on, shadow, off |
Is this claim unsupported speculation (add-block trust: Jev may only turn a non-block baseline into a block). | |
jevIntegrations.triage |
on |
one of on, shadow, off |
Mesh message urgency/kind labeling (advisory: its own triage client, not the add-block/relax-block trust model). | |
jevIntegrations.newRequest |
shadow |
one of on, shadow, off |
Classify a prompt as new-request/follow-up/correction/question (advisory trust). | |
jevIntegrations.claimLedger |
shadow |
one of on, shadow, off |
Is a flagged claim genuinely unsupported by evidence (relax-block trust: Jev may only turn a blocking baseline into a non-block). | |
jevIntegrations.outputVerifyGuard |
shadow |
one of on, shadow, off |
Does this test-runner output actually indicate a pass (advisory trust). | |
jevIntegrations.gitGuardSelfCredit |
shadow |
one of on, shadow, off |
Does this commit/PR message contain paraphrased AI self-credit (add-block trust; never relaxes git-guard). | |
jevIntegrations.modelRouting |
shadow |
one of on, shadow, off |
Is this agent-spawn task actually mechanical (relax-block trust). | |
jevIntegrations.tasklistTrivial |
shadow |
one of on, shadow, off |
tasklist-guard: is this session a genuinely non-trivial, multi-part effort (relax-block trust; asked synchronously, 1.5 s cap, fail-open). | |
jevIntegrations.codexNudgeSubstantial |
shadow |
one of on, shadow, off |
codex-nudge: are these file edits genuinely substantial, not just formatting (relax-block trust; asked synchronously, 1.5 s cap, fail-open). | |
jevIntegrations.mergeGateHedge |
shadow |
one of on, shadow, off |
Does this text hedge on merge-readiness (relax-block trust; askDetached fire-and-forget in shadow). | |
jevIntegrations.parentGateQuestion |
shadow |
one of on, shadow, off |
Is this unread child message really a question awaiting a reply (add-block trust; cache-only, zero network). | |
jevIntegrations.supervisorBlockerLabel |
shadow |
one of on, shadow, off |
Is a stale child waiting-on-parent or genuinely wedged (advisory trust; cache-only, zero network). | |
jevIntegrations.findingDedup |
on |
one of on, shadow, off |
Do two deadly-loop TRIO findings describe the same underlying issue, for advisory duplicate-grouping (advisory trust); default on — 65/65 correct at confidence >=0.85 on a 30-day, 3-project offline benchmark (see CHANGELOG 0.108.4). | |
jevIntegrations.postHandoverGate |
off |
one of on, shadow, off |
Does this new request fit in the remaining post-handover context budget (advisory trust; askDetached fire-and-forget, zero latency). Default off: an offline benchmark (n=299) found park-recall 17.6% vs 28.8% for the agent's own size judgment plus the measured budget backstop, no gain over the baseline. | |
jevIntegrations.speculationFramed |
shadow |
one of on, shadow, off |
When a deterministic speculation-guard hedge hit sits under a FRAMED heading/line prefix (Expected/Plan/"Should be <verb>:"/"Should still"/Unverified/"(unverified)"/"not yet measured"), is it a stated expectation/plan or an unverified claim presented as fact (relax-block trust: Jev may only turn the framed hit's block into a non-block; an unframed hedge never consults Jev and always blocks). | |
jevIntegrations.dispatchTier |
on |
one of on, shadow, off |
Recommend how each dispatchable task should be dispatched — workspace / workflow / subagent — on the DISPATCH NOW line (advisory trust; never blocks or forces; the Primary makes the final call). on = annotate "#7 … → subagent (0.91)"; shadow = log only; asked via askDetached only when a task's text changes (PostToolUse TaskCreate/TaskUpdate). Outcomes logged: actual dispatch followed/overridden, subagent one-lane vs escalated, workflow fan-out. | |
jevIntegrations.devswarmOnBrief |
on |
one of on, shadow, off |
DevSwarm supervision: is a child's off-scope work actually off its brief (relax-block over the off-scope straying warning; asked detached from the supervisor sweep only when off-scope fires, answer read from the cache next sweep; input: current step, scope, last 3 summaries, last 5 commit subjects, off-scope files, <=1.5k chars scrubbed). | |
jevIntegrations.devswarmExtraSanctioned |
on |
one of on, shadow, off |
DevSwarm supervision, recommendation (as devswarmOnBrief): did the user ask for this off-scope work (annotates the off-scope warning, threshold >=0.9; an explicit scope add always wins; input: the child's last user prompts, secrets scrubbed, plus the off-scope files, <=1k chars). |
|
jevIntegrations.devswarmWaitKind |
on |
one of on, shadow, off |
DevSwarm supervision, recommendation (as devswarmOnBrief): is an idle/stalled child stuck or legitimately waiting on CI, the owner or a peer (annotates the idle/stall warning; input: step, last summary, signal reasons, <=800 chars). | |
jevIntegrations.devswarmLoop |
on |
one of on, shadow, off |
DevSwarm supervision, recommendation (as devswarmOnBrief): is a busy child looping on its step (annotates a burn/stall warning, or adds its own advisory loop warning when none fired; threshold >=0.9; asked when the step is older than 2x devswarm.stepStallMin or a burn warning fired; input: step, time on step, tokens since the step moved, summaries, commit churn, <=1.2k chars). |
|
jevIntegrations.devswarmStepMap |
on |
one of on, shadow, off |
DevSwarm supervision, recommendation: which plan step a heartbeat summary without --step describes (threshold 0.8; when on it fills an inferred ~N step that shows only while the child never reported a step itself). |
DevSwarm¶
Multi-workspace mesh orchestration, liveness, and supervisor tuning. Every consumer takes an explicit env parameter (for testability); settings.js routes these through getWithEnv() so a home derived from that SAME env is used, never os.homedir().
| Setting | Default | Type | Env | What it does |
|---|---|---|---|---|
devswarm.hivecontrol |
"" |
string | ANTIHALL_DEVSWARM_HIVECONTROL |
Explicit path to the hivecontrol CLI binary (default: PATH lookup — no single default value; empty means "look it up"). |
devswarm.supervisorMode |
auto |
one of auto, on, off |
ANTIHALL_DEVSWARM_SUPERVISOR |
Force the DevSwarm supervisor context on/off, or auto-detect. |
devswarm.requiredGates |
done,merged,tests_passed |
comma-separated list | ANTIHALL_DEVSWARM_REQUIRED_GATES |
Merge gates required for DevSwarm tasks. |
devswarm.inboxCmd |
"" |
string | ANTIHALL_DEVSWARM_INBOX_CMD |
Consumer-configured command to read pending mesh messages (no built-in default). |
devswarm.heldPartitions |
"" |
comma-separated list | ANTIHALL_DEVSWARM_HELD_PARTITIONS |
Owner-held mesh partition ids (comma-separated). A held partition is exempt from the per-turn "ORPHANED MESH" warning and from reap-orphans; it still appears in diagnose/doctor as held by owner. |
devswarm.childGateStrict |
true |
boolean | ANTIHALL_DEVSWARM_CHILD_GATE_STRICT |
Strict child-gate enforcement. |
devswarm.parentGateCap |
3 |
number, 2 to 5 | ANTIHALL_DEVSWARM_PARENT_GATE_CAP |
Caps the parent-gate wait/child count, clamped to [2,5]. |
devswarm.parentGateNeglectMinUnread |
0 |
number, at least 0 | ANTIHALL_DEVSWARM_PARENT_GATE_NEGLECT_MIN_UNREAD |
Minimum real-unread count a genuinely NOT-busy child needs before the parent Stop gate hard-blocks on it; 0 = any real unread blocks (pre-existing sensitivity, unchanged default). A BUSY child (fresh real-work transcript, see parentGateBusyFreshMin) gets an advisory line instead, until its oldest unread passes parentGateBusyMaxAgeMin. A child waiting on its own question/plan approval always blocks. |
devswarm.parentGateBusyFreshMin |
5 |
number, at least 1 | ANTIHALL_DEVSWARM_PARENT_GATE_BUSY_FRESH_MIN |
Minutes a child's transcript may be quiet and still count as busy for the parent Stop gate. Busy needs positive evidence: a transcript written within this window whose latest turn is real work (not a mailbox ping, not waiting). A live pid or a fresh heartbeat alone never counts. An unresolved tool call on a transcript quiet longer than this is treated as waiting (permission prompt or hung tool). |
devswarm.parentGateBusyMaxAgeMin |
60 |
number, at least 1 | ANTIHALL_DEVSWARM_PARENT_GATE_BUSY_MAX_AGE_MIN |
Age cap (minutes) on the busy advisory: once a busy child's oldest unread message is older than this, the parent Stop gate blocks anyway ("busy but hasn't read mail in Xm"). An unknown message age never qualifies for the advisory. |
devswarm.parentGateNeglectGraceMin |
1 |
number, at least 1 | ANTIHALL_DEVSWARM_PARENT_GATE_NEGLECT_GRACE_MIN |
Grace window (minutes) for a plain unread backlog on the parent Stop gate: an unread message younger than this never counts as neglect by itself, independent of whether the child is separately provably busy — a Primary that just sent a child a message should not be hard-blocked seconds later, before the child has had a turn to read it. Never applies to a store-only (mesh-direct send, or dead/foreign-descriptor) row, and never suppresses an unanswered child question, an escalation, or unread older than this window. Default kept at 1 (not higher) so it never overlaps the 2-minute-old backlog this file's own busy/idle regressions use as their baseline "still neglect" fixture. |
devswarm.activeFloorPct |
50 |
number, 0 to 100 | ANTIHALL_DEVSWARM_ACTIVE_FLOOR_PCT |
Min percent of active workspaces kept in the archived cache (0 disables the floor). |
devswarm.archivedCacheMaxAgeMs |
(none) | number, at least 0 | ANTIHALL_DEVSWARM_ARCHIVED_CACHE_MAX_AGE_MS |
computed: no fixed default — 2x the reconcile sweep’s own resolved cooldown (itself env/default-derived), not a literal constant. |
devswarm.archivedGraceMs |
600000 |
number, at least 0 | ANTIHALL_DEVSWARM_ARCHIVED_GRACE_MS |
Grace period (ms) before a workspace is considered archived. |
devswarm.cooldownSec |
600 |
number, at least 0 | ANTIHALL_DEVSWARM_COOLDOWN_SEC |
Supervisor cooldown (sec) between recovery actions. |
devswarm.idleSec |
900 |
number, at least 60 | ANTIHALL_DEVSWARM_IDLE_SEC |
Supervisor idle threshold (sec). |
devswarm.dormantMs |
1800000 |
number | ANTIHALL_DEVSWARM_DORMANT_MS |
Dormant-workspace threshold (ms). |
devswarm.drainTtlMs |
600000 |
number, at least 0 | ANTIHALL_DEVSWARM_DRAIN_TTL_MS |
TTL (ms) for the drain marker. |
devswarm.graceSec |
5 |
number, 1 to 60 | ANTIHALL_DEVSWARM_GRACE_SEC |
Grace window (sec) before recovery in devswarm-recover. |
devswarm.maxRecoveries |
3 |
number, 1 to 20 | ANTIHALL_DEVSWARM_MAX_RECOVERIES |
Max auto-recovery attempts. |
devswarm.intervalSec |
90 |
number, 60 to 120 | ANTIHALL_DEVSWARM_INTERVAL |
Sweep interval (sec) used at supervisor install time, clamped [60,120]. |
devswarm.migrateMarkRead |
false |
boolean | ANTIHALL_DEVSWARM_MIGRATE_MARK_READ |
Mark migrated messages as read during state migration. |
devswarm.monitorTimeoutSec |
30 |
number, at least 0 | ANTIHALL_DEVSWARM_MONITOR_TIMEOUT_SEC |
Bounded cadence (sec) for monitor timeout in devswarm-ingest. |
devswarm.monitorNoOkFailMin |
10 |
number | ANTIHALL_DEVSWARM_MONITOR_NO_OK_FAIL_MIN |
Minutes without a successful monitor poll (since daemon start, or since the last success) before ingest health reads FAILING; inside the window a fresh daemon reads "starting up". |
devswarm.nudgeCooldownSec |
120 |
number, at least 0 | ANTIHALL_DEVSWARM_NUDGE_COOLDOWN_SEC |
Cooldown (sec) between supervisor nudges. |
devswarm.nudgeMaxAttempts |
2 |
number, 1 to 20 | ANTIHALL_DEVSWARM_NUDGE_MAX_ATTEMPTS |
Max nudge attempts before escalation. |
devswarm.nudgeWindowSec |
180 |
number, at least 1 | ANTIHALL_DEVSWARM_NUDGE_WINDOW_SEC |
Window (sec) for counting nudge attempts. |
devswarm.postSpawnGraceSec |
120 |
number, 0 to 1800 | ANTIHALL_DEVSWARM_POST_SPAWN_GRACE_SEC |
Grace period (sec) right after spawning a child workspace, clamped [0,1800]. |
devswarm.reapedRetentionDays |
30 |
number | ANTIHALL_DEVSWARM_REAPED_RETENTION_DAYS |
Retention window (days) for reaped-workspace logs. |
devswarm.receiptWindowMs |
300000 |
number, at least 0 | ANTIHALL_DEVSWARM_RECEIPT_WINDOW_MS |
Window (ms) for parent-reply receipt tracking. |
devswarm.archiveRequestRenagHours |
24 |
number, at least 1 | ANTIHALL_DEVSWARM_ARCHIVE_REQUEST_RENAG_HOURS |
Hours a pending archive-request suppresses the ARCHIVE-READY re-nudge and the CHILD NOT DRAINING nag for that child before re-nagging anyway. |
devswarm.reconcileSweep |
auto |
one of auto, off |
ANTIHALL_DEVSWARM_RECONCILE_SWEEP |
Enable/disable the periodic reconcile sweep in the supervisor. |
devswarm.reconcileSweepSec |
900 |
number, at least 300 | ANTIHALL_DEVSWARM_RECONCILE_SWEEP_SEC |
Interval (sec) for the reconcile sweep, floor 300s. |
devswarm.sweepTailMode |
node |
one of node, engine |
ANTIHALL_DEVSWARM_SWEEP_TAIL_MODE |
Who decides the DevSwarm sweep tail (archived registry rows, twin descriptors): node = the scheduled Node functions (default); engine = the engine decides each step only after Node's own function, run on a scratch mirror, agrees with it. |
devswarm.rowStaleMs |
86400000 |
number, at least 0 | ANTIHALL_DEVSWARM_ROW_STALE_MS |
Staleness threshold (ms) for workspace row selection. |
devswarm.sendReceiptRetentionDays |
7 |
number | ANTIHALL_DEVSWARM_SEND_RECEIPT_RETENTION_DAYS |
Retention window (days) for send-receipt records. |
devswarm.summaryRetentionDays |
30 |
number, at least 0 | ANTIHALL_DEVSWARM_SUMMARY_RETENTION_DAYS |
Retention window (days) for summary records. |
devswarm.wakeCron |
7,37 * * * * |
string | ANTIHALL_DEVSWARM_WAKE_CRON |
Wake-poll cron schedule override (treated as untrusted input). |
devswarm.rearmOnTickOnly |
true |
boolean | ANTIHALL_DEVSWARM_REARM_ON_TICK_ONLY |
TOKEN SAVINGS (0.117.0): re-arm a lapsed Monitor wake-watch ONLY from the cron tick's own watcherArmed:false check, never inline on the Monitor tool's own final/expired event (reply in <=1 line there instead) — collapses two overlapping ~30-minute re-arm triggers into one. false restores the pre-0.117.0 wording (re-arm inline on expiry too). Metric: ~/.anti-hall/devswarm/rearm-cues.jsonl records each cue by trigger (tick vs expiry). |
devswarm.cronMissingWarnMin |
60 |
number | ANTIHALL_DEVSWARM_CRON_MISSING_WARN_MIN |
Minutes without a fresh inbox-tick marker (cron not running — e.g. after a DevSwarm crash/session restore/Claude restart) before the Stop gate warns once, capped, to CronList/CronCreate the wake cron again. |
devswarm.wakeWatchPollMs |
2000 |
number, 250 to 60000 | ANTIHALL_DEVSWARM_WAKE_WATCH_POLL_MS |
Poll interval (ms) for the wake-watch loop, clamped [250,60000]. |
devswarm.wakeWatchIdleSkip |
true |
boolean | ANTIHALL_DEVSWARM_WAKE_WATCH_IDLE_SKIP |
TOKEN SAVINGS (#39): a Primary with 0 LIVE (non-archived) child workspaces gains nothing from an armed wake-watch Monitor — nothing will ever message it. When on and the caller is a Primary with 0 live children, inbox tick reports watcherArmed idle-skip (not false) so the cron prompt's "re-arm if watcherArmed false" rule does not fire, and devswarm-wake-watch.js, if started anyway, prints one line and exits 0 without arming. A live child (even one Monitor lapse away from false) is unchanged; archived-only, held (devswarm.heldPartitions) and archive-ignored children also count as 0 live. The cron fallback is never affected. Metric: ~/.anti-hall/devswarm/rearm-cues.jsonl records each idle-skip (trigger 'idle-skip'), surfaced by doctor. |
devswarm.dispatchTierText |
true |
boolean | ANTIHALL_DEVSWARM_DISPATCH_TIER_TEXT |
The DevSwarm PRIMARY dispatch-tier text ("the workspace is your top fan-out tier ...") that task-tracker, verify-first and verify-first-orch inject. Off removes it everywhere; it is also never injected in a repo whose CLAUDE.md/AGENTS.md forbids workspaces for real work (jev.dispatchTierNoWorkspaceRepos / dispatchTierDetectNoWorkspaces). Governs only this injected text; the separate devswarm.inlineWorkNudge has its own switch and is not affected. |
devswarm.inlineWorkNudge |
true |
boolean | ANTIHALL_DEVSWARM_INLINE_WORK_NUDGE |
Advisory, DevSwarm PRIMARY only: once per session, when pending actionable tasks exist, the Primary has PROVEN zero live child workspaces, and its main thread has made more than devswarm.inlineWorkNudgeThreshold Edit/Write/NotebookEdit calls, one note says workspace-scale work belongs in a child workspace. Silent when liveness cannot be determined, in a child workspace, and in a repo that forbids workspaces. Rides the edit-guard PreToolUse hook (so it is inactive while safety.editGuard is off or edit-guard is skipped). Never blocks. Independent of devswarm.dispatchTierText (turning that text off does not silence this nudge). |
devswarm.inlineWorkNudgeThreshold |
5 |
number, at least 1 | ANTIHALL_DEVSWARM_INLINE_WORK_NUDGE_THRESHOLD |
Main-thread Edit/Write/NotebookEdit calls a Primary may make before devswarm.inlineWorkNudge fires (the nudge fires on the call after this many). |
devswarm.tickRosterEvery |
0 |
number, at least 0 | ANTIHALL_DEVSWARM_TICK_ROSTER_EVERY |
Every Nth inbox tick --quiet on a Primary appends the compact roster table after the unchanged first line, but only when roster data proves at least one live child and unread is 0 and known. 0 (default) = never. The JSON form of tick (no --quiet, or --json) is never changed. |
devswarm.childGateRetentionDays |
14 |
number | ANTIHALL_DEVSWARM_CHILD_GATE_RETENTION_DAYS |
Days a per-session child-gate state file is kept before the housekeeping/doctor sweep removes it. |
devswarm.housekeepingSweep |
auto |
one of auto, off |
ANTIHALL_DEVSWARM_HOUSEKEEPING_SWEEP |
Supervisor disk-hygiene sweep (reaped logs, child-gate state); only "off" disables it. |
devswarm.housekeepingSweepSec |
3600 |
number, at least 300 | ANTIHALL_DEVSWARM_HOUSEKEEPING_SWEEP_SEC |
Seconds between housekeeping sweeps, floor 300. |
devswarm.supervisorLogRotateBytes |
10485760 |
number | ANTIHALL_DEVSWARM_SUPERVISOR_LOG_ROTATE_BYTES |
Size at which the supervisor rotates its own log. |
devswarm.inboxGraceSec |
120 |
number, at least 0 | ANTIHALL_DEVSWARM_INBOX_GRACE_SEC |
Grace window (sec) before a child's fresh unread is flagged, unless it heartbeats first; 0 = no grace. |
devswarm.stableLauncher |
true |
boolean | ANTIHALL_DEVSWARM_STABLE_LAUNCHER |
Point injected DevSwarm directive text (mailbox wake cron, Monitor re-arm, comms override, drain nudge) at a version-independent launcher under ~/.anti-hall/bin/ instead of the current hook's own version-pinned plugin-cache path, so crons/Monitors/handovers survive an anti-hall update without a manual recreate. false reverts to the raw versioned path. |
devswarm.supervisorSweepBudgetMs |
20000 |
number, at least 0 | ANTIHALL_SUPERVISOR_SWEEP_BUDGET_MS |
Time budget (ms) for one supervisor sweep pass. |
devswarm.supervisorBlockerLabelReaskSec |
21600 |
number, at least 60 | ANTIHALL_DEVSWARM_SUPERVISOR_BLOCKER_LABEL_REASK_SEC |
Seconds a supervisorBlockerLabel ask/log is suppressed while its input (childId+kind+ts) is unchanged, before a periodic re-ask fires anyway. Also the per-input re-ask window of the five devswarm* supervision Jev integrations (0.117.0). |
devswarm.autoArchive.mode |
on |
one of on, dry-run, off |
ANTIHALL_DEVSWARM_AUTO_ARCHIVE_MODE |
Auto-archive finished workspaces (needs DevSwarm ≥ 2.5.3). |
devswarm.autoArchive.idleMin |
30 |
number, at least 5 | ANTIHALL_DEVSWARM_AUTO_ARCHIVE_IDLE_MIN |
Minutes idle before a finished workspace is eligible for auto-archive. |
devswarm.autoArchive.maxPerSweep |
3 |
number, 1 to 20 | ANTIHALL_DEVSWARM_AUTO_ARCHIVE_MAX_PER_SWEEP |
Max workspaces auto-archived in one sweep. |
devswarm.autoArchive.ignorePings |
true |
boolean | ANTIHALL_DEVSWARM_AUTO_ARCHIVE_IGNORE_PINGS |
Idle timer ignores a finished workspace's own mailbox-wake/heartbeat/status turns; real work (an AI turn, a tool call, a new message, a commit) still resets it. Off = any activity resets it. |
devswarm.retention.days |
30 |
number, at least 0 | ANTIHALL_DEVSWARM_RETENTION_DAYS |
Days of message bodies kept before archive+prune; 0 = retention off. |
devswarm.retention.maxStoreMB |
100 |
number, at least 0 | ANTIHALL_DEVSWARM_RETENTION_MAX_STORE_MB |
Store size limit (MB): above it, oldest bodies are pruned regardless of age; 0 = no limit. |
devswarm.retention.keepPerPartition |
200 |
number, at least 0 | ANTIHALL_DEVSWARM_RETENTION_KEEP_PER_PARTITION |
Newest messages per partition that are never pruned (age or size). |
devswarm.retention.archive |
true |
boolean | ANTIHALL_DEVSWARM_RETENTION_ARCHIVE |
Write pruned bodies to the gzip archive first (restorable via devswarm.js retention restore). |
devswarm.retention.archiveMaxMB |
0 |
number, at least 0 | ANTIHALL_DEVSWARM_RETENTION_ARCHIVE_MAX_MB |
Archive size cap (MB); 0 (default) = never evict; above a set cap the oldest archive months are dropped. doctor warns past 500 MB. |
devswarm.parentGate |
true |
boolean | devswarm-parent-gate (Stop): make a Primary attend to a child with unread mail or a stale verdict before stopping. | |
devswarm.childGate |
true |
boolean | devswarm-child-gate (Stop): make a child workspace heartbeat/report to its parent before going idle. | |
devswarm.parentInbox |
true |
boolean | devswarm-parent-inbox (UserPromptSubmit): inject the workspace roster and unread child mail into a Primary. | |
devswarm.childTurn |
true |
boolean | devswarm-child-turn (UserPromptSubmit): inject a child workspace's pending mail each turn. | |
devswarm.childRole |
true |
boolean | devswarm-child-role (SessionStart): inject the mesh-only messaging directive into Primary and child sessions. | |
devswarm.childDrain |
true |
boolean | devswarm-child-drain (PostToolUse Bash): re-surface a child's unread mail mid-task (throttled). | |
devswarm.parentReplyTracker |
true |
boolean | devswarm-parent-reply-tracker (PostToolUse Bash): record the Primary's direct replies so the parent gate can tell read from answered. | |
devswarm.commsGuard |
true |
boolean | devswarm-comms-guard (PreToolUse SendMessage): block SendMessage to a DevSwarm workspace (mesh messaging only). | |
devswarm.inboxReadGuard |
true |
boolean | inbox-read-guard (PreToolUse Read): block raw Read-tool reads of the DevSwarm inbox/store (use the wrapper). | |
devswarm.wakeWatch |
true |
boolean | devswarm-wake-watch monitor: wake an idle session the moment new mesh mail lands (the cron fallback stays). | |
devswarm.appSync |
true |
boolean | ANTIHALL_DEVSWARM_APP_SYNC |
Supervisor app-DB sync: apply the DevSwarm app database (archive state, names, drift) every tick. |
devswarm.screenshotSync |
true |
boolean | devswarm.js sync-ui: reconcile a transcribed sidebar screenshot against the app DB. |
|
devswarm.spawnFromOrigin |
true |
boolean | devswarm.js spawn: fetch origin first and fast-forward the local default branch so a child never starts from stale tooling; refuses when it is behind and cannot be updated (unless --from-local). |
|
devswarm.spawnStrictFlagValues |
true |
boolean | ANTIHALL_DEVSWARM_SPAWN_STRICT_FLAG_VALUES |
devswarm.js spawn: refuse when a value-taking create option (-s/--source, -a/--agent, -t/--title, -p/--prompt) has no value or is handed what looks like the next option (e.g. -t -p "brief" would make "-p" the title), naming the flag. |
devswarm.sendMultiRecipient |
true |
boolean | ANTIHALL_DEVSWARM_SEND_MULTI_RECIPIENT |
devswarm.js send: --to <id1>,<id2> or a repeated --to sends the same body to each (deduped) recipient, attempts every one even after a failure, and exits non-zero if any failed. false restores the old parsing (last --to wins). |
devswarm.spawnFetchTtlSec |
300 |
number, at least 0 | ANTIHALL_DEVSWARM_SPAWN_FETCH_TTL_SEC |
devswarm.js spawn: skip the origin fetch when the remote-tracking ref was already updated within this many seconds (0 = always fetch). |
devswarm.spawnCreateTimeoutMs |
180000 |
number, at least 1000 | ANTIHALL_DEVSWARM_SPAWN_CREATE_TIMEOUT_MS |
Timeout (ms) for the hivecontrol workspace create call spawn makes; on timeout only our own child process is killed. |
devswarm.planTracking |
true |
boolean | ANTIHALL_DEVSWARM_PLAN_TRACKING |
Step-plan tracking: spawn turns a numbered list in -p into the child's plan file, the roster and workspace table show "3/7 done · doing #4 · 42m · progress 18m ago", and the child is reminded to report heartbeat --step N. Off: no plan is written or shown (the explicit plan / heartbeat --step verbs still work). |
devswarm.planRequired |
false |
boolean | ANTIHALL_DEVSWARM_PLAN_REQUIRED |
Ask every child without a step plan to write one (devswarm.js plan set) on each turn. Never refuses a spawn — numbered step lists stay encouraged, not enforced. |
devswarm.stepStallMin |
30 |
number, at least 5 | ANTIHALL_DEVSWARM_STEP_STALL_MIN |
Minutes a busy child with a step plan may go without step progress (or since its last correction) before the supervisor raises a stall straying warning; also the window in which step progress after a correction counts as "correction worked". |
devswarm.strayWarnMax |
2 |
number, 0 to 10 | ANTIHALL_DEVSWARM_STRAY_WARN_MAX |
Most straying warnings per signal (stall / off-scope / idle / jev-*) per plan step; each is shown once as an advisory DEVSWARM STRAYING line. 0 turns straying warnings off. |
devswarm.burnTokensWarn |
2000000 |
number, at least 0 | ANTIHALL_DEVSWARM_BURN_TOKENS_WARN |
Token-burn straying warning: a child with a step plan that spends more than this many weighted tokens (input + output + cache writes + burnCacheReadPct% x cache reads, read incrementally from its own session transcript) since its last step progress gets one burn warning ("used 2.1M tokens since step 3 last moved"). Default 2M: with the 10% cache-read weight, one model call at a 150k-token cached context costs ~17k weighted tokens, so 2M is roughly 100+ calls with no step moving. 0 turns the burn signal off. |
devswarm.burnCacheReadPct |
10 |
number, 0 to 100 | ANTIHALL_DEVSWARM_BURN_CACHE_READ_PCT |
Weight of cache-read tokens in the token-burn figure, in percent (0-100). Default 10 mirrors prompt-cache pricing, where a cache read bills at a tenth of the base input rate; 100 counts every cached token in full. |
devswarm.respawnGraceMin |
20 |
number, at least 0 | ANTIHALL_DEVSWARM_RESPAWN_GRACE_MIN |
Minutes after a correct warning (the plan's warned_at) before the Primary may run devswarm.js respawn <id>. Respawn is never automatic and refuses without a warning. |
devswarm.respawnWipWaitSec |
120 |
number, at least 0 | ANTIHALL_DEVSWARM_RESPAWN_WIP_WAIT_SEC |
Seconds respawn waits for the child to commit and push its WIP after being asked; whatever is still dirty or unpushed is then parked on a new pushed park/<branch>-<ts> branch. |
devswarm.archivedChildStop |
true |
boolean | ANTIHALL_DEVSWARM_ARCHIVED_CHILD_STOP |
An archived child workspace can never re-register its descriptor and is told once to save a handover and stop, instead of being nagged to heartbeat forever. false reverts to pre-fix behaviour (descriptor rewritten every turn, normal heartbeat-report forcing). |
devswarm.maintainerNotice.post |
false |
boolean | ANTIHALL_DEVSWARM_MAINTAINER_NOTICE_POST |
SAFETY (confirm to change — see settings.js set/reset). Allow devswarm.js notice --post from THIS checkout; also requires the checkout's own plugins/anti-hall/.claude-plugin/plugin.json to have name "anti-hall" (a mistake guard, not authentication — see companion/lib/devswarm-maintainer-notice.js). Off refuses every post regardless of the checkout. If changed: this checkout would be allowed to post a maintainer notice that every project's Primary sees — the checkout-name check is a mistake guard, not authentication, so only turn this on in the anti-hall dev checkout. |
devswarm.maintainerNotice.show |
true |
boolean | Show unseen maintainer notices to a Primary (devswarm-parent-inbox.js). Off suppresses the surface only — posting/listing via the CLI is unaffected. | |
devswarm.startupSampling |
true |
boolean | ANTIHALL_DEVSWARM_STARTUP_SAMPLING |
Supervisor reconcile sweep: opportunistically probe hivecontrol workspace info <id> (read-only, bounded, 3s timeout) for stale/not-draining rows and log a non-null startup field or a terminalId change to ~/.anti-hall/logs/devswarm-startup-samples.ndjson — pure data capture toward designing paused-workspace detection. |
devswarm.pausedProbeMax |
8 |
number, 1 to 20 | ANTIHALL_DEVSWARM_PAUSED_PROBE_MAX |
Max hivecontrol workspace info probes per supervisor sweep tick for the startup-state sampler above. |
Statusline¶
The rich statusline (version chip, phase bar, account segment).
| Setting | Default | Type | Env | What it does |
|---|---|---|---|---|
statusline.base |
"" |
string | ANTIHALL_STATUSLINE_BASE |
Shell command run as the line-1 base in consolidated statusline mode. |
statusline.noEmail |
false |
boolean | ANTIHALL_STATUSLINE_NO_EMAIL |
Suppress the email segment in the statusline. |
Codex Nudge¶
Hand-off nudge suggesting Codex for review/diagnosis.
| Setting | Default | Type | Env | What it does |
|---|---|---|---|---|
codexNudge.enabled |
true |
boolean | ANTIHALL_CODEX_NUDGE |
Enable the Codex hand-off nudge hook. |
codexNudge.min |
3 |
number, at least 1 | ANTIHALL_CODEX_NUDGE_MIN |
Minimum substantial code-file edits before the nudge fires. |
Process watch¶
Leftover processes of ended Claude sessions (report by default; kill is a per-class opt-in), and agents silent past a threshold. Warns only, never stops an agent. Needs the ah-engine.
| Setting | Default | Type | Env | What it does |
|---|---|---|---|---|
procwatch.enabled |
true |
boolean | ANTIHALL_PROCWATCH |
procwatch (scheduled sweep + SessionStart/UserPromptSubmit/PreToolUse advisory): look for processes a Claude session left behind (marked by the environment Claude Code sets, owner session gone, class pattern, minimum age) and for agents with no output. Never touches a live session, an unmarked process or a system process. |
procwatch.devServerMode |
report |
one of off, report, kill |
ANTIHALL_PROCWATCH_DEV_SERVER |
dev_server class of the process watch: dev servers and watchers an agent started. off | report (list only, the default) | kill (stop them one pid at a time after a grace period). |
procwatch.testRunnerMode |
report |
one of off, report, kill |
ANTIHALL_PROCWATCH_TEST_RUNNER |
test_runner class of the process watch: test runners and their children. off | report (list only, the default) | kill (stop them one pid at a time after a grace period). |
procwatch.buildDaemonMode |
report |
one of off, report, kill |
ANTIHALL_PROCWATCH_BUILD_DAEMON |
build_daemon class of the process watch: build tool daemons. off | report (list only, the default) | kill (stop them one pid at a time after a grace period). |
procwatch.mcpServerMode |
report |
one of off, report, kill |
ANTIHALL_PROCWATCH_MCP_SERVER |
mcp_server class of the process watch: MCP servers of ended sessions (the SessionEnd reaper, maintenance.sessionEndReaper, is separate). off | report (list only, the default) | kill (stop them one pid at a time after a grace period). |
procwatch.shellTaskMode |
report |
one of off, report, kill |
ANTIHALL_PROCWATCH_SHELL_TASK |
shell_task class of the process watch: background shell commands of ended sessions. off | report (list only, the default) | kill (stop them one pid at a time after a grace period). |
procwatch.otherMode |
report |
one of off, report, kill |
ANTIHALL_PROCWATCH_OTHER |
other (catch-all) class of the process watch: any other process a Claude session started and left behind, oldest first. off | report (list only, the default) | kill (stop them one pid at a time after a grace period). |
procwatch.stuckMinutes |
20 |
number, at least 1 | ANTIHALL_PROCWATCH_STUCK_MINUTES |
Minutes without output after which a background agent of this session is named in a stuck-agent advisory (UserPromptSubmit; warn only, once per cooldown). Reuses the silent-agent-nudge detection. |
Resource watch¶
Soft warning when processes of a live Claude session use too much CPU or memory, or the system swaps. Never kills or throttles by default.
| Setting | Default | Type | Env | What it does |
|---|---|---|---|---|
resourceWatch.enabled |
true |
boolean | ANTIHALL_RESOURCE_WATCH |
resource-watch: sample the processes under live Claude sessions each sweep and warn the session (advisory only). |
resourceWatch.cpuPercent |
90 |
number, at least 1 | ANTIHALL_RESOURCE_WATCH_CPU |
Per-core CPU percent (100 = one core busy; a multi-threaded process can exceed it) every sample of the window must reach. |
resourceWatch.cpuWindowSeconds |
120 |
number, at least 10 | ANTIHALL_RESOURCE_WATCH_CPU_WINDOW |
Seconds the CPU reading must hold. |
resourceWatch.memoryMb |
4096 |
number, at least 16 | ANTIHALL_RESOURCE_WATCH_MEM |
Memory in MB (resident set on Linux, physical footprint on macOS) at which a process of a live session is named. |
resourceWatch.swapMb |
8192 |
number, at least 0 | ANTIHALL_RESOURCE_WATCH_SWAP |
System swap in use, MB, that triggers a warning; 0 = off. |
resourceWatch.pressurePercent |
25 |
number, at least 0 | ANTIHALL_RESOURCE_WATCH_PSI |
Linux memory pressure (PSI some avg10, percent) that triggers a warning; 0 = off. |
resourceWatch.macPressureLevel |
2 |
number, at least 0 | ANTIHALL_RESOURCE_WATCH_MAC_PRESSURE |
macOS memory pressure level (2 warn, 4 critical) that triggers a warning; 0 = off. |
resourceWatch.cooldownSeconds |
900 |
number, at least 0 | ANTIHALL_RESOURCE_WATCH_COOLDOWN |
Least seconds before the same process (or system warning) is named again. |
resourceWatch.renice |
false |
boolean | ANTIHALL_RESOURCE_WATCH_RENICE |
Opt-in: lower the priority (nice 10) of a process the watch warned about, once. Off by default; the watch never kills. |
Disk watch¶
Soft warning when the volumes Claude sessions write to run low on free space. Never deletes anything.
| Setting | Default | Type | Env | What it does |
|---|---|---|---|---|
diskWatch.enabled |
true |
boolean | ANTIHALL_DISK_WATCH |
disk-watch: warn (SessionStart/UserPromptSubmit) when the project, HOME or temp volume is below the warn floor, and before heavy commands (PreToolUse) at the critical floor; names the biggest build/cache directories as a suggestion. |
diskWatch.warnGb |
20 |
number, at least 0 | ANTIHALL_DISK_WATCH_WARN_GB |
Warn below this many GB free (0 = not used). |
diskWatch.warnPercent |
10 |
number, at least 0 | ANTIHALL_DISK_WATCH_WARN_PCT |
Warn below this percent free (0 = not used). |
diskWatch.criticalGb |
5 |
number, at least 0 | ANTIHALL_DISK_WATCH_CRITICAL_GB |
Critical below this many GB free (0 = not used). |
diskWatch.criticalPercent |
3 |
number, at least 0 | ANTIHALL_DISK_WATCH_CRITICAL_PCT |
Critical below this percent free (0 = not used). |
diskWatch.cooldownSeconds |
1800 |
number, at least 0 | ANTIHALL_DISK_WATCH_COOLDOWN |
Least seconds before the same level is warned about again (a worse level always is). |
diskWatch.blockAtCritical |
false |
boolean | ANTIHALL_DISK_WATCH_BLOCK |
Opt-in: at the critical level, block heavy commands (builds, clones, worktree add) instead of only warning. Off by default. |
Engine¶
The optional ah-engine binary (native hook answers).
| Setting | Default | Type | Env | What it does |
|---|---|---|---|---|
engine.bootstrap |
true |
boolean | AH_ENGINE_BOOTSTRAP |
Download and install the sha256-pinned ah-engine binary from the GitHub Release on SessionStart (once per pinned release). Off: nothing is downloaded and the Node hooks answer everything. AH_ENGINE_BOOTSTRAP=0/1 overrides this key. |
engine.autoUpdate |
off |
one of off, stable, dev |
AH_ENGINE_AUTO_UPDATE |
Update the engine binary on its own, at most once a day: off (default), stable (latest ah-engine-v* release) or dev (latest dev pre-release, which also syncs the plugin files of a live kit). Runs hooks/ah-update.sh --auto from the engine scheduler; verifies SHA256SUMS and the GitHub attestation; the previous binary is kept (ah-update.sh --rollback). One-shot: sh hooks/ah-update.sh --from FILE | --channel stable|dev | --rollback. URLs and timeouts: engine/ah-update.toml. |
Defects¶
The two-way defect-reporting channel.
| Setting | Default | Type | Env | What it does |
|---|---|---|---|---|
defects.defaultProj |
"" |
string | ANTIHALL_DEFECT_PROJ |
Default project tag used when filing an anti-hall defect (max 64 chars). |
Engine defaults¶
The Rust engine reads its values from plugins/anti-hall/engine/defaults/, 7512 entries in 67 files (limits, intervals, message texts, patterns). The table lists the ones you can override with an environment variable.
| Value | Default | Env | File | What it does |
|---|---|---|---|---|
schedule.agent_tick_ms |
60000 ms |
AH_ENGINE_AGENT_TICK_MS |
agent_tracker.toml |
Interval of the agent_tick job; 0 turns the tracker's ticks off. |
config.watch_ms |
500 ms |
AH_ENGINE_CONFIG_WATCH_MS |
config.toml |
How often the daemon checks the config files for a change. |
config.debounce_ms |
300 ms |
AH_ENGINE_CONFIG_DEBOUNCE_MS |
config.toml |
A change must stay unchanged this long before it is loaded, so a half-written or rapidly edited file is read once. |
dispatch.in_process |
0 |
AH_ENGINE_DISPATCH_IN_PROCESS |
dispatch.toml |
Run the built-in checks inside the hook client (1) instead of asking the daemon (0, the default). |
dispatch.max_timeout_s |
86400 s |
AH_ENGINE_DISPATCH_MAX_TIMEOUT_S |
dispatch.toml |
An upper bound on any Node hook's timeout, whatever its hooks.json entry says (the default is above every real timeout, so it changes nothing; tests lower it to exercise a hook that never exits). |
daemon.workers |
4 |
AH_ENGINE_WORKERS |
engine.toml |
Worker threads evaluating requests. |
daemon.queue |
16 |
AH_ENGINE_QUEUE |
engine.toml |
Connections that may wait for a worker; beyond this the daemon answers BUSY and the client falls back. |
daemon.max_request |
1048576 bytes |
AH_ENGINE_MAX_REQUEST |
engine.toml |
Largest request the daemon reads; the client sends nothing larger (it falls back instead). |
daemon.read_ms |
1000 ms |
AH_ENGINE_READ_MS |
engine.toml |
Total time a client has to deliver its request. |
daemon.write_ms |
1000 ms |
AH_ENGINE_WRITE_MS |
engine.toml |
Time allowed to write a reply. |
daemon.eval_budget_us |
200000 us |
AH_ENGINE_EVAL_BUDGET_US |
engine.toml |
Per-request thread CPU budget for rule evaluation; 0 turns the budget off. |
daemon.mem_mb |
512 MB |
AH_ENGINE_MEM_MB |
engine.toml |
Data-segment limit applied with setrlimit; 0 = none. It is a ceiling against runaway allocation, not a budget (the RSS cap is the budget), and Linux enforces it on thread stacks, so it must exceed daemon.workers times git.stack_mb plus headroom or a check thread cannot start. macOS accepts the call but does not enforce it. |
daemon.rss_cap_kb |
131072 KB |
AH_ENGINE_RSS_CAP_KB |
engine.toml |
Resident-set cap; above it the daemon drains and exits cleanly and the next call starts a fresh one; 0 = none. Set from measurement, not guessed (DECISIONS.md 1.111, 2026-10-09): on the real-payload replay (2,113 recorded hook calls, unpaced, isolated home) the engine with 16 scripted checks sits at 69 MB after one pass and 78 MB after three (jemalloc, aarch64-apple-darwin), and at 102 MB after one pass with the system allocator (the x86_64-apple-darwin and musl builds, measured as a system-allocator build on this Mac); the live heap is 45-52 MB. The engine before the scripted checks (live4) measured 62 and 68 MB on the same replay, so the previous 64 MB cap (set from a synthetic soak with a 16-18 MB live heap) sat inside the steady state of both: the daemon restarted 4 times in 600 s and the crash-loop breaker sent every hook to Node. The cap sits above the larger figure with room for the slow growth both builds show on repeated passes (about 1.5 MB per 1,000 calls), so only real growth trips it. |
daemon.rss_check_ms |
10000 ms |
AH_ENGINE_RSS_CHECK_MS |
engine.toml |
How often the watchdog samples resident memory. |
daemon.stuck_ms |
8000 ms |
AH_ENGINE_STUCK_MS |
engine.toml |
A worker busy on one request for longer than this trips a drain and exit. |
daemon.stall_ms |
5000 ms |
AH_ENGINE_STALL_MS |
engine.toml |
An accept loop silent for longer than this trips a drain and exit. |
daemon.watchdog_tick_ms |
250 ms |
AH_ENGINE_WATCHDOG_TICK_MS |
engine.toml |
How often the watchdog thread wakes to look at heartbeats. |
daemon.nice |
5 |
AH_ENGINE_NICE |
engine.toml |
nice increment applied to the daemon process. |
daemon.session_rps |
50 |
AH_ENGINE_SESSION_RPS |
engine.toml |
Sustained requests per second allowed per session; 0 = unlimited. |
daemon.session_burst |
200 |
AH_ENGINE_SESSION_BURST |
engine.toml |
Token-bucket burst per session. |
daemon.project_rps |
100 |
AH_ENGINE_PROJECT_RPS |
engine.toml |
Sustained requests per second allowed per project; 0 = unlimited. |
daemon.project_burst |
400 |
AH_ENGINE_PROJECT_BURST |
engine.toml |
Token-bucket burst per project. |
daemon.idle_exit_s |
0 s |
AH_ENGINE_IDLE_EXIT_S |
engine.toml |
Seconds without any request after which the daemon exits (the next hook call starts a fresh one, and the scheduler catches up its missed jobs); 0 keeps it resident. 0 by default: the daemon is always resident so the scheduler and mailbox keep running after every session closes (D7). A daemon whose state dir, lock file or executable is gone still exits on its own (daemon.orphan_check_ms). |
client.deadline_ms |
2000 ms |
AH_ENGINE_DEADLINE_MS |
engine.toml |
Overall deadline for one engine exchange (connect, write, read); a hard watchdog thread enforces it. |
client.breaker_n |
5 |
AH_ENGINE_BREAKER_N |
engine.toml |
Engine failures within the window that open the breaker (the client then skips the engine). |
client.breaker_window_s |
60 s |
AH_ENGINE_BREAKER_WINDOW_S |
engine.toml |
Window in which breaker failures are counted. |
client.breaker_cooldown_s |
60 s |
AH_ENGINE_BREAKER_COOLDOWN_S |
engine.toml |
How long the breaker stays open once tripped. |
client.crash_n |
4 |
AH_ENGINE_CRASH_N |
engine.toml |
Daemon deaths within the window that stop respawning. |
client.crash_window_s |
600 s |
AH_ENGINE_CRASH_WINDOW_S |
engine.toml |
Window in which daemon deaths are counted. |
client.crash_cooldown_s |
1800 s |
AH_ENGINE_CRASH_COOLDOWN_S |
engine.toml |
How long respawning stays stopped after a crash loop. |
client.fallback_ms |
8000 ms |
AH_ENGINE_FALLBACK_MS |
engine.toml |
The Node fallback hook must finish, and its stdout and stderr must reach EOF, within this long. A hook still running at the deadline is killed (then plain allow, since it is unavailable); one that finished with output still unread is an error outcome, never an empty stdout. |
schedule.gh_poll_ms |
20000 ms |
AH_ENGINE_GH_POLL_MS |
github_rt.toml |
Interval of the gh_poll job, the tick that decides which repos are due (the cadences below decide how often a repo is really polled); 0 turns GitHub realtime off. |
schedule.jev_sweep_ms |
900000 ms |
AH_ENGINE_JEV_SWEEP_MS |
jev_sweep.toml |
Interval of the jev_sweep job; 0 turns it off. |
ops.shadow_rate_statusline |
50 |
AH_ENGINE_SHADOW_RATE_STATUSLINE |
operator.toml |
How many runs in a thousand of the statusline command are also run by the Node version in the background and compared (0 turns the shadow off). The engine result is always the real one. |
ops.shadow_rate_settings |
1000 |
AH_ENGINE_SHADOW_RATE_SETTINGS |
operator.toml |
How many runs in a thousand of the settings command are also run by the Node version in the background and compared (0 turns the shadow off). The engine result is always the real one. |
ops.shadow_rate_defect |
1000 |
AH_ENGINE_SHADOW_RATE_DEFECT |
operator.toml |
How many runs in a thousand of the defect command are also run by the Node version in the background and compared (0 turns the shadow off). The engine result is always the real one. |
realtime.poll_ms |
750 ms |
AH_ENGINE_RT_POLL_MS |
realtime.toml |
How often a polled directory is listed and its watched files stat'ed, in milliseconds. Only directories that cannot use OS events are polled (9p, drvfs, NFS, SMB, FUSE, a directory that does not exist yet, backend = poll), so this is the detection latency of those: at most this plus debounce_ms. Polling costs CPU in proportion to the files watched divided by this interval. |
realtime.debounce_ms |
100 ms |
AH_ENGINE_RT_DEBOUNCE_MS |
realtime.toml |
A changed file is reported once it has been quiet this long, so a burst of writes becomes one report. |
realtime.max_delay_ms |
1000 ms |
AH_ENGINE_RT_MAX_DELAY_MS |
realtime.toml |
A file that keeps changing is still reported at least this often (the ceiling on debounce_ms), so a busy source is never starved. |
schedule.tick_ms |
1000 ms |
AH_ENGINE_TICK_MS |
schedules.toml |
Longest the ticker sleeps between checks; it wakes earlier when a job is due sooner or schedule run asks. |
schedule.maintain_ms |
86400000 ms |
AH_ENGINE_MAINTAIN_MS |
schedules.toml |
Interval of the maintain job (D26); 0 turns it off. |
schedule.handovers_ms |
600000 ms |
AH_ENGINE_HANDOVERS_MS |
schedules.toml |
Interval of the handovers job; 0 turns it off. |
schedule.backup_ms |
0 ms |
AH_ENGINE_BACKUP_MS |
schedules.toml |
Interval of the backup job (D27); 0 (the default) turns it off. |
schedule.telemetry_rollup_ms |
86400000 ms |
AH_ENGINE_TELEMETRY_ROLLUP_MS |
schedules.toml |
Interval of the telemetry rollup job (D78); 0 turns it off. |
schedule.procwatch_ms |
30000 ms |
AH_ENGINE_PROCWATCH_MS |
schedules.toml |
Interval of the process watch job (resource sampling; orphan scans run every procwatch.scan_every_s); 0 turns it off. |
script.enabled |
1 |
AH_ENGINE_SCRIPT |
script.toml |
1: a check whose script exists runs the script instead of its compiled port; 0: the compiled port always runs (the parity baseline). |
script.time_limit_ms |
50 ms |
AH_ENGINE_SCRIPT_TIME_MS |
script.toml |
CPU-time limit of one script call (the interpreter thread's own CPU time, see script.wall_limit_factor for the wall-clock backstop); past it the interpreter is interrupted and the call defers to Node (never a silent allow). |
script.exec_timeout_scale |
1 |
AH_ENGINE_SCRIPT_EXEC_SCALE |
script.toml |
Multiplier on the time every ah.exec run and every process listing a script asks for may take (the limit a script asks for, and the longest one, are both multiplied). 1 in production; a test build on a loaded machine raises it so a slow child process is not read as a failure. |
session.gitignore_probe_ms |
1000 ms |
AH_ENGINE_GITIGNORE_PROBE_MS |
session.toml |
The longest the engine waits for the gitignore probe before it hands the hook to Node. Node waits 3000 ms, but the client gives up on the engine after client.deadline_ms, so a longer wait here could answer after the client has already left; a test keeps the shipped value below that deadline. |
ops.shadow_rate_phase |
1000 |
AH_ENGINE_SHADOW_RATE_PHASE |
slcfg.toml |
How many runs in a thousand of the phase command are also run by the Node version in the background and compared (0 turns the shadow off). The engine result is always the real one. |
ops.shadow_rate_install |
1000 |
AH_ENGINE_SHADOW_RATE_INSTALL |
slcfg.toml |
How many runs in a thousand of install-statusline are also run by the Node installer in the background, on a scratch copy of the settings (never a second real write), and compared (0 turns the shadow off). |
ops.shadow_rate_uninstall |
1000 |
AH_ENGINE_SHADOW_RATE_UNINSTALL |
slcfg.toml |
How many runs in a thousand of uninstall-statusline are also run by the Node uninstaller in the background, on a scratch copy of the settings, and compared (0 turns the shadow off). |
storage.fullfsync |
0 |
AH_ENGINE_FULLFSYNC |
storage.toml |
1 makes SQLite use F_FULLFSYNC on macOS, which also survives power loss at a large cost in commit rate (D73: 479 against 43k commits per second measured); 0 keeps the plain fsync. Off until metrics decide. |
storage.write_queue |
1024 |
AH_ENGINE_WRITE_QUEUE |
storage.toml |
Writes that may wait for the writer thread; beyond this a write is refused as busy (the client retries, then spools). |
storage.group_commit_ms |
0 ms |
AH_ENGINE_GROUP_COMMIT_MS |
storage.toml |
Group-commit window: after taking a write, the writer waits up to this long for more before committing them together (D23); 0 commits at once with whatever is already queued, which still groups writes that arrive during a commit. |
storage.ack_timeout_ms |
1000 ms |
AH_ENGINE_ACK_TIMEOUT_MS |
storage.toml |
How long a request waits for its write to commit before it is answered with an error (the client then retries or spools; the write id makes a late commit harmless); kept below client.ctl_timeout_ms so the client hears the error. |
tier.budget_kb |
2048 KB |
AH_ENGINE_TIER_BUDGET_KB |
storage.toml |
Memory budget of the active key-value items; past it the least recently used item is dropped from memory (SQLite keeps it). |
spool.retries |
4 |
AH_ENGINE_SPOOL_RETRIES |
storage.toml |
Retries of a project write before it is spooled (the first attempt is not counted). |
spool.backoff_ms |
20 ms |
AH_ENGINE_SPOOL_BACKOFF_MS |
storage.toml |
First retry delay; each retry doubles it, up to backoff_max_ms, with random jitter of up to half the delay. |
spool.max_bytes |
16777216 bytes |
AH_ENGINE_SPOOL_MAX_BYTES |
storage.toml |
Largest spool; a write that would grow it past this is refused instead of spooled, so the client learns it was not kept. |
spool.drain_ms |
1000 ms |
AH_ENGINE_SPOOL_DRAIN_MS |
storage.toml |
Interval of the scheduled spool drain job (it also drains on start and before each project write). |
telemetry.max_events |
5000 |
AH_ENGINE_MAX_EVENTS |
telemetry.toml |
Most impact events kept in memory (oldest dropped first); counts per kind are kept exactly in separate counters. |
telemetry.snapshot_ms |
60000 ms |
AH_ENGINE_SNAPSHOT_MS |
telemetry.toml |
Interval of the scheduled metrics snapshot job, which keeps the counters in hot.db and their rollups in archive.db (D51); the daemon also keeps one when it exits. |
telemetry.flush_ms |
10000 ms |
AH_ENGINE_TELEMETRY_FLUSH_MS |
telemetry.toml |
How often the recorder's counters and events are stored in hot.db, and at shutdown. A kill -9 loses at most this much (the data recorded since the last flush). |
telemetry.health_snapshot_ms |
60000 ms |
AH_ENGINE_HEALTH_SNAPSHOT_MS |
telemetry.toml |
How often the daemon records a daemon health snapshot event (resident set, its cap, restarts, degraded flag, queue and worker load, saturation). Events are kept for retention_days and capped by max_event_rows. |