Argus — Requirements¶
- Date: 2026-07-08 (stories completed 2026-07-10)
- Author: Aaron (interactive walk-through with Claude)
- Status: APPROVED 2026-07-10 (all §11 questions ruled) — decomposed to the board: consolidation epic + P1–P7 features, rolling task decomposition (F1 filed first)
- Scope: Full-stack consolidation — the app's information architecture and interactivity AND the hub changes that make it possible (effective-config resolver, config migration, scheduler, retiring duplicate surfaces). Decided over app-only: the UI scatter is a symptom of backend scatter.
- Trigger: Wanting to unify the app and make it more usable after sustained real use
1. Introduction¶
Argus is a self-hosted control plane + universal memory hub (Go hub + native SwiftUI app for iPhone/iPad/Mac) whose reason to exist is trust: it works reliably and you can always tell whether it's working. After weeks of real daily use — including a full build-out of the command center and the first fully autonomous build loops — the system works, but its configuration and surfaces have grown organically and fragmented. This PRD defines the consolidation: one coherent information architecture, one config home, one frontend, and the agent/dispatch model (routing + context) that the next phase of autonomous work needs.
2. Background / Current State¶
The full system inventory — every hub endpoint (128 routes), every app surface, the config-fragmentation matrix, workflows, and prioritized recommendations R1–R6 — lives in docs/plans/2026-07-07-argus-inventory-consolidation.md. This PRD builds on that audit and supersedes parts of it where noted (R5(d)/OQ#3 superseded by US-28; R6 reframed as US-28's removal gate; ARGUS-409's backend framing superseded by US-18). Also background: the two live verification trust holes the audit's follow-up review found were fixed immediately (ARGUS-450/451, plus the 452 grandfather rule) rather than waiting for this PRD.
3. Problem Statement¶
Three operational concepts — how a role/project runs (model + backend), when a project builds (dispatch), and when to notify — are each configured in 4–6 disconnected stores (DB tables, SKILL.md frontmatter, config.ini, env vars, per-run columns), and nothing reports the effective resolved value. In the app this surfaces as 2.5 settings homes, config split across screens, and a parallel web UI that drifts from the native app. For a new user (the Hermes lesson) nothing says what to do next; for the owner, it's hard to see where anything is configured or what's in effect — which contradicts the system's founding promise. Meanwhile the autonomous loop is outgrowing its primitives: no per-task model routing, no deliberate context assembly, cron in a config file, agents' definitions scattered.
4. Goals & Success Metrics¶
Measurable end-states (each maps to story evidence gates):
- One config home: every behavioral setting is editable in the app under Settings; zero settings require SSH or file edits (secrets/wiring excepted per US-30). Grep gate: no behavioral tunable read from env at runtime.
- Effective values visible: every config screen shows the resolved value + its source (provenance) via
GET /effective-config— never a guess. - One frontend: /ui/* returns 404/410; the native app passes the US-28 parity checklist.
- Agent = definition + trigger + config: skills and schedules are one object; Ofelia + config.ini retired; schedules DB-versioned (append-only) and app-editable.
- Dispatch contract: every run is stamped with its resolved model (US-18 ladder) and its context bundle (US-27) — both inspectable on the run detail.
- Findability: any board item reachable by search regardless of state/segment (shipped: US-410/443/420 precedents); a childless container never vanishes; closed reads DONE.
- New-user path: fresh install → connect hub → add first project (git preflight) → first agent run, with designed empty states throughout (US-8).
- Alerting that can be trusted: severity taxonomy live, auto-resolve wherever observable, one Inbox queue, quiet hours server-side (US-11/23).
5. Non-Goals / Out of Scope¶
- Chat UX — stays as-is (decided).
- Multi-tenant / multi-user — single-user system; no access tiers, no localization.
- LiteLLM's own configuration (config.yaml on pve-llm-infra) — separate service; Argus owns only the tier mapping (US-31).
- Fable 5 as a task/builder backend — interactive use only until per-task model config lands (US-18 policy).
- Hard deletion of projects or tasks — archive/soft-delete only.
- Morning-triage as a dedicated feature — dropped; the home surface (US-20) covers it.
- New notification transports — native push only (Telegram is being removed, not replaced with something else).
- The Argus memory architecture itself (distill/recall design) — tuning knobs get exposed (US-29), the architecture doesn't change here.
6. Solution Overview¶
Target IA (app): six top-level surfaces — Command (home, US-20 redesign) · Chat · Inbox (the one pending-you queue: approvals, review, alerts — US-23) · Work (tasks/board/flow + activity + landing + skill catalog under one section with segments — full merge, ruled) · Memory (overview, graph, effectiveness — US-19) · Settings (the one config home — US-6: connection/tokens/diagnostics read-only, global agent defaults, quiet window, Projects, Memory, Providers). Plus the Projects status surface (cards → detail, US-24's 9-slot layout) with zero inline config.
The Agent model: Agent = definition (SKILL.md in a git repo — the global repo, or the project's own agents/ folder tracked plainly in that project's repo, US-1) + trigger (schedule | event | manual, DB-stored append-only versions, US-10) + config (model, backend, notify, requires_approval — US-21). Connectors are an agent kind (US-26). Prompts render read-only in-app; edits go through the chat agent as git commits (US-2).
The dispatch contract: at hire-time the hub resolves (a) which model via the US-18 ladder (task override → project main → fallback on 7d-limit/refusal → global) and (b) what context via the US-27 assembler (contract + lineage + dependency outcomes + prior attempts, budgeted recall, tools for the rest). Both stamped on the run, both inspectable.
One backend spine: GET /effective-config?project=&role= returns every concept as {value, source} (provenance enum: global | project | per-run | env | backend-coupled | frontmatter — schema pinned in the audit R1). Env migrates to DB where behavioral (US-30). The board is the orchestrator: US-15's ready-and-unblocked set is the fan-out queue.
Subtractions (as important as additions): Telegram subsystem (US-9), Ofelia + config.ini (US-10), the web UI (US-28), duplicate config writers (audit R5).
7. User Stories¶
Vocabulary (decided): an Agent = definition (the SKILL.md prompt) + trigger (schedule | event | manual) + config (model, backend, notify). "Skill" refers only to the definition file format. The app stops presenting Skills and Scheduled as separate worlds.
- US-1 — Global vs project agents. (Corrected reading 2026-07-13 — the nested-own-repo convention was a misinterpretation.) As Aaron, I can define global agents (high-level, cross-project) in the existing dedicated repo (
claude-code-skills), and project agents in that project repo'sagents/folder — a plain tracked directory, committed like any other content. No nested repo, no gitignore, no extra per-project agents repo: one project repo on Forgejo holds docs + agents, visible, backed up, PR-able, and CI-validated (agent frontmatter + secret scan is the code-less repo's gate).skill-syncscans oneagents_pathper registered project, stampingprojectscope into the same registry. First conversion: runonyourown/tdarr (2026-07-13). - US-2 — Agent viewing (read-only prompts). I can view an agent's prompt in rendered markdown in the app, and remove an agent. No in-app prompt editing — changes happen by asking the chat agent, which edits the agents repo like any other code change (git-tracked, PR-able).
- US-3 — Schedule visibility + editing. I can see all scheduled runs, review them, and update cadence from the app — this is a frequent, low-friction action.
- US-4 — Agents and triggers are one thing. A schedule is not a disconnected cron line; it is the agent's trigger. I see one object: this agent, on this trigger, with this config.
- US-5 — Projects home. A main screen with a card per project; tap for detail. (Detail contents: TBD, own walk-through.)
- US-6 — One config home: the Settings menu. (Amended after design research.) ALL configuration lives in the dedicated Settings menu, consistently: connection + tokens + global defaults + quiet window, and Settings → Projects — per-project config (dispatch on/off + windows, schedules, assigned agents + per-agent config, deploy hooks, notify, add project, archive — soft-delete, never hard delete). The Projects status surface (US-5/US-24) carries NO inline config editing — status/activity and config never share a scroll; a "Configure" row deep-links to Settings → that project. Archive implications enumerated in Technical Requirements: dispatch, /command.json, verify sweeps, and landing scope must all respect archived.
- US-7 — Project = git repo. A project is backed by a git repo (that's what defines its context). New-project flow: pick a folder (new or existing) → preflight (is it a repo; are the right files tracked/configured; agents folder present) → anything missing is shown as an explicit "here's what will be done" checklist with a confirm checkbox before Argus does it. Extends the existing repo-attach onboarding (bootstrap PR).
- US-8 — Brand-new user. As a new user unfamiliar with the tool, the first-run experience tells me what to do (the Hermes lesson: nothing was clear). Guided path: connect hub → add first project → see first agent run. Every screen has a designed empty state that says what comes next, never a blank list.
- US-9 — Telegram retired. Native app push replaces Telegram entirely: channel registry, slash-command router, per-chat menus,
/ui/telegram, andARGUS_TELEGRAM_*toggles are removed. Gate: native push must be confirmed as the sole transport for every alert class before removal (rollout item). - US-10 — Schedules live in the DB, versioned; the hub IS the scheduler. Hub-native scheduler replaces Ofelia + config.ini + the task-runner container + the run.py self-report shim. Schedule rows are append-only: every edit INSERTs a new row and moves the
activeflag; history is the row trail. Pause = deactivate without delete. Triggers enumerate asschedule (cron)|event|manual. Execution model — jobs collapse to three kinds: (1) internal — hub functions fired directly (verify sweep, digest, watchdog, counters; today's scripts are HTTP shims to the hub's own endpoints); (2) service calls — the hub POSTs to the memory service with a timeout (distill, grade, lint, dedup-backfill); (3) agent runs — enqueued onto the existing agent-run queue, claimed by the host agent-runner unchanged. The hub writes the run row itself when it fires (it is the ledger; no self-reporting). Failure classes removed by construction: name drift (schedule row IS the job; watchdog derives expectations from the same active rows the scheduler fires from), restart-window orphans (no completion-POST race), silent Ofelia failures (a failed fire is a run row, flowing into US-11 alerting), and the no-overlap gap (no_overlapbecomes a per-schedule DB flag enforced on EVERY trigger path — scheduled, run-now, event). Missed-fire policy: per-schedule flag — skip-to-next for high-frequency jobs, catch-up-once for daily+ (default assigned by cadence). - US-11 — Alerting. (Adopted from research.) Taxonomy: critical (interrupt now: post-merge main break, prod deploy failure, Grafana critical, watchdog-blind) / warning (today: run failures, CI-red landings, ordinary no-shows) / info (never interrupts: digest, suppression notices) — plus approval as its own kind (a request blocking an agent, not an alert; sorts above alerts). Lifecycle: firing → acked → resolved(auto|dismissed); opening an item's detail acks implicitly; auto-resolve wherever the condition is observable (no-show clears when the run fires, main-break when main greens, Grafana resolved-webhooks clear); resolved items grey + archive after 7 days. Re-notify: critical unacked re-pushes at +15m and +2h then badge-only (3 interruptions max); warning one push ever; approval one push + one +4h reminder; a critical that auto-resolves pre-ack still sends the resolved push. Grouping: dedup key (source, entity, failure-kind) with count + first/last-seen on one item; 60-90s debounce collapses cascades (children absorbed as detail lines); storm breaker: >10 distinct alerts in 5 min = one critical "alert storm" item. Quiet hours: severity → APNs interruption level — critical = time-sensitive (breaks through DND/Focus); warning + approval held SERVER-side during the quiet window, flushed as one "while you were out" summary; info passive always. Quiet window is a DB-stored setting (editable in the app under Settings, default 23:00-08:00), one global window to start. Inbox row: severity chip, source glyph, title, one-line evidence, count badge, relative last-seen, state; swipe ack/dismiss; detail carries occurrence history, correlated children, raw evidence excerpt, deep link, and ONE primary contextual action per source (Retry / Open PR / Redeploy / Approve) mirrored as the push's long-press actions.
- US-12 — Template project. A template repo defines the common project structure (an
agents/folder as a plain tracked directory per US-1's corrected reading, common actions/agent-run definitions, a.forgejo/workflows/validate.ymlagent-frontmatter + secret-scan gate, the tracked-files layout US-7 preflights). "New project" can instantiate it, or the agent creates the structure on first run. Git-native, so cheap to maintain. Status: template, preflight, and instantiate all landed on the plain-tracked agents/ shape (ARGUS-555/556/577) —runonyourown/project-templateon Forgejo defines the agreed structure (agents/ with README + example SKILL.md as plain tracked content, CLAUDE.md skeleton, .forgejo/workflows/validate.yml agent-frontmatter + secret-scan gate),POST /projects/template/instantiatecopies it into a chosen folder with no git-init of agents/, and the onboarding preflight (ARGUS-556) fails the old nested+gitignored shape with a fix pointing at committing agents/ into the repo. Remaining: the app's New Project flow that wires folder-pick + preflight + this operation together (ARGUS-557). - US-13 — Project or general tasks. Every task belongs to a registered project or the explicit
generalbucket. Filing against an unknown project slug is REJECTED (today a typo silently creates a new project). Per-project tags on task/feature/epic give a custom grouping layer (tags exist today; per-project namespace + management is the delta). - US-14 — DB-enforced task schema. Agents cannot file tasks missing the fields required for that type. Two layers: DB constraints for structure (per-type required fields, e.g. task requires non-empty acceptance), lint for semantic quality. DB enforcement protects against every writer, present and future.
- US-15 — Ordering, critical path, blockers. Tasks are set up in completion order: filing N tasks in sequence auto-chains depends_on unless told otherwise. The hub computes topological order, critical path, and a "next up" queue from the existing links; dispatcher and app both consume the computed order (business logic server-side; the app renders).
- US-16 — Autonomous runs: opt-in with user-set windows. Default OFF per project. When enabled, the user sets the active window(s) (e.g. overnight only: queue tasks by day, they build at night). Supersedes the hardcoded 23:00-07:00 night window and the off/night/always enum.
- US-17 — Dual-backend runs with memory parity. Tasks run with claude OR codex with the same memory captures and injections. Status: interactive parity landed, headless parity open — ARGUS-425 (codex-native hooks: recall emits codex JSON, reads codex sessions), 426/429 (capture fixes) deliver memory parity for INTERACTIVE codex-local sessions only; CodexUsage per-run token accounting and 407/408 backend + cap-fallback are verified independently. Triage (ARGUS-562, 2026-07-13): ARGUS-423 (interactive parity) is cancelled, confirmed fully superseded by 425/426/429 — no remaining delta. ARGUS-417 (headless runner parity) predates 425 (cancelled before 425 existed, so not actually superseded by it) and stays cancelled as a design document; its real remaining delta —
internal/hub/codex_backend.go'scodex execpath has zero recall/capture wiring today — is rescoped to ARGUS-571. -
US-18 — Main + fallback model per project, driven by 7d limits. Each project defines a main model and a backup/fallback model (across the two subscriptions), with failover based on each subscription's 7-day usage limits. Claude usage capture exists per run; codex usage metering + per-project threshold config are the delta. This is the centerpiece of the Agent Config surface and supersedes the auto/claude/codex backend framing (ARGUS-409). Fable 5 policy (decided):
claude-fable-5joins the models allowlist but is NOT selectable for task/builder runs until per-task model config exists — interactive use only (hard design/debugging), with easy work handed to Sonnet/Opus subagents; it burns quota at 2x Opus weight. Model picker guidance (decided): the model selector displays a when-to-use line per model, served by the hub in /models.json (e.g. Fable: hardest design/debugging, interactive-only, 2x quota; Opus 4.8: default hard builder; Sonnet 5: routine builds; Haiku: light/chat) - guidance is data, not UI copy. Per-task model resolution (decided): a model is fixed per run at dispatch time, resolved by ladder: task-level override (optional Model field on the compose sheet, feeding the hub's existing per-run tier — usually left empty) > project main model > project fallback (when main is at its 7d limit or refuses) > global default. The dispatcher stamps the resolved model on each run; run rows and the command center display model + cost per run so routing is adjustable at the project level, not per task. The board's ready-and-unblocked set (US-15) is the fan-out queue - one runner per ready task, each with its own resolved model; no separate orchestration layer. -
US-19 — Memory effectiveness, overall + per project. A Memory page shows the overall rollup and trends; each project's detail shows the same scoped to it. Metric axes: recall quality (precision trend — the existing recall-grade job's avg_precision, persisted + project-scoped instead of a log line), usage (injections/session, % sessions with an injection, hot notes, dead-weight % never recalled — from the existing injection audit log), capture health (notes/week, dedup rate, contradiction/parked — from distill stats, moved out of detail strings into structured storage). One hub aggregate feeds both (
/memory/effectiveness.json?project=). Caveat on record: outcome impact is proxied by precision + usage; ground truth would need an in-chat usefulness signal (future). Added axis (decided 2026-07-12): human-time economics — true human interactive time (raw turns filtered to origin=human once ARGUS-526 stamps provenance; gap-capped sessionization) vs agent execution time (agent_runsdurations), rolled up as human-minutes-per-verified-task — the autonomy ratio, computed deterministically in the hub, per project and overall. -
US-20 — Main page (home) redesign. The home surface shows current status + issues in a modern, sleek element UI: high-level first, every element tap-through to its detail. Content: what needs me (approvals), what's broken, what's running, pipeline — plus memory KPIs (US-19's aggregate gets a home card). Keeps the /command.json severity-ordered data spine from command center v2; the redesign is presentation. Chat page stays as-is (decided). Pipeline slice pulled forward (decided 2026-07-11, ARGUS-512..517): a bors-style per-task pipeline (BUILD → PR → LAND → DEPLOY → VERIFY, server-composed by deterministic join, honest waiting-reasons on every step) replaces the PIPELINE/WORKING NOW/BROKEN sections with one strip list and, once parity is shown, replaces the LANDING segment entirely (Work-merge ruling executed early). What remains for P6 here: memory KPIs card, the broader visual redesign, and the remaining section layout.
- US-21 — Approval-gated scheduled runs. A trigger-level
requires_approvalflag: when the schedule fires, it files a pending approval instead of executing; the actionable push (Approve/Reject) arrives; approval enqueues the run. Human-in-the-loop autonomy per agent. Config lives on the agent's trigger (US-4 model). -
US-22 — Task flow view + strict links. The board gains a flow view that clearly renders blockers, link types, and the hub-computed critical path (US-15). Trustworthiness comes from link strictness, enforced not hoped: batch filings auto-chain; a task filed with no links must explicitly declare independence; the hub lints dangling/orphan tasks.
-
US-23 — On-the-go approvals: three doors, one queue. Everything awaiting the human lives in ONE section — the Inbox (approvals, review items, and later alert acks; rename open if "Inbox" doesn't read right). Home (US-20) previews it (count + top items, tap-through). Push notifications act on it directly: approval pushes already carry the iOS APPROVAL category — long-press/expand reveals Approve/Reject, working from the lock screen with the app closed (OS shows actions on expand only, never inline; app must launch once post-install to register the category). Requirement: every approval class, including US-21 scheduled-run gates, reuses this actionable category. No new notification section — three doors, one queue.
-
US-24 — Project detail screen. (Adopted from research: Linear/Vercel/Datadog/Railway/Komodo convergence — live status above the fold with one composite health signal; status/activity/config never share a scroll; one primary verb, visible even when disabled.) iPhone top→bottom: (1) header — name, repos, composite health badge, dispatch chip ("armed, next window Tue 22:00"), DISPATCH NOW rendered disabled-with-reason when gated, never hidden; (2) conditional attention strip — renders only when nonempty: failed run, stuck queue head, blocked count, degraded capture health, each deep-linking to the offender; (3) now/next — running agent with elapsed + live tap-through, or next window + what would dispatch; (4) work snapshot — ready/in-progress/blocked counts, epic progress, next-up → board; (5) landing queue — depth, head PR + CI glyph; (6) recent runs — last 3-5 with outcome/duration/cost; (7) memory KPIs (US-19), capture-health promoted to the attention strip only when degraded; (8) agents & skills rows — model/backend chip, last-run glyph; (9) "Configure" row → Settings → this project (US-6: no inline config here). iPad/Mac: same order, two columns, no new information.
-
US-25 — Verification hardening. (From the code-grounded research review; the mechanism's architecture is strong — structural verifier/agent separation, fail-closed human token, re-derived truth — these close the found gaps.) (a) [filed immediately as hub tasks — live trust bugs: ARGUS-450, ARGUS-451]
previdence must require green CI on the merged head, not merged+referenced alone (today a directly-merged PR verifies with red/absent CI; only the landing queue's pre-merge gate covers it);artifactevidence demoted from anchor status (today an agent can write a file under root and self-verify with zero PR/human involvement). (b) Into this effort: merged-PR keyword lint (a merged PR bare-mentioning an unverified task raises a finding — closes the stranded-task soft spot from the PR side); deploy-coverage rule (a task in a deploy_hook project cannot verify without satisfied deploy evidence, regardless of merge path) — triaged (ARGUS-581, 2026-07-14): already enforced, no gap. ARGUS-343's landing hook (cmd/hub/main.go'sWithLandedHook) attaches an unsatisfieddeploy_logevidence row viahub.AttachDeployEvidencefor every deploy hook matching the landed repo, before running the verify sweep in the same callback;verifyOne's AND-rule (internal/hub/verify.go) treatsdeploy_logas a bound anchor kind whosesatisfiedflag is set only externally (hub.SatisfyDeployEvidence, on a real deploy success), so a task in a deploy_hook project cannot verify on merged-PR evidence alone — it stays held until the deploy completes. Demonstrated end-to-end by the existingTestDeployQueueAndEvidenceHold(internal/hub/deploys_test.go), which asserts unverified with satisfiedprevidence + unsatisfieddeploy_log, then verified onceSatisfyDeployEvidenceclears it. No grandfather cutoff needed (452 pattern) since this is confirming an existing rule, not introducing a stricter one; drift lint or auto-stamp forverified AND lifecycle IN (backlog, ready); record actor on evidence rows. Each carries the research's one-line evidence gate. -
US-26 — Data connectors. Pluggable content sources (karakeep/Hoarder first; anything that accumulates content) with scheduled extraction into the memory system. A connector is an agent kind, not a new concept — definition (source + what to extract) + trigger (schedule) + config (credentials/endpoint, cadence, scope) — inheriting DB-scheduled versioning (US-10), optional approval gating (US-21), notify policy, and run-history visibility for free. Configured under Settings → Connectors (US-6). Memory-grade requirements: every extracted note carries provenance (source, item id, URL — recall can cite it) and runs are idempotent + incremental (only new/changed items; re-runs never duplicate). Per-connector capture health rolls into US-19 — a dead connector surfaces as degraded capture, never silence.
-
US-27 — Context assembly per run (hub-side). At dispatch, the hub assembles each run's context bundle — the other half of per-task model routing. Three tiers: (1) deterministic contract, always injected, computed by code: the task's why + acceptance; the LINEAGE (parent feature/epic whys — "give the reason, not just the request"); the DEPENDENCY NEIGHBORHOOD (depends_on tasks' outcomes — merged PRs/what was built — plus what's downstream); PRIOR ATTEMPTS (failure report from a bounced run, so re-dispatch never starts blind); project conventions (repo CLAUDE.md / agents folder). (2) retrieved, budgeted: the existing project-boosted memory recall — precision over volume (injected-irrelevant is measured harm via recall-grade). (3) discoverable: repo/board/history via the agent's tools — pointers, not prompt-stuffing. One assembler in the hub composes the bundle (testable: given this task graph, the bundle contains X); the run detail shows what context a run received, tying context quality into US-19's effectiveness metrics. Delta vs today: tiers 2/3 exist; tier 1 (lineage, dependency outcomes, prior attempts, single assembler) is new.
-
US-28 — Retire the web UI. The native app (iPhone/iPad/Mac) is the only frontend; the server-rendered /ui/ surface is removed entirely once native parity is reached. Rationale: single user with the app on every device (Tailscale-only); two frontends are the fragmentation engine (features exist twice or drift); removal deletes the public no-bearer write-mirror security concern outright (supersedes the audit's R5(d)/OQ#3 "keep + document" decision — that was an answer to "is it safe to keep," not "do we need it"). Survives, not web UI: the JSON API, /app + manifest.plist (OTA install), /healthz, /metrics. Removal gate — native parity checklist: memory review/parked/lint actions (Inbox, US-23), deep memory browsing (episodes/notes/injections into US-19's memory page), agent-run force/model controls, plus a final sweep for any /ui-only capability. Audit R6 is reframed: the web-only ports are the removal gate, not an end in themselves. Last slice of the rollout; evidence gate: /ui/ routes return 404/410, ui_*.go + templates deleted, app covers the checklist.
-
US-29 — Memory config in Settings. Settings → Memory exposes the memory service's behavioral tunables natively: recall floor (today ARGUS_RECALL_FLOOR), project-scope boost, distill/dedup thresholds, capture toggles, connector defaults (US-26). Companion to US-19: the effectiveness page says whether memory is working; this screen is where you tune it.
- US-30 — Env-to-DB migration: wiring vs behavior. Principle: env is for wiring, DB is for behavior. Stays env: bootstrap/infrastructure (DB URL, ports, service URLs) and ALL secrets (SOPS+age; secrets never in the DB in plaintext) — shown read-only in an app diagnostics view, never editable. Moves to DB (app-editable, no restart): behavioral tunables — recall floor, boosts, distill params, digest cadence, quiet window, dispatch windows (US-16), schedules (US-10), notify defaults, model routing (US-18), provider tier mappings (US-31). Each migrated knob shifts its /effective-config provenance from
envtoglobal/project. The audit's fragmentation-matrix env rows are the migration inventory.
Timezone split: durable timestamps, logs, ledgers, and schedule cron rows stay UTC. Human-facing windows (global quiet window, per-project dispatch windows, and the fallback night dispatch window) evaluate in the DB-backed hub timezone setting, default America/Chicago, and can be changed without restarting the hub.
- US-31 — Provider config (LiteLLM + codex). Argus owns the MAPPING, not the providers: which LiteLLM tier serves embeddings vs judgment (distill/lint/grade), the codex/OpenAI subscription config for US-18's second backend, and the 7d-limit thresholds. DB-stored, edited under Settings → Providers, with provider/endpoint health shown read-only. LiteLLM's own config (config.yaml on pve-llm-infra) stays out of scope — separate service. Keys stay env/SOPS, never DB.
-
US-32 — Live updates: one SSE invalidation stream. The hub exposes
GET /events(Server-Sent Events, bearer-gated): small invalidation signals broadcast on writes — run started/finished, task changed, landing moved, deploy completed, alert fired. The app holds ONE connection while foregrounded; views subscribe to event kinds and REFETCH their existing server-composed aggregates on signal (events are invalidations, not data deltas — no client-side merging, no second data path). Replaces the per-view polling timers (command center/flow 15s) and the stale-on-tab-return boards. The live pulse becomes honest: tied to actual stream connectedness — connected shows live, dropped shows "reconnecting," never a silently stale screen. Foreground-only; pull-to-refresh remains the manual fallback. Rejected alternatives on record: per-view polling (latency + battery, scales badly), WebSockets (bidirectionality unneeded), APNs silent pushes (rate-limited, unreliable for UI). -
US-33 — Forge connect + repo discovery. Projects onboard by PICKING repos, not typing owner/name strings (a typo silently mis-attaches; a new project string silently creates a project). Phase A — Discover from Forgejo (small): Settings -> Projects gains DISCOVER, listing every repo the hub's existing Forgejo connection can see, already-attached ones marked; selecting a repo either creates a project (slug proposed from the repo name) or attaches to an existing one (repo != project: argus itself is two repos under one project). Per-repo opt-in only - discovery never bulk-onboards, so 30 repos never means 30 bootstrap PRs. No new credentials: Phase A rides the env/SOPS Forgejo token (US-30: secrets never in the DB). Phase B - Connect a forge (unscheduled, gated): GitHub/GitLab/Gitea instances entered in the UI with credentials - gated on an encrypted-at-rest secrets story AND the ForgeSource abstraction (US-34), since the landing queue's rebase-update, CI polling, and PR semantics are Forgejo-specific today.
-
US-34 — CI visibility: normalized forge CI reads. The hub consumes CI as a boolean today (landing gate, verify checker); when a landing goes ci_red the operator must leave for the Forgejo web UI to learn WHY - the biggest remaining leave-the-app debug path, and a direct blocker on the US-28 parity checklist. A read-only ForgeSource interface (list repos, default branch, combined status, workflow runs, failing-step log excerpt) with the Forgejo implementation first, exposed hub-side scoped git system -> repo -> ref/PR -> checks, short-TTL cached. Business logic server-side: the HUB extracts the failing check + log tail; the app renders. Three surfaces in value order: (1) landing failures explain themselves - tap a ci_red row, see the failing check + excerpt; (2) task PR-evidence rows show their real check rollup (the checker's own view, made visible); (3) CI health chips - project detail shows its repos' default-branch state, one forge-wide list gives the instance picture. Deliberately NOT a CI browser - that duplicates Forgejo's UI and fights the subtraction philosophy; scope is the operational loop (why did MY landing/task/repo go red). Phase A of US-33 and this story share the connection and the Work-section screen family.
-
US-35 — Agent notification bus: push to agents, with status-heartbeat semantics. (Added 2026-07-12; unparks the multi-agent coordination lot now that P3 is done.) Argus pushes events to AGENTS the way it already pushes to Aaron's devices — no agent ever discovers state by polling. Durable per-agent notification rows, written by the same event paths that feed SSE (task verified, landing/deploy failed, approval waiting, watchdog no-show, schedule fire failure), routed by ownership + subscriptions (
task:ARGUS-N,landing:*, severity classes — US-11 taxonomy, never a firehose). Three delivery doors by agent shape: (1) interactive sessions — the recall hook appends pending notifications to its existing per-prompt injection (push-on-next-breath, harness-agnostic); (2) long-lived agents (the runner; an orchestrating session overnight) — a long-poll endpoint (GET /agents/notifications?wait=) that returns the moment an event lands: push semantics over one bounded HTTP request, no persistent stream to babysit, re-arm on return; (3) any agent — an MCP drain tool over the same queue. Status-heartbeat payload (the core requirement): every notification carries the DELTA, not just the event — done: what completed and what evidence closed it; unblocked: which dependents became dispatchable (depends_on graph); in-flight: what is building now (pipelines view); next: the ready queue head. One glance = the whole board pulse, computed deterministically from existing ledgers (the ARGUS-512 composer + links), so "task done" always means the same thing: evidence-verified, named dependents released, and the notification says so explicitly. Delivery is at-least-once with acked drain; undelivered rows age out visibly, never silently. -
US-36 — Native alerting: Argus detects, Argus pushes. (Added 2026-07-13; supersedes the Grafana relay as the SOURCE for Argus-domain alerts — the epic-level delta on top of US-11, which keeps taxonomy/lifecycle/grouping/quiet-hours ownership.) The hub already sees every Argus failure class natively (failed runs, red landings, no-shows, capture health, deploy failures); routing them through Grafana rules that relay back as verbatim
{title, message}text is a pre-app vestige (unrounded floats, dead localhost links, undismissable rows). End state: the hub detects and pushes its own alerts through the US-11 pipeline (taxonomy, firing→acked→auto-resolved lifecycle, dedup/grouping/storm-breaker, server-held quiet hours, actionable APNs categories),ARGUS_SELF_ALERTSdelegation inverts (Argus alerts for itself), and every Argus-domain Grafana rule is deleted — EXCEPT one: a dead-man's-switch ("hub heartbeat stale → critical"), the minimal outside-in signal, because a system that only alerts about itself cannot report its own death (proven live 2026-07-13: the karakeep ingest-stalled rule was the only watcher that noticed a frozen heartbeat row). Cutover rule, per the scheduler pattern: each Grafana rule is deleted only after its native replacement has fired live at least once (per-class parity, never a flag-day). Non-Argus homelab rules (tdarr infra, Proxmox, UPS) are out of scope and stay in Grafana. Slots as its own phase-scale epic; P6 slims to home redesign + Inbox + SSE + first-run.
Pending walk-throughs: none — morning triage dropped (not needed for now).
8. Technical Requirements¶
Fable 5 readiness (from Anthropic's released guidance, 2026-07-10):
- Skill/prompt shape for agents ("de-prescribe"): skills keep goal + intent, hard constraints/trust rules, verification method, and reporting rules — and drop procedural step recipes, prescribed tool sequences, and prior-model guardrail language ("CRITICAL: you MUST", "if in doubt do X"). Task bodies carry no step lists: rich why (intent) + crisp acceptance (done-condition) is the contract — US-14's schema enforcement pushes this shape. A/B existing skills (overnight-build first) with scaffolding removed before Fable becomes a runner backend.
- Timeout/watchdog re-sizing: Fable turns run minutes-long by design; agent-runner claude -p timeouts and no-show watchdog windows sized for prior models must be re-derived per backend model (the false-no-show mode: a run outlasting its schedule).
- Reasoning-echo audit: before Fable becomes a runner backend, grep skills/prompts for transcribe-internal-reasoning phrasing ("show your thinking", "explain your reasoning process") — triggers refusal classifiers on Fable. Asking for decisions, rationale, and evidence is fine; asking for the thinking process is not.
- Refusal fallback: Fable-backed runs must fall back to Opus 4.8 on stop_reason: refusal (same mechanism as US-18's 7d-limit failover).
9. Rollout & Migration Plan¶
Phased vertical slices; each phase lands independently and the system stays fully working between phases. Data migrates with its feature, never separately.
- P0 — Trust fixes (done): ARGUS-450 (CI in pr-evidence), 451 (artifact anchor demoted), 452 (grandfather rule).
- P1 — The resolver + Agent Config (done):
GET /effective-config(schema per audit R1) + durable backend/main/fallback columns + the Agent Config surface (US-18, absorbing ARGUS-409 rewritten). First slice because R2–R5 and every config screen reuse it. - P2 — Settings consolidation (done; US-5's Projects home shipped alongside — phase credit recorded 2026-07-13): one Settings home (US-6), ConfigView folded in, Projects/Memory/Providers sections (US-29/31), quiet window, iOS text-scale control (§11 ruling 4); project detail rebuilt to the 9-slot layout with Configure deep-link (US-24).
- P3 — Scheduler + env migration (done — cutover approved and task-runner retired 2026-07-13): schedules to DB append-only, hub-native firing, Ofelia + config.ini retired, watchdog reads active rows (US-10); env→DB per the wiring-vs-behavior rule (US-30); per-project dispatch windows (US-16); approval-gated triggers (US-21).
- P4 — Agents as git + dispatch contract (done 2026-07-13 — decomposed and landed as ARGUS-549..577; US-1 corrected to plain tracked agents/; stragglers: ARGUS-557 new-project flow, 565/571 staged): per-project agents repos + template project + preflight (US-1/7/12), skill-sync per project, context assembler (US-27), per-task model field + routing ladder live end-to-end (US-18), connectors (US-26), codex memory parity (US-17), forge repo discovery + normalized CI reads (US-33 Phase A, US-34; default_owner wiring pulled forward 2026-07-11 as ARGUS-500); agent notification bus (US-35 — the coordination substrate the dispatch-contract era needs; first hub slice pulled forward 2026-07-12).
- P5 — Board intelligence + flow + agent surface unification: DB-enforced schema (US-14), ordering/critical path/auto-chaining (US-15), flow view + link strictness (US-22), global-search/childless/DONE polish continues (US-13 tags, general bucket); agent surface unification (US-2/3/4 — phase home assigned 2026-07-13 after falling through the original mapping): hub-side agent-to-trigger-to-runs join (agent_runs currently has no durable schedule correlation), one-object rendering per project (this agent, on this trigger, with this config, last runs), prompt viewing (US-2) and cadence editing (US-3) from the app; US-25 remainder (same 2026-07-13 assignment): deploy-coverage verification rule, verified-while-backlog drift lint, evidence actor audit — triage against what ARGUS-540/563 already delivered before filing.
- P6 — Home + alerting + live (alerting slice re-homed 2026-07-13: US-11's pipeline ships inside the US-36 native-alerting epic; P6 keeps the surfaces): command center v2 visual redesign + memory KPIs (US-20), memory effectiveness aggregate + Memory page + project-detail slot 7 (US-19 — US-20's home card and the US-24 placeholder both consume its
/memory/effectiveness.json), alerting taxonomy/lifecycle/grouping/quiet hours (US-11), three-doors Inbox (US-23), new-user first-run + empty states (US-8), SSE live-update stream replacing polling (US-32). Quiet-hours enforcement depends on the timezone foundation (ARGUS-503) — windows must evaluate in the configured zone, not container-UTC. - P7 — Subtractions (last, gated): Telegram removal after push covers every alert class (US-9); web UI removal after the parity checklist (US-28).
Migration rules: every schema change is a forward-only migration; retired config sources keep working until their replacement phase lands (no flag-day); each phase's tasks carry the story's evidence gate.
10. Acceptance Criteria¶
The PRD is delivered when the eight goals in §4 are all true, verified per-story by the board's own mechanism (merged PR + green CI evidence, or on-device approval), with §9's phases landed through P7. Spot-check acceptance, end-to-end: (1) fresh-install walkthrough reaches first agent run without documentation; (2) every configurable concept in the audit's fragmentation matrix has exactly one write path and shows effective value + source in the app; (3) a task filed with no model/context hints gets a correctly-routed model and a complete context bundle, visible on its run; (4) /ui/* is gone and nothing breaks; (5) an overnight sprint (N ready tasks, mixed models) lands with per-run model + cost visible next morning.
11. Risks & Open Questions¶
Open questions — RULED 2026-07-10:
1. The Work merge: MERGE ALL FIVE (Aaron: "I haven't used the app that much, it will be fine"). Tasks + Activity + Landing + Flow + Skills-catalog become one Work section with segments — the full six-surface IA as the audit proposed.
2. default_owner: WIRE IT — seeds new tasks' owner (P4).
3. /effective-config explain depth: CONFIRMED — winner+source {value, source} by default; full ladder behind ?explain=1, surfaced in-app as a "why?" tap on config rows.
4. iOS text-scale: IN-APP CONTROL — the app gets its own text-size setting on iOS/iPadOS (macOS already has Cmd +/-), landing in Settings (P2).
Risks: - Scheduler cutover (P3) is the highest-blast-radius change (every scheduled job + no-show watchdog). Mitigation: run DB-scheduler in shadow (log-only) against Ofelia before the switch; the no-show watchdog itself is the regression detector. - Codex 7d-limit signal (US-18): (resolved) the 7-day rolling aggregation per subscription + failover thresholds landed as ARGUS-463 and the dispatch ladder (ARGUS-550) consumes them — failover is no longer claude-side-only. ARGUS-417/423 triaged (ARGUS-562): 423 cancelled/superseded by 425/426/429; 417's real delta rescoped to ARGUS-571 (headless codex-backend runner memory parity, staged). - Retroactive verification changes — 450/451 taught us re-derivation applies new rules to history; every verification-adjacent change needs a grandfather decision up front (452 pattern). - Fable-backed runs: the policy gate cleared — per-task model config + refusal fallback to Opus 4.8 landed (ARGUS-550/551) and Fable is picker-selectable per Aaron's 2026-07-12 ruling (2x-quota guidance line). Still open before heavy Fable builder use: timeout/watchdog re-sizing, de-prescribed skills, reasoning-echo audit (§8). - Single-maintainer bandwidth: phases are sized to land via the existing autonomous loop (hub-agent/mac-agent + landing queue); anything requiring sustained manual work is a smell.
12. Constraints¶
- Single user, self-hosted, Tailscale-only — never expose services publicly; Funnel only for external webhooks.
- Secrets: SOPS+age; never plaintext in DB or commits (constrains US-30/31).
- Forgejo is origin; GitHub read-only mirror. One task = one PR = one green check; merged via the REST merge API.
- The verification contract is inviolable: no "done" lifecycle;
verifiedset only by the deterministic checker (or human approval); agents never self-verify. Consolidation must not weaken it. - Business logic in the backend; the app renders (every aggregate/rollup/resolution is hub-side).
- Platform: iOS 5-tab ceiling; APNs categories require one app launch post-install; no headless Mac runner (mac-agent work is interactive/handoff — decided, ARGUS-346); OTA via Ad Hoc + manifest.
- Quota: Fable 5 at 2× Opus weight on the Max plan — interactive only; subagents pinned to Sonnet/Opus for routine work.
- Deterministic-first: code/CI/cron for facts, LLMs only for judgment (constrains where the context assembler and lint use models).
13. Dependencies¶
- The audit: docs/plans/2026-07-07-argus-inventory-consolidation.md (inventory + R1–R6 + fragmentation matrix — the migration inventory for US-30).
- Board history: ARGUS-409 (superseded, rewrite under US-18), 450/451/452 (P0, done), the command-center epic 387 (shipped; US-20 builds on it).
- Services: the memory service (recall/distill/grade — US-19/27/29 touch its API), LiteLLM proxy on pve-llm-infra (US-31 maps tiers; its own config out of scope), APNs (US-11/23), Forgejo API (landing, evidence, agents repos), codex CLI (US-17/18 second backend).
- Tooling: xcodegen + Ad Hoc OTA pipeline (unchanged), skill-sync (extended per-project in US-1), the existing landing queue + deploy hooks (unchanged, consumed by rollout).
- Model guidance: Anthropic's Fable 5 released guidance (§8 readiness block) — revisit when the model lineup changes.