TradingAgents Dashboard PRD — Pipeline / Debate / Approvals / Agents

For: Apollo (UI/design agent — downstream) From: Plutus (planning + research) Date: 2026-05-04 Session: War Room Session 10 — TradingAgents baseline adoption Sibling: apollo-design-brief.md (v1 — 5 pages already specced) Depth: Hybrid — full data contracts + page intent + key components. Layout/visual decisions delegated to Apollo.


What This Is

PRD for four new dashboard pages added to the existing Trading War Room dashboard surface, plus the n8n telemetry + human-approval gate infrastructure that backs them.

These pages exist because Plutus adopted TauricResearch/TradingAgents v0.2.4 as the baseline analyst/researcher pipeline (operator override 2026-05-04). Operator priority is "UI from day 1 that allows tracking of all operations." These pages deliver that.

This PRD covers:

Plus the supporting layer:


Scope Boundary

In scope Out of scope
Page intent + components for the 4 new pages TradingAgents adoption mechanics (separate baseline-integration backlog)
Data contracts (R2 paths, JSON schemas, SQLite DDL) Apollo handoff doc (drafted separately AFTER this PRD)
n8n workflow specs (intent + IO contracts) n8n workflow JSON build (n8n-hub task)
Telemetry event schema VPS provisioning of TradingAgents Python (Hades + Proteus task)
Operator decision log schema Per-node LLM routing patch to llm_clients/factory.py (~1 day, separate Python task)
Acceptance criteria per page Layout/CSS/component implementation (Apollo craft)
Build order FinBERT deployment, VectorBT integration (PRD 1D)

Locked Decisions

# Decision Choice Rationale
1 PRD depth Hybrid (data contracts + intent + key components, layout to Apollo) v1 brief over-specified visuals; bypass Apollo's craft. New PRD locks engineering surface, frees design surface.
2 New pages /pipeline, /debate, /approvals (own page + topbar badge), /agents (full fleet) Operator priority: "track ALL operations from day 1"
3 /pipeline multi-instrument layout Grid summary (7 tickers) + detail panel for selected Glance + drill-down in single view
4 /debate multi-instrument layout Per-ticker tabs, single-ticker view Debate is per-ticker per-session by nature
5 Approval UX Stage gate (expand thesis + risk panels) + required reasoning field (30-200 chars) Forces eye-pass without slowing fast calls; reasoning becomes institutional memory
6 Reject path Reason field required; reject-only (no modify-and-resubmit in v1) Modify path adds round-trip complexity; defer
7 TradingAgents host Hostinger VPS (alongside n8n) Single deployment surface, n8n triggers via Execute Command, telemetry stays in-network
8 Telemetry transport TradingAgents BaseCallbackHandler → HTTP POST to n8n local webhook → SQLite + R2 Async fire-and-forget, ~50-200ms per event, doesn't slow agent runtime
9 Refresh strategy R2 polling — 3s on active pages, 5s on topbar approval badge, 30s on /agents Matches existing dashboard pattern; no WebSocket infra needed
10 Per-node LLM routing display Surfaced read-only on /pipeline drill-down + /debate per-section badge Config-driven, exposed for transparency
11 Mobile bounds 375px (matches v1 brief). /approvals especially critical Operator approves from phone
12 Build order 1 = /approvals, 2 = /pipeline, 3 = /debate, 4 = /agents /approvals gates real money; ship first
13 Retention SQLite hot 30d, R2 archive forever (Parquet) Hot queryable + cold immutable archive
14 /agents scope Full fleet (4 trading-* repos + TA nodes + n8n workflows + Watchdog + CB Pipeline + system daemons) "Track all operations" priority

Architecture (Data Flow)

TradingAgents (VPS, Python 3.10+, v0.2.4 SHA-pinned fork)
  │
  ├── PlutusTelemetryCallback (LangGraph BaseCallbackHandler)
  │     POST localhost:5678/webhook/ta-telemetry → n8n
  │
  ├── trading_memory.md (per-ticker reflection log)
  │     n8n Local File Trigger watches → parses → SQLite + R2
  │
  └── PM output halt (ADR-006 gate)
        Writes /tmp/.../pending-{order_id}.json
        n8n Local File Trigger → SQLite order_log → R2 pending-orders/index.json

n8n (VPS) — webhook handlers + cron workflows
  │
  ├── ta-telemetry-receiver         (webhook)   → SQLite pipeline_log + R2 pipeline/{session_id}/
  ├── ta-decision-log-mirror        (file)      → SQLite trade_log + R2 decisions/{ticker}/
  ├── ta-pending-order-receiver     (file)      → SQLite order_log + R2 pending-orders/
  ├── approval-receiver             (webhook)   ← dashboard POST → SQLite operator_decision_log + R2 → forward to Alpaca
  ├── alpaca-fill-watcher           (cron 30s)  → SQLite order_log update + R2 fills/
  └── fleet-health-aggregator       (cron 60s)  → R2 fleet-health/index.json

R2 (Cloudflare) — read-only static fetch from dashboard
  ├── pipeline/{session_id}/{node}-{ts}.json
  ├── debate/{ticker}/{session_id}.json (full transcript per session)
  ├── pending-orders/index.json (active queue)
  ├── operator-decisions/{date}.json
  ├── agent-state/{agent_name}.json (per-agent latest)
  ├── fleet-health/index.json (aggregate /agents view)
  └── archive/parquet/{table}/{date}.parquet (cold storage)

Dashboard (CF Pages, React) — polls R2
  ├── /pipeline    ← R2 pipeline/* + agent-state, poll 3s
  ├── /debate      ← R2 debate/{ticker}/{session_id}, no poll (final once written)
  ├── /approvals   ← R2 pending-orders, poll 3s; topbar badge poll 5s
  ├── /agents      ← R2 fleet-health/index.json, poll 30s
  └── POST → n8n approval-receiver webhook (operator decisions only)

Why this shape: - TradingAgents Python on VPS keeps network short for telemetry + file watching - n8n owns all writes to SQLite + R2 (single mutation surface, audit-clean) - Dashboard reads R2 only (no Alpaca creds in browser, no SQLite access from frontend) - Operator decisions go through n8n → can't reach Alpaca without n8n audit row first (security win) - File-based agent comms (ADR-003) preserved at TradingAgents output boundary

Required infra: n8n must have read/write access to TradingAgents working directories (/tmp/trading-war-room/{session_id}/ and ~/.tradingagents/memory/). Since both run on the same VPS, this is a Docker volume mount of the host paths into the n8n container. Provisioning lands in the VPS-baseline backlog task (Hades + Proteus).


Schemas

pipeline-event.json (NEW — _schemas/pipeline-event.json)

Telemetry event emitted by TradingAgents PlutusTelemetryCallback. One row per node entry/exit, LLM call, tool call, or error.

{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "type": "object",
  "required": ["event_id", "session_id", "run_id", "ticker", "node", "phase", "ts"],
  "properties": {
    "event_id":   { "type": "string", "format": "uuid" },
    "session_id": { "type": "string", "description": "War room session id" },
    "run_id":     { "type": "string", "format": "uuid", "description": "Single TradingAgents.propagate() invocation" },
    "ticker":     { "type": "string", "description": "Instrument symbol, e.g. AAPL" },
    "node": {
      "type": "string",
      "enum": [
        "market_analyst", "social_analyst", "news_analyst", "fundamentals_analyst",
        "bull_researcher", "bear_researcher", "research_manager",
        "trader",
        "aggressive_debator", "conservative_debator", "neutral_debator",
        "portfolio_manager"
      ]
    },
    "phase": {
      "type": "string",
      "enum": ["chain_start", "chain_end", "llm_start", "llm_end", "tool_start", "tool_end", "error"]
    },
    "ts":          { "type": "string", "format": "date-time" },
    "provider":    { "type": "string", "description": "openrouter|anthropic|openai|google|...; absent for non-LLM phases" },
    "model":       { "type": "string", "description": "x-ai/grok-4.1-fast etc." },
    "tokens": {
      "type": "object",
      "properties": {
        "prompt":     { "type": "integer" },
        "completion": { "type": "integer" }
      }
    },
    "cost_usd":    { "type": "number" },
    "latency_ms":  { "type": "integer" },
    "tool_name":   { "type": "string", "description": "Set when phase=tool_*" },
    "tool_input":  { "type": "string", "maxLength": 1024, "description": "Truncated to 1KB" },
    "tool_output": { "type": "string", "maxLength": 1024, "description": "Truncated to 1KB" },
    "payload_ref": { "type": "string", "description": "R2 path to full payload if truncated" },
    "error_message": { "type": "string", "description": "Set when phase=error" }
  }
}

operator-decision.json (NEW — _schemas/operator-decision.json)

One row per human-approval-gate decision (approve or reject).

{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "type": "object",
  "required": ["decision_id", "order_id", "ticker", "session_id", "decision", "operator_reasoning", "pm_output_snapshot", "ts"],
  "properties": {
    "decision_id":         { "type": "string", "format": "uuid" },
    "order_id":            { "type": "string", "format": "uuid" },
    "ticker":              { "type": "string" },
    "session_id":          { "type": "string" },
    "decision":            { "type": "string", "enum": ["approve", "reject"] },
    "operator_reasoning":  { "type": "string", "minLength": 30, "maxLength": 200 },
    "pm_output_snapshot":  { "type": "object", "description": "Full PM output the operator saw at decision time" },
    "ts":                  { "type": "string", "format": "date-time" }
  }
}

pending-order.json (NEW — _schemas/pending-order.json)

Written by TradingAgents PM-output halt wrapper.

{
  "type": "object",
  "required": ["order_id", "ticker", "session_id", "run_id", "pm_output", "created_ts"],
  "properties": {
    "order_id":   { "type": "string", "format": "uuid" },
    "ticker":     { "type": "string" },
    "session_id": { "type": "string" },
    "run_id":     { "type": "string" },
    "pm_output": {
      "type": "object",
      "description": "Full portfolio_manager node output — proposed entry/stop/target/size + thesis + risk panel",
      "required": ["entry", "stop", "target", "position_size", "thesis", "risk_panel"]
    },
    "debate_ref": { "type": "string", "description": "R2 path to /debate transcript for this session" },
    "created_ts": { "type": "string", "format": "date-time" }
  }
}

SQLite DDL (extends data/trading.db)

-- Per-event telemetry stream
CREATE TABLE pipeline_log (
  event_id          TEXT PRIMARY KEY,
  session_id        TEXT NOT NULL,
  run_id            TEXT NOT NULL,
  ticker            TEXT NOT NULL,
  node              TEXT NOT NULL,
  phase             TEXT NOT NULL,
  ts                TIMESTAMP NOT NULL,
  provider          TEXT,
  model             TEXT,
  prompt_tokens     INTEGER,
  completion_tokens INTEGER,
  cost_usd          REAL,
  latency_ms        INTEGER,
  tool_name         TEXT,
  payload_ref       TEXT,
  error_message     TEXT
);
CREATE INDEX idx_pipeline_run        ON pipeline_log(run_id, ts);
CREATE INDEX idx_pipeline_ticker_ts  ON pipeline_log(ticker, ts DESC);
CREATE INDEX idx_pipeline_session    ON pipeline_log(session_id);

-- Order lifecycle (pending → approved/rejected → submitted → filled/cancelled)
CREATE TABLE order_log (
  order_id         TEXT PRIMARY KEY,
  ticker           TEXT NOT NULL,
  session_id       TEXT NOT NULL,
  run_id           TEXT NOT NULL,
  pm_output        JSON NOT NULL,
  status           TEXT NOT NULL CHECK(status IN ('pending','approved','rejected','submitted','filled','cancelled','error')),
  alpaca_order_id  TEXT,
  created_ts       TIMESTAMP NOT NULL,
  updated_ts       TIMESTAMP NOT NULL
);
CREATE INDEX idx_order_status ON order_log(status, updated_ts DESC);

-- Operator reasoning trail (audit + institutional memory)
CREATE TABLE operator_decision_log (
  decision_id          TEXT PRIMARY KEY,
  order_id             TEXT NOT NULL,
  ticker               TEXT NOT NULL,
  session_id           TEXT NOT NULL,
  decision             TEXT NOT NULL CHECK(decision IN ('approve','reject')),
  operator_reasoning   TEXT NOT NULL CHECK(length(operator_reasoning) >= 30 AND length(operator_reasoning) <= 200),
  pm_output_snapshot   JSON NOT NULL,
  ts                   TIMESTAMP NOT NULL,
  FOREIGN KEY(order_id) REFERENCES order_log(order_id)
);
CREATE INDEX idx_decision_ticker_ts ON operator_decision_log(ticker, ts DESC);
CREATE INDEX idx_decision_session   ON operator_decision_log(session_id);

Retention policy. SQLite keeps 30d hot. n8n weekly cron archives older rows to R2: archive/parquet/{table}/{YYYY-MM}.parquet (one file per table per month), then DELETEs from SQLite WHERE ts < now-30d.


Page Specifications

/approvals — Build Phase 1 (highest priority — gates real money)

Intent. Operator-facing gate for ADR-006. Approve / reject pending orders with required reasoning. Topbar badge surfaces pending count from any page so operator cannot miss a queued approval.

Data contract. - Reads R2: pending-orders/index.json — array of pending-order.json objects, sorted by created_ts ASC. - Reads R2: operator-decisions/{date}.json for history view (last 30d). - Writes via POST {N8N_PUBLIC_URL}/webhook/approval — payload matches operator-decision.json minus decision_id + ts (n8n assigns). Resolve N8N_PUBLIC_URL from fleet SSOT (fleet_services WHERE name = 'n8n-hub'). Per global rule 10-no-hardcoded-infra.md: do NOT hardcode the URL in dashboard config; inject at build time from CF Pages env var sourced from SSOT.

Components (key functional pieces — Apollo decides layout). - Topbar approval badge (cross-page component): amber-pulsing dot + count when pending > 0; click → /approvals. Polls R2 every 5s. - Pending queue — count + list of orders awaiting approval, sorted oldest-first. - Per-order detail card — ticker, entry, stop, target, position size, PM thesis (collapsed by default), risk panel (collapsed by default), debate link to /debate?ticker=X&session=Y. - Stage gate — Approve button DISABLED until both thesis and risk panels have been expanded once during this page session. Tooltip on hover: "Expand thesis + risk to enable approval." - Reasoning field — required text input, 30-200 chars, live char counter. Approve button DISABLED until count ≥ 30. Field clears on submit. - Approve / Reject buttons — green / red. Reject also requires reason field (same 30-200 char rule). - History tab — past 30d of operator decisions. Shows ticker, decision, reasoning text, ts. Searchable by ticker.

Mobile (375px). Stage gate and reasoning field must be operable on phone. Approve button reachable without zooming.

Polling. 3s on the queue list. 5s on the topbar badge.

Edge cases. - TradingAgents writes pending-order.json while operator already on page → new card appears at bottom of queue without page reload. - Network failure on POST approval → show retry button, do NOT disable form, do NOT lose typed reasoning. - Double-click guard on Approve / Reject (debounce 1s). - Approve while operator already submitted on another tab → n8n returns 409, dashboard shows "already-decided" state.


/pipeline — Build Phase 2

Intent. Live view of TradingAgents runs across all watchlist instruments. Operator sees what's running, what's stuck, what each LLM call costs, where errors happened.

Layout pattern: D — grid summary + detail panel. - Top: 7-instrument grid (one card per watchlist ticker). Each card shows ticker, current node (e.g. "bull_researcher"), status dot (green/amber/red), elapsed time, cumulative cost USD. - Bottom: detail panel for currently-selected ticker (single-ticker focus).

Detail panel components. - Node timeline — analyst → researchers → trader → risk debators → portfolio_manager. Each node renders status (queued / running / done / errored), entry timestamp, exit timestamp, duration. - Per-node telemetry — provider badge ("openrouter:grok-4.1-fast"), token counts (prompt + completion), cost USD, latency ms. - Live LLM call log — latest 20 events for the selected ticker, newest first. Each row: timestamp, node, model, tokens, cost, latency. - Tool call log — tool_name, input snippet (1KB), output snippet (1KB), latency. - Run summary — total cost USD, total duration, errors (count + list). - Cross-page links — "View debate →" → /debate?ticker=X&session=Y. "View approvals →" → /approvals?order=X (if PM output produced an order).

Cross-page component: cost ticker. Top-right of /pipeline shows running USD total for current war room session across all tickers.

Data contract. - Reads R2: pipeline/{session_id}/{run_id}/{event_id}.json (one file per event) OR R2: pipeline/{session_id}/index.json (rollup). - Operator should expect a rollup index for grid view + per-event reads only when drilled into a specific ticker. - Reads R2: agent-state/tradingagents.json for overall pipeline daemon health.

Polling. 3s while page is active. Pause polling when document.visibilityState !== 'visible'.

Edge cases. - TradingAgents emits an error event → status dot red on grid card + error block highlighted in detail panel. - Empty state (no runs today) → all 7 cards show "idle" with last-run timestamp. - Multiple concurrent runs for same ticker → grid card shows latest; detail panel has run selector dropdown.


/debate — Build Phase 3

Intent. Audit-grade view of bull/bear/manager debate transcripts per ticker per session. Read-only. Searchable history of every prior session's debate.

Layout pattern: A — per-ticker tabs, single-ticker view. - Top: 7 ticker tabs (matches watchlist). - Right of tabs: session selector dropdown (date + time of the session). - Body: 3-column layout for the selected (ticker, session) pair.

Components. - Ticker tabs — click switches the displayed ticker. - Session selector — dropdown, lists all sessions where this ticker had a debate. Default = most recent. - 3-column transcript — bull thesis | bear thesis | research-manager synthesis. Each column scroll-independent. - Per-round expandablemax_debate_rounds config drives how many rounds rendered. Each round collapsible. - Risk debate section (below 3-column) — aggressive | conservative | neutral debators. Same 3-column shape. - PM final synthesis — full text at bottom, includes proposed entry/stop/target/size. - Per-section provider badge — small inline badge showing which LLM produced each section ("openrouter:grok-4.1-fast" for bull, "anthropic:claude-4.6" for bear, etc.). Surfaces per-node LLM routing decision visibly. - Permalink — copy URL button → /debate?ticker=AAPL&session=2026-05-04T13:30:00Z.

Data contract. - Reads R2: debate/{ticker}/{session_id}.json — single full transcript document per (ticker, session) pair. - Schema: bull rounds[], bear rounds[], research_manager_synthesis, risk_debators (aggressive/conservative/neutral), portfolio_manager_output, metadata (per-section provider/model/tokens/cost). - Written by ta-decision-log-mirror n8n workflow when PM output completes (one PUT per session per ticker).

Polling. None — debate is final once written. Page fetches once on load.

Edge cases. - Mid-session ticker has no debate yet → empty state with "No debate yet — pipeline still running. View /pipeline →" link. - Session selector empty for ticker with no historical debate → state "No prior debates for this ticker."


/agents — Build Phase 4

Intent. Full plutus fleet visibility. One page where operator sees health of every operational component — Claude Code agents, TradingAgents pipeline, n8n workflows, monitoring daemons, infrastructure.

Sections (vertical stack — Apollo decides exact layout).

  1. War Room Agents (4 trading-* Claude Code repos) - For each: name (trading-operator, trading-analyst, trading-risk, trading-execution), last session timestamp, current state (idle / running / errored), error count last 7d, skill version drift flag (boolean — is repo's skill set in sync with master spec?).

  2. TradingAgents Pipeline - Per-instrument: ticker, last run completion timestamp, total runs today, total cost today USD, error rate last 7d (% of runs that errored). - Aggregate: total cost today USD across all tickers.

  3. n8n Workflows - For each (Watchdog, Market Data Pipeline, Trade Log Pipeline, Weekly Report, Grok Briefing, CB Pipeline, Signal Intake, ta-telemetry-receiver, ta-decision-log-mirror, ta-pending-order-receiver, approval-receiver, alpaca-fill-watcher, fleet-health-aggregator):

  4. System Daemons - Argus (Pantheon governance), Hypnos (overnight reports), Talos (probe family). - Per daemon: last green check timestamp, alert count today.

  5. Infrastructure - VPS reachable, R2 reachable, Cloudflare API reachable, Alpaca paper API reachable, OpenRouter reachable. - Each: yes/no + last check timestamp.

Data contract. - Reads R2: fleet-health/index.json — single aggregate document. - Written by fleet-health-aggregator n8n workflow on cron 60s. - Aggregator queries: SQLite for trading agent state, n8n MCP for workflow status, daemon HTTP endpoints, Alpaca/R2/CF/OpenRouter ping endpoints.

Polling. 30s on /agents page.

Edge cases. - Aggregator failure (last write > 5 min ago) → top-of-page banner "Fleet aggregator stale — last update {timestamp}". - New agent / workflow added → aggregator picks it up automatically (config-driven).


n8n Workflow Specs (intent + IO contracts; n8n-hub builds JSON)

Workflow Trigger Action Failure mode
ta-telemetry-receiver Webhook POST /webhook/ta-telemetry Validate payload against pipeline-event.json. INSERT into pipeline_log. PUT to R2: pipeline/{session_id}/{run_id}/{event_id}.json. Update R2 rollup pipeline/{session_id}/index.json. Return 202 fire-and-forget. Drop event with structured error log; never block TradingAgents runtime.
ta-decision-log-mirror Local File Trigger on ~/.tradingagents/memory/trading_memory.md change Parse new entries since last watermark. INSERT new rows into trade_log. PUT to R2: decisions/{ticker}/{date}.json (one file per ticker per day, append). When PM output completes, also PUT to R2: debate/{ticker}/{session_id}.json (full transcript snapshot). Skip malformed entry, log to error sink. Re-process on next file change.
ta-pending-order-receiver Local File Trigger on /tmp/trading-war-room/{session_id}/pending-{order_id}.json create Validate against pending-order.json schema. INSERT into order_log status=pending. PUT to R2: pending-orders/index.json (rebuild full active queue). Reject malformed file, alert via Watchdog channel.
approval-receiver Webhook POST /webhook/approval (from dashboard) Validate payload against operator-decision.json (minus assigned fields). INSERT into operator_decision_log. UPDATE order_log status. IF approve → call Alpaca place_order via Alpaca MCP / Alpaca API → record alpaca_order_id. PUT to R2: operator-decisions/{date}.json. PUT updated R2: pending-orders/index.json removing this order. Return 200 on success or 409 if already-decided. If Alpaca submit fails → status=error in order_log, write to R2, dashboard surfaces error.
alpaca-fill-watcher Cron 30s Poll Alpaca paper API for fills/cancellations on outstanding submitted orders. UPDATE order_log status. PUT to R2: fills/{order_id}.json. Skip transient API errors, retry next cron tick.
fleet-health-aggregator Cron 60s Query all sources (SQLite, n8n MCP, daemons, infra ping). Compose aggregate JSON per /agents data contract. PUT to R2: fleet-health/index.json. If any source fails, mark that section as unknown with error message; still write the doc.
archive-cron Cron weekly Sunday 03:00 UTC For each table with retention policy (pipeline_log, operator_decision_log, etc.), SELECT rows older than 30d → write to R2: archive/parquet/{table}/{YYYY-MM}.parquet → DELETE from SQLite. Halt + alert if Parquet write fails; do NOT delete without successful archive.

Key constraint: all SQLite writes go through n8n. Dashboard never writes to SQLite directly. Order submissions to Alpaca never originate from dashboard — always via n8n approval-receiver. This preserves the audit invariant: every order has both an order_log row AND an operator_decision_log row before it reaches Alpaca.


Cross-Page Components (extend v1 design system)

Component Where used Behavior
Topbar approval badge All pages Amber pulsing dot + count, visible when pending > 0. Click → /approvals. Polls R2 every 5s. Hidden when count = 0.
Per-node LLM badge /pipeline, /debate Inline pill showing provider + model. Format: openrouter:grok-4.1-fast. Color-coded by provider family (optional, Apollo decides).
Cost ticker /pipeline top-right Running USD total for current war room session. Updates on every telemetry event.
Status dot /pipeline, /agents Green = healthy, amber = degraded/stale, red = errored. Same semantic as v1 brief.

Acceptance Criteria

/approvals

/pipeline

/debate

/agents

Telemetry layer (cross-cutting)


Build Order + Dependencies

Phase Page Depends on Why this order
1 /approvals TradingAgents v0.2.4 baseline running on VPS, PM-output halt wrapper, n8n approval-receiver workflow, Alpaca paper credentials wired Gates real money flow — highest stakes, ship first.
2 /pipeline TradingAgents PlutusTelemetryCallback integrated, n8n ta-telemetry-receiver workflow, R2 pipeline/* schema Visibility into runs — operator can see what's happening.
3 /debate n8n ta-decision-log-mirror writing R2 debate/{ticker}/{session_id}.json Audit trail completeness — final view of reasoning.
4 /agents All other workflows operational + fleet-health-aggregator workflow Fleet view depends on the underlying data being there.

Parallel work that doesn't block this PRD: - VPS provisioning of TradingAgents Python (Hades + Proteus task) - Per-node LLM routing patch to llm_clients/factory.py (~1 day Python task — NOT required for v1; default config uses single provider) - TradingAgents PM-output halt wrapper Python implementation - PlutusTelemetryCallback Python implementation


Out of Scope (this PRD)


Open Questions for Apollo (downstream design brief)

When the Apollo handoff is drafted (next session, separate doc), these are the design-craft decisions to delegate:

  1. Exact card layout for /pipeline 7-ticker grid
  2. Visual treatment of stage gate on /approvals (animation? glow? muted-then-unmute?)
  3. 3-column responsive behavior on /debate (collapse to single column at what breakpoint?)
  4. Per-section provider badge color treatment (one color per provider family? or neutral?)
  5. Empty state illustrations / messaging tone
  6. Cost ticker visual prominence (subtle / loud)
  7. Error-state treatment across all pages (banner / inline / toast?)

Cross-References


Status: PRD draft complete. Awaiting operator review before downstream Apollo handoff is composed.