agenthropic

Configuration reference

How to read this page. The configuration surface is built; the reference table below is the environment the server actually reads. The page began life as a design target derived from the design basis (docs/ai/DESIGN.md) and the build plan (docs/analysis/development-plan.md), written before any code existed, and that older narrative is kept underneath as the record — so a row marked (planned) or (leaning — unconfirmed) describes what the design left open at the time, not what is open now. Each such row carries an As built note saying how it resolved. The alerting slice (webhook targets, alert rules, the Telegram token_ref) was never built and is v2.0 material. The security invariants were binding then and remain binding now.

Update — 2026-07 (as built; revised 2026-09). The configuration surface is built and is smaller and more decided than this page assumes. Verified against apps/server/src/config.ts:

Design-era prose is kept below with As built notes where the shipped code settled a question this page left open.

This page is the complete reference for every configuration and environment option the design basis and build plan have fixed for agenthropic, as a table plus per-option detail. The key takeaway: two values are non-negotiable and fixed today — the loopback bind (always 127.0.0.1, never configurable to anything else) and the DASHBOARD_TOKEN (mandatory, timingSafeEqual-checked, fail-startup-when-unset) — and everything else on this page (the listen port, the SQLite path, the backup directory, the exact config-file format) is either a fixed requirement with an illustrative shape or explicitly (planned), because WP-U0’s config loader is designed but its concrete file format is not yet decided. Nothing below should be read as a value you can put in a .env file today — this documents what the loader will accept once WP-U0 lands, so Phase 1 implements exactly this and nothing weaker. (As built: WP-U0 landed. The environment variables in the next section are real and usable now — though there is no .env file support; they must be actual environment variables.)

Reference table

As built — the environment the server reads today. There is no config file and no other variable; anything not in this table cannot be configured. The first eleven rows are what loadConfig parses; DASHBOARD_INSTANCE is read separately, by the corpus identity resolver.

Variable Required? Default What it controls Validation
DASHBOARD_TOKEN Mandatory none — the process throws at startup if unset Bearer token for every /api/* route and the SSE stream; hashed to SHA-256 then compared with timingSafeEqual Must be present and ≥ 16 characters; shorter is a startup error naming the actual length
DASHBOARD_PORT optional 4317 TCP port on the loopback bind Integer 0–65535; anything else is a startup error
DASHBOARD_DB_PATH optional data/agenthropic.db Path to the WAL-mode SQLite file Free-form path
DASHBOARD_INGEST optional on Master switch for corpus ingest. Tests boot with 0 so they never touch the real corpus 1/true/0/false only; anything else is a startup error
CLAUDE_PROJECTS_DIR optional unset → the canonical ~/.claude/projects Corpus root override — where session transcripts are read from Empty string counts as unset, so a stray CLAUDE_PROJECTS_DIR= cannot silently mean the working directory
DASHBOARD_POLL_INTERVAL_MS optional 3000 — PROVISIONAL (LABEL-ME), not ratified Tail-follow poll cadence (WP-IN5) Positive integer
DASHBOARD_WATCHDOG_MINUTES optional 10 — PROVISIONAL (LABEL-ME), not ratified Inactivity window after which the missing-Stop watchdog marks an agent unknown (WP-IN12) Positive integer
DASHBOARD_RETENTION_EVENTS_DAYS optional 90 — signed v1.0 policy (D3, 2026-09-08) Age window for normalized events rows; the daily retention pass prunes older rows after a successful backup (WP-D10) Non-negative integer up to 36500 (100 years); 0 disables the rule; empty counts as unset
DASHBOARD_RETENTION_BACKUP_DAYS optional 30 — signed v1.0 policy Age window for backup files in <dirname(DASHBOARD_DB_PATH)>/backups Non-negative integer up to 36500 (100 years); 0 disables the rule; empty counts as unset
DASHBOARD_RETENTION_BACKUP_KEEP_MIN optional 7 — signed v1.0 policy Keep-minimum floor for backup files: the newest N survive whatever the window says; the floor wins over the age Positive integer (never below 1)
DASHBOARD_WEB_ROOT optional apps/web/dist, resolved from the server module’s own location (import.meta.url), not from the working directory Directory of the built SPA that the single-port server serves alongside /api; when the bundle is absent every non-/api path answers 503 with a “not built yet, run pnpm start” message Free-form path; empty string counts as unset
DASHBOARD_INSTANCE optional the machine’s hostname The instance label stamped on every persisted agent and orchestration edge Free-form string; unset falls back to hostname()

What “integer” means in those rows (parsing hardened 2026-09). Every numeric variable goes through one helper, parseDigits: plain decimal digits (/^\d+$/) that also survive Number.isSafeInteger. Anything else is a startup error rather than a coercion, because Number() alone is far too lenient for configuration - it reads whitespace as 0 and accepts hex, exponent, signed and decimal-point spellings. So DASHBOARD_PORT=" 80", 0x1F, +80, 4e3 and 80.0 all fail loudly instead of quietly becoming a port. DASHBOARD_POLL_INTERVAL_MS carries one extra bound: it is capped at 2147483647, Node’s timer ceiling, because a larger setInterval delay is silently clamped to 1 ms - a value meant as “poll once a month” would otherwise spin. DASHBOARD_WATCHDOG_MINUTES has no ceiling beyond the safe-integer range. And DASHBOARD_DB_PATH= (set but empty) counts as unset, the same house rule CLAUDE_PROJECTS_DIR and DASHBOARD_WEB_ROOT follow: better-sqlite3 opens '' as an anonymous temporary database, so the empty value would discard every row at exit and put the backups under a working-directory-relative path.

There is no listen-host variable: the bind is the exported constant HOST = '127.0.0.1' with no configuration path at all.

Why DASHBOARD_INSTANCE matters more than its size suggests. Every agent and every edge is written with both a hostId (always the OS hostname) and an instance. Because instance is never null, the global DAG is queryable per instance — which is what lets one database hold work from more than one logical dashboard on the same machine and still keep the graphs apart. Left unset it equals the hostname, and the distinction simply collapses to “this machine”, which is the right default for a single-instance install.

The design-era table follows, kept as the record. Every (planned) row in it is now resolved or withdrawn; see the As built column.

Option Required? Default What it controls Source As built
DASHBOARD_TOKEN Mandatory none — server refuses to start if unset Bearer token, compared with timingSafeEqual, gating every read endpoint, every write endpoint, and the SSE stream WP-U0, WP-F7; DESIGN §8 Holds, plus a 16-character minimum the design did not state
Listen host Fixed 127.0.0.1 — 0.0.0.0 is never an accepted value, not even behind a flag The bind address for every listener the server opens (read API, hook-ingest receiver, /api/stream) WP-U0, WP-F7; DESIGN §8 Holds — a constant, not a defaulted option
Listen port (planned — default undecided) (planned) TCP port the Fastify server listens on, loopback-side only WP-U0 (stack itself leaning, unconfirmed per the project CLAUDE.md) Decided: DASHBOARD_PORT, default 4317
~/.claude/projects path Fixed conventional location; an override mechanism, if any, is (planned) the standard Claude Code transcript directory Ground-truth source of session/agent/token-usage JSONL, read by the TokenReader/TokenSource port CD-6 (concept-analysis-v2.md); WP-IN5 An override does exist: CLAUDE_PROJECTS_DIR
SQLite database path (planned — exact path undecided) (planned) Location of the single WAL-mode SQLite file (+ its -wal/-shm siblings) WP-D2; data model Decided: DASHBOARD_DB_PATH, default data/agenthropic.db
WAL mode / foreign_keys Fixed, not a toggle asserted ON on every connection open Journal mode and FK enforcement pragma-checked at connect time, not merely configured once WP-D2; DESIGN §8 Holds — both pragmas are set and read back on every open, and a mismatch throws rather than warns
Backup directory (planned) (planned) Where WP-F8’s online-backup artifacts (agenthropic-<ts>.db) land WP-F8; backup & restore §2 Derived, not configured: <dirname(DASHBOARD_DB_PATH)>/backups. The naming convention shipped as designed
Backup retention window (planned — no default days fixed) (planned) How long backup files are kept before the pruning step deletes them WP-D10; backup & restore §4 Decided (D3, 2026-09-08): 30 days behind a floor of 7, as the defaults of DASHBOARD_RETENTION_BACKUP_DAYS / DASHBOARD_RETENTION_BACKUP_KEEP_MIN. Row-level retention is signed too: events at 90 days (DASHBOARD_RETENTION_EVENTS_DAYS), token_usage never
model_pricing source Fixed requirement; seed content/refresh mechanism (planned) seeded, versioned, dated (effective_from, verified_on) Per-token rates keyed by model × service_tier × speed × inference_geo, driving every dollar figure shown WP-C1; DESIGN §4; data model Table exists and is seeded by migrations 7/11 (five models, cache buckets derived) and 18 (claude-opus-5, claude-fable-5-1, all five buckets explicit, 2026-09-10), keyed (model, bucket, effective_from). There is no verified_on column, and the seeded rates are marked as awaiting ratification. There is no refresh mechanism and no configuration for one
Alert rules (alert_rules) Operator-configured none by default Cost-threshold, stuck-agent, and error trigger conditions WP-A2, WP-A5; Phase 5, roadmap Does not exist — v2.0, KC-5-gated
Webhook targets (webhook_targets) Operator-configured none by default Outbound delivery destinations (Telegram today); dialed only from this operator-set table, never from an event payload WP-A2, WP-A4; Telegram alerts Does not exist — v2.0, KC-5-gated
Telegram bot token Mandatory when Telegram alerting is enabled none — held by reference only The @baev_bot_bot bot token, resolved through token_ref (launchd env or a chmod 600 dotfile), never a raw column value WP-A3, WP-A6; CD-10 Does not exist — no token_ref resolver, no Telegram code path, nothing to enable

Every non-fixed row above is either (planned) — a shape the design commits to but without a literal default yet — or (leaning — unconfirmed), tied directly to the still-open stack decision (the project CLAUDE.md: pnpm monorepo, Fastify, better-sqlite3, React/Vite/D3 are leaning, not confirmed). Nothing in this table is invented to fill the gap. (As built: the stack is no longer leaning — pnpm monorepo, Fastify + TypeBox, better-sqlite3 and React/Vite are what shipped.)

How configuration is supplied

WP-U0’s Definition-of-Done names a config loader explicitly, alongside the loopback-or-fail bind, the timing-safe token middleware, the same-origin helper, and TypeBox plugin registration — so a config-loading layer is a fixed part of the server bootstrap, not a later addition. What is not fixed by any source document is the loader’s literal shape: whether it reads only process environment variables, layers a config file (JSON/YAML/.env) underneath the environment, or does both with a defined precedence. Treat the following as the designed intent, marked (planned) where the concrete mechanism is still open:

As built: the loader reads process environment variables only. No config file of any format is read, so the precedence question below never had to be answered — it is not an open decision, it is a path not taken. launchd’s EnvironmentVariables dictionary and a chmod 600 dotfile that exports variables into the process both still work, because both end up as environment variables; but the server itself knows nothing about either mechanism. The token_ref resolver was never built (nothing needs it — see the alerting note above).

Operator sets values (DASHBOARD_TOKEN, TELEGRAM token_ref, …)
                │
                ▼
   ┌────────────────────────────┐        ┌────────────────────────────────┐
   │  launchd env (preferred)    │  or    │  chmod 600 dotfile              │
   │  EnvironmentVariables dict  │        │  (>0600 → rejected, WP-A3 gate) │
   └──────────────┬───────────────────────────────────┬────────────────────┘
                  │                                    │
                  ▼                                    ▼
              config loader (WP-U0) / token_ref resolver (WP-A3)
                                  │
                                  ▼
              held in server process memory only for the process lifetime
              never written to SQLite · never sent over SSE · never logged

Security-critical options, in depth

DASHBOARD_TOKEN — mandatory, timing-safe, fail-startup-when-unset

Every endpoint the server exposes — read, write, and the SSE stream — sits behind this token, compared with Node’s crypto.timingSafeEqual, never a naive === string compare (which leaks timing information about how many leading bytes matched). If the environment variable is unset, the server refuses to start rather than falling back to “no auth needed” — there is no opt-in/opt-out toggle. This directly corrects hoangsonww’s documented mistake: its DASHBOARD_TOKEN is opt-in and becomes a silent no-op when unset, so a deployment that forgets to set it has no auth at all despite shipping an auth feature (DESIGN §8; security model rule 2).

WP-F7 builds the timingSafeEqual primitive as a unit-tested, initially-failing contract test; WP-U0’s Fastify bootstrap wires it in and is done-when that contract test turns green, explicitly including “fails startup when token unset.” Sample env file — always a placeholder, never a real value:

DASHBOARD_TOKEN=<token>

As built: all of this holds, with two additions the design did not state. The token must be at least 16 characters — a shorter value is rejected at startup with an error naming its length, so a one-character “token” is not a valid deployment. And the comparison hashes both sides to SHA-256 digests before timingSafeEqual, so differing input lengths leak nothing through timing and cannot trip timingSafeEqual’s equal-length precondition. The token is held in a closure, never persisted, and the request-log serializer strips it from the SSE ?token= URL before any log line is written.

Listen host — fixed at 127.0.0.1, never 0.0.0.0

This is not a default that can be overridden — it is a fixed, loopback-or-fail bind. 0.0.0.0 is never an accepted value, not behind a flag, not for convenience during development. Three of the six audited rival dashboards (simple10, cast, claude-code-templates) bind 0.0.0.0 by default and are LAN- or network-reachable the moment the process starts regardless of what their auth layer does (security model rule 1; DESIGN §8). WP-U0 implements the loopback-or-fail listen call; WP-F7’s contract tests assert the process refuses to start bound to anything else, and the Phase 1 exit gate requires those tests green.

ANTI-PATTERN — never a real config value in this codebase, not even as an example:
  HOST=0.0.0.0

If you need to reach the dashboard from off the host, that is a tunnel problem, not a bind-address problem — see remote access: SSH port-forward or Tailscale only, terminating at 127.0.0.1, never a reverse proxy to a widened bind.

token_ref — secrets held by reference, never by value

Update — 2026-07 (as built): nothing in this section exists. There is no webhook_targets table, no token_ref column, no resolver, and no permissions gate, because there is no outbound secret to hold — Telegram delivery and the whole alerting slice are v2.0 material behind KC-5. The invariant below is recorded as a design commitment that binds whoever eventually builds alerting, not as a mechanism you can configure today.

The Telegram bot token (and any future webhook credential) is never stored as a raw value anywhere the server writes to disk or sends over the wire. WP-A3 owns a token_ref resolver: the webhook_targets table stores a reference string, and the resolver looks up the actual secret at delivery time from one of two operator-controlled locations — launchd environment variables, or a file whose permissions are chmod 600 and no wider. A static gate rejects any secret-holding dotfile with permissions looser than 0600 as a build-failing condition, not a review-time reminder. CD-10 states the invariant directly: the token is held “via token_ref → launchd env / chmod-600 (never in SQLite, never to the browser).” See Telegram alerts for the delivery side of this, and data model’s webhook_targets.token_ref column for the storage side — that column is a reference string, and no column in the alerting schema ever holds the actual bot token.

The ground-truth data source: ~/.claude/projects

agenthropic’s token counts are ground truth, read verbatim from ~/.claude/projects/*.jsonl — never inferred or estimated. This path is the standard location Claude Code itself writes session transcripts to, and it is the source the TokenReader/TokenSource port (CD-6) and the JSONL tail-follower (WP-IN5) read from. No source document names an environment variable or config key for overriding this location; whether one exists is (planned). Treat the conventional path as fixed today and any override mechanism as undecided.

As built: an override exists and is named CLAUDE_PROJECTS_DIR. Unset (or set to the empty string, which is deliberately treated as unset so a stray CLAUDE_PROJECTS_DIR= cannot resolve to the working directory) means the canonical ~/.claude/projects. Corpus reading is additionally governed by DASHBOARD_INGEST — set it to 0 or false and the watcher never opens the corpus at all, which is how the test suite guarantees it cannot touch real transcripts. The tail-follow cadence is DASHBOARD_POLL_INTERVAL_MS (default 3000, PROVISIONAL).

Storage: SQLite path and WAL mode

The single persisted store is SQLite, and it always runs in WAL journaling mode with foreign_keys enforcement — both are pragma-asserted on every connection open by WP-D2, not configured once and trusted to stay set. This is a structural requirement (the ingest side writes continuously while the read side reads concurrently for live views), not a tuning knob an operator can turn off. The exact filesystem path the database lives at is (planned) — no source document fixes it; see data model for the schema this file holds and backup & restore §1 for why WAL specifically.

As built: the path is DASHBOARD_DB_PATH, default data/agenthropic.db; the parent directory is created on open. The pragma discipline is stronger than “configured once”: both pragmas are set and then read back, and a connection that does not report WAL journaling or enforced foreign keys throws and refuses to be used.

-- Asserted on every connection open (WP-D2), not merely configured once:
PRAGMA journal_mode = WAL;
PRAGMA foreign_keys = ON;

Backup directory and retention

WP-F8 owns the backup routine (online-backup, safe against a live WAL database) and its tested-restore proof; WP-D10 owns the retention TTL sweeper and payload redaction at the ingest boundary. Both the backup directory path and the retention window (in days) are (planned) — no source document fixes either number. What is fixed is the requirement itself: a backup exists, its restore path is actually exercised (not assumed to work because a file exists), and CD-10 requires retention TTL and payload redaction live from Phase 1, not deferred as later cleanup. Full detail, including the tested-restore drill and the reference launchd scheduling shape: backup & restore.

As built: this section splits three ways.

The daily backup job

The server schedules its own backups. At startup the composition root calls scheduleDailyBackups(db, <dirname(DASHBOARD_DB_PATH)>/backups, retention), and each pass writes agenthropic-<timestamp>.db into that directory and then — only if that write succeeded — runs the retention pass described in the next section, which prunes expired events rows and expires old backup files. The backup directory is derived from the database path; the cadence is compiled in; the windows are settings:

Setting Value Meaning
BACKUP_INTERVAL_MS (constant) 24 hours How often a pass fires.
DASHBOARD_RETENTION_BACKUP_DAYS 30 (0 disables) Backups older than this are eligible for deletion.
DASHBOARD_RETENTION_BACKUP_KEEP_MIN 7 Safety floor: the newest seven always survive, whatever the window says.

The floor is the part that is not really a policy number: however wrong the window turns out to be, a retention pass that could leave zero backups would be a data-loss mechanism rather than a retention one, so the floor cannot be configured below 1 and wins over the age window.

The reason this exists at all is recorded in the source as review M-20: events_raw is the one table that cannot be re-derived from the corpus, and a backup capability that nothing ever runs is not a backup. The timer is unref-ed, so it never keeps a process alive past server close, and a failed backup logs, skips that cycle’s retention pass and waits for the next tick — the server outliving its backup schedule is the better failure of the two, and nothing is ever pruned in a cycle whose backup did not land. An overlapping manual run is skipped rather than allowed to write into the directory concurrently.

Retention: the signed v1.0 policy

The retention module (apps/server/src/retention/) is implemented, tested and — since the policy was signed on 2026-09-08 (decision D3) — wired. loadConfig reads three variables for it:

Variable Default Validation Purpose
DASHBOARD_RETENTION_EVENTS_DAYS 90 non-negative integer up to 36500; 0 disables Age window for normalized events rows. Older rows are pruned by the daily pass.
DASHBOARD_RETENTION_BACKUP_DAYS 30 non-negative integer up to 36500; 0 disables Age window for backup files in <dirname(DASHBOARD_DB_PATH)>/backups.
DASHBOARD_RETENTION_BACKUP_KEEP_MIN 7 positive integer (never below 1) Keep-minimum floor for backup files; the floor wins over the age window.

There is deliberately no variable for token_usage: those rows are the ground truth behind every reported dollar and are never pruned in v1.0. The wired policy has no input for such a window, a test proves it cannot name that table whatever the events window is, and loadConfig refuses to start if DASHBOARD_RETENTION_TOKEN_USAGE_DAYS is set to anything but the empty string, so a stale value cannot be mistaken for a working knob. The remaining DASHBOARD_RETENTION_* names the module knows (_TOKEN_USAGE_ACK_COST_LOSS, _BACKUP_DIR, _MAX_ROWS_PER_RUN, _RAW_EVENTS) are read only by the library loader loadRetentionPolicy, which the running server does not call — setting them changes nothing. The backup directory is always derived from DASHBOARD_DB_PATH, the per-run row budget is the module default (10 000), and events_raw is always kept forever.

How it runs. start() builds the policy with signedRetentionPolicy, logs it in words together with a dry run (retention policy: ... followed by retention dry run (nothing deleted): ... — counts only; boot never deletes), and hands the runner to the daily backup timer. Each tick writes the backup first and runs the retention pass only if that write succeeded; the pass logs one retention: ... line, a failure is logged as retention failed: <message> and never crashes the server, and every run that actually deleted rows writes its receipt through the append-only journal next to the database (<DASHBOARD_DB_PATH>.retention-journal.jsonl) inside the delete transaction. /api/health does not report retention state; the log lines are the signal. The full operational picture, including the log-line grammar: backup & restore §4.

Three properties of the mechanism still hold under the signed policy:

Cost engine: model_pricing source

WP-C1 owns the versioned model_pricing table and its authoritative, dated seed — DESIGN §4 names it as the table that drives the dollar-cost and delegation-savings tiles, and CD-4 fixes the versioning columns (effective_from, verified_on) literally. The seed itself must be pricing that is genuinely dated and verified, not a hardcoded snapshot quietly going stale: WP-C6’s staleness gate fails CI outright if the golden fixture corpus contains a model+bucket combination with no matching priced row — a cost can never silently compute to zero for an unpriced model. The refresh cadence for keeping this table current after ship is (planned). See data model model_pricing and cost model for dated-price resolution and delegation-savings mechanics.

As built: the table exists, seeded by a migration, keyed (model, bucket, effective_from) so several dated rows can coexist per model+bucket. Three corrections to the paragraph above:

The one widening that has happened so far was a migration, not a mechanism. A boot over the owner’s real corpus on 2026-09-09 found 52 of 60 sessions refused at the gate for claude-opus-5 and claude-fable-5-1; migration 18 (2026-09-10) ships official five-bucket rows for both at the seed’s floor, and because the corpus watcher re-reads model_pricing on every pass, sessions parked for want of a row are re-admitted on the next tick without a restart. A boot on 2026-09-18 at schema 18 confirmed it on the same machine: all 54 sessions the corpus held that day were admitted, sessionsExcluded 0. It happened again on 2026-09-26: the corpus had moved to claude-opus-5-5, a boot at schema 18 refused 27 of 61 sessions, and migration 19 ships that model’s five rows. A missing row is cured by a row, never by relaxing the gate.

Alert rules and webhook targets

Update — 2026-07 (as built): none of this exists. There is no alert_rules table, no webhook_targets table, no dispatcher and no operator alerts API — WP-A8/WP-A9 were cut outright. Alerting is v2.0, entered only through kill checkpoint KC-5, which demands evidence of sustained real v1.0 use before a line of it is written; if that evidence never appears, none of it is ever built and the roadmap counts that as a success. The SSRF invariant stated below remains binding on whoever eventually builds it, but there is nothing to configure today. See Telegram alerts for the full gating story.

alert_rules and webhook_targets are entirely operator-configured — there is no default rule and no default target. An operator creates a rule (cost_threshold, stuck_agent, or error, per WP-A5) and a delivery target (Telegram today, per WP-A2) through the authenticated operator alerts API that WP-A8 builds in Phase 6. The webhook dispatcher (WP-A4) only ever calls a target row from this operator-configured table — it never constructs a delivery URL from data that arrived inside an ingested event, closing the SSRF path disler’s dispatcher left open (DESIGN §8; security model rule 6). Configuring the Telegram relay itself — the bot token’s token_ref, the rule kinds, and the throttled delivery behavior — is the dedicated subject of Telegram alerts, which ships with Phase 5 per the roadmap, after the read API and dashboard (Phase 4).

Security defaults

Every default on this page, where one exists at all, resolves toward the more restrictive option, never the more permissive one:

The full nine-rule catalogue — rule → why → how each is enforced, with the CI gate backing every one — lives in the security model; this page only restates the subset that is directly configuration-shaped.

What’s decided vs. open

Aspect Status As built
DASHBOARD_TOKEN mandatory, timingSafeEqual, fail-startup-when-unset Fixed (DESIGN §8, WP-U0, WP-F7) Holds, plus a 16-character minimum
Listen host 127.0.0.1, 0.0.0.0 never accepted Fixed (DESIGN §8, WP-U0, WP-F7) Holds — a constant with no configuration path
WAL mode + foreign_keys asserted on connect Fixed (WP-D2, DESIGN §8) Holds — set and read back; a mismatch throws
Secret handling via token_ref (launchd env / chmod 600, >0600 rejected) Fixed mechanism (WP-A3, CD-10) Not built — no secret exists to hold; binding only on a future v2.0 alerting build
Listen port default Planned — undecided Decided: DASHBOARD_PORT, default 4317
Config loader’s env-vs-file precedence Planned — WP-U0 names a “config loader,” format/precedence undecided Moot — environment variables only; no file is read
SQLite database path Planned — undecided Decided: DASHBOARD_DB_PATH, default data/agenthropic.db
Backup directory + retention window (days) Planned — requirement fixed (CD-10, WP-F8/WP-D10), numbers undecided Backups run daily into <dirname(DASHBOARD_DB_PATH)>/backups. Retention is signed (D3, 2026-09-08) and wired after each successful backup: backup files expire at 30 days behind a floor of 7, events rows at 90 days, token_usage never — DASHBOARD_RETENTION_BACKUP_DAYS / _BACKUP_KEEP_MIN / _EVENTS_DAYS
~/.claude/projects override mechanism Planned — conventional path assumed fixed, override undecided Decided: CLAUDE_PROJECTS_DIR (plus DASHBOARD_INGEST as a master off switch)
model_pricing seed refresh cadence Planned — versioning columns fixed (CD-4), cadence undecided Still no cadence and no refresh mechanism; verified_on was never added and the seeded rates are PROVISIONAL. Coverage widens by migration only — 18 added claude-opus-5 and claude-fable-5-1 on 2026-09-10 — and that is the one refresh path that exists
Stack underneath all of the above (Fastify, better-sqlite3, pnpm monorepo) Leaning — unconfirmed (the project CLAUDE.md) Confirmed and shipped
Poll interval / watchdog window (not in the design-era table) DASHBOARD_POLL_INTERVAL_MS = 3000 and DASHBOARD_WATCHDOG_MINUTES = 10, both PROVISIONAL (LABEL-ME) — chosen, not ratified
Instance label (not in the design-era table) DASHBOARD_INSTANCE, defaulting to the machine hostname — the non-null instance column that makes the global DAG queryable per instance

See also