Skip to content

Redhound Product Roadmap

This document is forward-looking only. What shipped, and the dated decision trail explaining why, live in roadmap_history.md — including the full record for the living-thesis spine (T1/T2), the resolved SEV-0, and every correction folded in since 2026-07-03. Warnings that still govern live work were moved onto the items they govern; if an item below carries a ⚠️, that warning was paid for once already.

How to read it: priority tables (P0/P1…) group by importance; "Recommended sequencing" (bottom) is the actual order of work. The Verified basis / dependency column records what was found in the codebase at the time of writing, not what was assumed — re-verify before starting an item; code moves. (That warning is not boilerplate: on 2026-07-12 an item was picked up and found already shipped. See the stale-basis correction in the history.)

Current state (2026-07-19): the engine works end to end and the platform now has a front door — the daily brief (thin v0, PR #477) is where the spine's T1/T2 work finally reaches a user. EDGAR ingestion (#3) is COMPLETE: Form 4 (PR #481) + material 8-K / 13D-G (PR #482) ingest to catalysts and emit insider.filings / sec.filings. T3 (triggered re-analysis, the expensive tier) is now LIVE on two sources: the filing-trigger consumer (T3.1, PR #483) consumes both filing streams, and earnings-reaction (T3.2, PR #491) fires the full swarm after a held/watched symbol reports — the film's expensive tier now advances because something happened, on filings and earnings, not on a timer. Remaining on the spine: T4 (position-health / exit intelligence) — the next milestone and the top of the queue. ⚠️ #2 (historical analog finder) is SHELVED — its corpus spike returned a hard NO-GO (see the corpus-volume bottleneck note under the sequence below). ⚠️ CI has been dead since ~2026-07-14 on a GitHub billing failure (§ release engineering), so make ci-check is the only real gate and green checkmarks mean nothing.

Redhound is a multi-agent trading-analysis platform whose autonomous engine already works end to end (universe sweep → 7-preset screener → confluence-scored rank → full agent swarm → paper-track). The remaining work is not "more analysts" — it's (a) feeding the engine non-commodity data, (b) letting outcomes train the ranker, © closing the exit side of the trade loop (the platform is entry-heavy, exit-light), and (d) the foundation/compliance layer required to monetize and launch publicly. As of 2026-07-11 the value-loop work is organized under a single spine — the living thesis (§ below): today the platform produces one-shot pictures; the product is a film.

Who this is for (product compass)

The platform is structurally a swing-to-position trading copilot (days to months) for semi-pro, self-directed retail traders — people with a real brokerage account trading around a day job. The code answers this unambiguously: outcome horizons are 5d/20d/60d, scanning is overnight batch, the price feed is 30-second heartbeat polling, and the signal inputs are multi-day catalysts (earnings proximity, regime, sector rotation) — not microstructure.

Implications for prioritization: - Day trading is out of scope by construction. Don't pull tick-level streaming forward from P3; the cadence doesn't need it and the audience it would buy is smaller than it looks. - The overnight-sweep → morning-brief → alert-while-at-work loop is the product's natural shape — lean into it. Positioning line: institutional-grade research for people with day jobs. - Long-term investors are served (fundamental/valuation analysts) but are not the pulse; expiring setups, invalidation levels, and adaptive weights beat at swing tempo.


The organizing spine: the living thesis (film, not picture)

(Added 2026-07-11, product-workflow review.) The platform's central structural flaw: a session is modeled as a terminal artifact — run swarm → report → done. Re-analyzing a symbol creates an unrelated sibling session; every analysis implies its own death conditions (invalidation level, setup expiry), but nothing watches them — the user is the polling loop. The fix is not mutable sessions (immutable frames are load-bearing for the track record, outcome rows, and cost accounting) but a new layer above them: a per-symbol living thesis that links sessions as frames over time and carries state no single frame can:

  • Status — active / invalidated / expired / played-out
  • Delta — which analysts flipped since the last frame, confidence trajectory, distance to invalidation
  • Resolution — how the thesis ended (feeds outcome tracking and the analog finder at thesis level, not just signal level)

Cost model (a film is not 30 fps). Re-running the full swarm per thesis per day dies on LLM cost at any real user count. The pipeline is already stratified by cost (deterministic analysts and risk overlay are free; only the debate burns money), so the film is tiered the same way confidence-gating already is:

  1. Every scan cycle (free): price vs. invalidation level, expiry check, regime change — pure arithmetic over data the overnight sweep already fetches.
  2. Overnight (cheap): deterministic analysts re-run; consensus-flip detection vs. the previous frame (the "feed-level twin" already on this roadmap).
  3. On material change only (expensive): full swarm + debate, triggered by tier ½ (invalidation approached, analyst flip, earnings hit, filing landed) — never on a timer.

Scope discipline: the film covers held + watched symbols only — symbols the user has committed attention to. Discovery stays with the sweep; this keeps trigger fan-out bounded.

Grading policy — SETTLED in the T1 spec (2026-07-12), don't reopen it. The question was what gets graded once theses update: the original call, the latest call, or the managed sequence. The answer shipped as two ledgers: frames keep their existing 1d/5d/20d/60d grades untouched, and a thesis is graded exactly once at terminal state as a managed sequence (entry = first frame, exit = terminal price). Live theses sit in an explicit open bucket, ungraded. See docs/specs/2026-07-12-t1-living-thesis-design.md § "Grading — the two ledgers".

These are milestones of one spine, not independent widgets — one data model and one tiered loop. T1 (thesis object) and T2 (overnight flips) shipped 2026-07-13; T3 (triggered re-analysis, the expensive tier) and T4 (trim/exit/add) remain and live in P1a below.

Every major surface becomes a view of the thesis: portfolio = theses where the user holds a position (exit intelligence falls out), watchlist = theses stalked for entry, daily brief = "what happened in your theses overnight", stock page = the thesis timeline, alerts = the thesis speaking up between visits. This also fixes retention at the root: a picture gives the user no reason to return; a film that advances during the workday is the return visit — the "institutional-grade research for people with day jobs" positioning made literal: the analyst kept working while you were in a meeting.


What we have today (verified)

Analysis engine - 9 analyst specialists + deterministic signal aggregator + confidence-gated debate + zero-LLM risk overlay, run as Temporal workers over Kafka (no in-process pipeline). - Adaptive analyst weights (WeightService), pgvector agent memory (agent_memory.py), forward-tested signal track record. The leaderboard scores all 8 breakdown analysts and edge weights are adaptive on a carve-budget (PR #473). ⚠️ "Adaptive" and "track record" work only from the 2026-07-12 fixes forward — both were inert/corrupt before then. Outcome rows were regenerated clean on 2026-07-14, so the data is trustworthy now; treat any analysis of pre-2026-07-12 rows as suspect. (Why: see the signal-pipeline integrity pass and the SEV-0 notes in the history.)

Surfacesdaily brief (the front door: an OvernightBrief band at the top of Home, thin v0 shipped 2026-07-17, PR #477), stock profiles, screener, portfolio, watchlists/alerts, background scanner, opportunities feed (now the single discovery surface — catalyst is a source facet on it since PR #476; the sessions/analyses archive is demoted out of nav), Proving Ground (fund track records — rebuilt 2026-07-07, dedb9b3a9: fixed 100×-inflated returns, unified cost-basis return definition, per-track SPY alpha badge, paper-trade→session "why" links; the strategy builder was folded into this surface as a per-strategy risk-rules editor — shipped 2026-07-15, PR #474: /strategies now redirects to /fund, and each user track carries an editable four-rule set).

Infra — Temporal Schedules (job/data workers), WebSocket price feed, Prometheus/Grafana + structured logging, Sentry (backend + frontend), PostHog analytics (frontend), Resend email with unsubscribe + suppression list, cost tracking, per-IP rate limiting.


P0 — Launch blockers (monetization + compliance)

Nothing that follows can wait if the goal is a paid, EU-reachable public launch.

Item Type Effort Impact Verified basis / dependency
Billing / Stripe — wire payments; tier mutation routes through set_user_tier(source="stripe_webhook"); set REDHOUND_PLAN_SELF_SWITCH_ENABLED=false in prod Foundation M Business-critical No Stripe integration (only a TODO comment, plans.py:48). The self-switch "leak" is already gated by an env flag (plans.py:50, auth.py:385) — real gap is billing itself + confirming the prod flag is off.
Cookie-consent banner — consent gate before PostHog fires Foundation S Business-critical PostHog fully wired frontend (lib/analytics/posthog.ts, PostHogProvider.tsx); no consent UI found → EU users tracked without lawful basis. Now a blocker, not a fast-follow.
Log PII / secret redactionbefore_send scrubber for Sentry + log breadcrumbs Foundation S–M High observability/sentry.py has no before_send / scrubber. Default send_default_pii=False mitigates, but breadcrumbs/request bodies can still carry tokens/emails/broker data.
LLM prompt-injection guardrails — sanitize untrusted news before it hits the LLM; validate agent output Edge/Safety M High No sanitization/guardrail code in agents/ or orchestration/. Analysts ingest untrusted news directly → trading-specific manipulation risk.
2FA / MFA (TOTP) — second factor, esp. for broker-credential holders Foundation S–M Business-critical No TOTP/MFA anywhere in backend/. Broker creds protected by password only.

P1a — The living-thesis spine (the organizing work)

The keystone sequence. T1 (thesis object + invalidation watching) and T2 (overnight flip detection) shipped 2026-07-13 (PRs #471/#472), along with the two slices beneath them; the film exists end-to-end (frames → deltas → discovery) and reaches users via the daily brief. Full detail in the history. T3 and T4 remain — ordered by milestone, not priority.

⚠️ Constraints the shipped spine settled — inherit these, don't re-litigate them:

  • Invalidation = close-below at eval time, gated on is_market_open() — a real close, never an intraday print. Precedence is plan-verdict > price > expiry; terminal is terminal.
  • Single writer = job-worker. User-intent status stays orthogonal and API-owned.
  • Frames are immutable; sessions attach via sessions.thesis_id. ThesisService.apply_frame is worker-written only (OPEN/ATTACH/FLIP/SKIP). The lifecycle vocab is live/…, not active/… (backend/contracts/models/lifecycle.py).
  • The Kafka topics are still unconsumed (opportunity.lifecycle, ThesisUpdatedEvent/ ThesisLifecycleEvent). The daily brief was meant to be the first subscriber, but thin v0 reads denormalized DB columns instead. True event-consumption lands with the full brief (#9) — if you need a consumer before then, you are the first.
  • ⚠️ The 22:00 / 11:00 UTC offset relationship is load-bearing. ThesisRefreshWorkflow writes the delta snapshot at 22:00 UTC; the brief reads it at 11:00 UTC and works only because its window reaches back past that write — not because it fires after it (it fires 11h before). EST slack is ~1h. Retune either offset and "theses moved" silently reads 0 forever, no error.
  • Scope stays held ∪ watched symbols. Discovery belongs to the sweep; this is what keeps trigger fan-out bounded when T3 lands.
Milestone Item Effort Verified basis / dependency
T3 Event-driven scanning + trigger center — real-time triggers (gaps, filings, unusual flow) → conditional deep-analysis; "thesis watchers" as a top-level surface M–L Substrate ready (Temporal + Kafka). Triggered full-swarm re-analysis (the expensive tier of the film) lands here — the LLM re-analyzes because something happened, never on a timer. Bound to EDGAR: T3's first trigger source is the EDGAR filing stream (now shipped in P1b) — do not build the poller twice. T3 is the consumer (filing event → conditional analysis), not the ingester. ✅ First two slices SHIPPED — T3.1 filings (PR #483, 2026-07-18), T3.2 earnings-reaction (PR #491, 2026-07-19) (docs/specs/2026-07-18-t3-filing-triggers-design.md, docs/specs/2026-07-19-t3-2-earnings-reaction-triggers-design.md): stateless, backend-first. Deliberate divergence — the earlier "long-running per-thesis workflow is the textbook Temporal use case" framing was rejected: durability already lives in the theses table + daily T1 sweep + Kafka log, so a stateless event→create_and_dispatch evaluator reuses everything for a filing→dispatch flow and avoids per-thesis workflow weight. Trigger taxonomy settled 2026-07-18 (optimize for reacting to new information first): earnings-reaction = T3.2 (same axis; fires after a company reports — a cheap sweep extension, not a consumer. Renamed from "earnings-proximity" 2026-07-19: the pre-earnings posture already exists free in risk_overlay.py:125 / signal_aggregator.py:1129, and the print itself is the new information. See docs/specs/2026-07-19-t3-2-earnings-reaction-triggers-design.md.); price-proximity/exit-reactive triggers move to T4 (their real output is a trim/exit/hold judgment — a bare price-triggered re-run is "T4 in a T3 costume"; price breach is already deterministic in the T1 sweep); real-time gap/flow later. Per-user frames + thesis-watcher UI also deferred.
T4 Position-health / exit intelligence — continuously re-evaluate open positions: thesis deterioration (which analysts flipped), distance to invalidation, expiry, explicit trim/exit/add calls on portfolio M The platform is entry-heavy, exit-light: the risk overlay computes stops only at decision time (risk_overlay.py); nothing re-checks a holding afterward. Portfolio + IBKR data already exist. v1 is simply T1+T2 viewed on the portfolio; the full trim/exit/add engine is T4, after triggers (T3). Exit discipline is where swing traders actually lose P&L.

P1b — Edge & moat (data + surfaces, non-spine)

The differentiators that plug into the spine. Confluence (done) makes each more valuable.

Item Type Effort Impact Verified basis / dependency
Outcome→scanner learning loop — realized expectancy per preset × opportunity-type × regime fed back as preset weights; publish per-preset hit-rate to users Edge M Very High (moat) Outcome data exists (prediction_evaluator, action_plan, paper_trade); sweep/screener read none of it. Basis corrected 2026-07-17 — the attribution premise was wrong. Outcomes ARE reachable from their preset today: sweep_results stores best_preset + all preset_scores + session_id, and sweep_results.session_id joins to signals.session_id, which carries the graded outcome. Slice A (read-only per-preset expectancy) shipped as a query, not a migration — spec docs/specs/2026-07-17-preset-attribution-learning-loop-design.md, plan docs/plans/2026-07-17-preset-attribution-slice-a.md. Two defects found and fixed there: profit_loss is stored raw/undirected (naive AVG books winning SELLs as losses), and the sweep→session dispatch link was run-unscoped. Remaining = slice B (NOT built): preset_weights + nightly job mirroring weight_service (PR #473), blocked on the corpus-volume bottleneck — with ~10 graded-at-20d setups (0 at 60d) as of 2026-07-17, slice A's numbers can't yet be trusted to gate it. ⚠️ Known limitation: outcomes exist only for dispatched top-N, so weights risk a rich-get-richer loop — see the spec's "Known limitations".
Historical analog finder — embed each analyzed setup; at analysis time surface "N similar setups since 2025; X% resolved up within 20d, median +Y%". Also a discovery mode (2026-07-10): rank/badge the opportunities feed by similarity to past winning setups Edge S–M High pgvector, agent_memory.py, stored sessions, and (post-fix) outcome rows all exist; no similarity retrieval over past setups exists anywhere. Mostly a retrieval query over infrastructure already paid for. ⛔ SHELVED 2026-07-17 — corpus spike NO-GO. The "unblocked" basis was wrong on volume: the 2026-07-14 regeneration produced ~50 signals but only 10 matured to 20d, 0 to 60d, and a near-neighbor base rate needs thousands. Blocked on the corpus-volume bottleneck, not code (design is sound — docs/specs/2026-07-17-historical-analog-finder-design.md). Revisit when graded volume is deep.
EDGAR ingestion + confluence signal — real-time SEC poller/parser (Form 4 insider clusters, material 8-Ks, 13D/G activist stakes); upgrade the existing InsiderAnalyst to the primary source and add the 8-K/13D-G features it doesn't cover Edge M Very High SHIPPED 2026-07-18. Form 4 (PR #481, docs/specs/2026-07-17-edgar-form4-ingestion-design.md) + material 8-K / 13D-G (PR #482, docs/specs/2026-07-18-edgar-8k-13dg-catalysts-design.md). Firehose→universe poller, pure parsers, InsiderAnalyst repointed to EDGAR-primary; 8-K/13D-G land as deterministic catalysts. Both insider.filings + sec.filings streams emit, unconsumed — the T3 first slice (above) is the consumer. Scope corrected 2026-07-13 — this is not greenfield. backend/agents/analysts/insider_analyst.py already feeds insider conviction into confluence, but reads FMP's lagged, repackaged data via fetch_via_data_layer on a 30-day pull-based lookback. So the work is: (a) build EDGAR ingestion (poller + parser) — the genuinely new part; (b) repoint the analyst at it; © add 8-K/13D-G. EDGAR is free for commercial use and scales via firehose regardless of universe size; cost = parsing, not access. Breaks the single-vendor (FMP-only) dependency. Bound to T3: EDGAR's edge is latency, which a nightly confluence signal under-realizes — so have the poller emit filing events from day one, even with no consumer, exactly as slice A shipped opportunity.lifecycle ahead of its first subscriber. T3 then adds the consumer, not the ingester.
Earnings-call transcript intelligence — QoQ guidance-language and tone deltas as a new signal into confluence Edge M High No transcript code in backend/data/ (verified 2026-07-08); FMP serves transcripts on the existing platform key; the LLM pipeline + model-server already exist. The cheapest second non-commodity signal after EDGAR — no new vendor, no new infra.
Unified daily brief — one personalized morning home (overnight sweep + catalysts on holdings + fired alerts + regime + thesis status changes) Surface M High Thin v0 SHIPPED 2026-07-17 (PR #477) — the platform has a front door; the full version remains open. Thin v0: daily_briefs frozen snapshot (migration 045) + pre-market Temporal schedule (11:00 UTC, trading days) + GET /api/v1/brief + the Home band, with four DB-sourced sections and a once-per-run VIX regime. Sourcing decision (2026-07-16), still governing: v0 reads the denormalized DB columns the spine events already write (Opportunity.lifecycle_state/setup_expiry migration 040; theses.last_delta_kind snapshot migration 042; Notification) rather than subscribing to the opportunity.lifecycle/thesis Kafka topics — a once-daily pre-market batch reaches the identical user outcome, and true event-consumption + push is what buys real-time delivery, so it lands with the full version + unified notification layer (#9), not v0. The full version (learning-loop + trigger content) builds late and inherits two gaps v0 knowingly shipped — no intraday live personal state on Home, and fired alerts with no destination to link to (see #9).
Conversational research copilot — ask-anything chat/RAG over own data (portfolio, signals, sweep, vector memory) Surface L Very High No chat/copilot/RAG route today. Substrate partially exists (agent_memory.py + pgvector). The single biggest missing surface; built last, once it has real substance (track record, attribution, analogs, triggers, briefs) to interrogate.

Purpose of each P1 task

  • Position-health / exit intelligence (spine T4) — close the other half of the trade loop. Everything else answers "what should I buy and when?"; this answers "should I still be holding this?" — continuous thesis re-evaluation on open positions (analyst flips, invalidation proximity, setup expiry) with explicit trim/exit/add calls. Exit discipline is where swing traders actually lose money, and virtually no competitor does it well either.
  • Historical analog finder — turn the archive into evidence. "Setups like this one historically resolved up 62% of the time in 20d" converts a confidence score into a base rate users can trust — and it's mostly a pgvector retrieval query over data the platform already stores. Run in reverse it's also a discovery channel: surface sweep candidates because they resemble past winners, not only because a preset fired.
  • Earnings-call transcript intelligence — the second non-commodity signal, at near-zero marginal infrastructure: FMP already serves transcripts, the LLM pipeline already exists. QoQ guidance-language deltas are a real, well-studied edge that slots straight into confluence.
  • Outcome→scanner learning loop — turn a commodity screener into one that provably improves (the moat). Measures whether each preset actually made money and re-weights it, so the ranker compounds instead of firing blind forever. Publishing per-preset expectancy to users is the trust/marketing weapon almost no competitor has.
  • EDGAR ingestion + confluence signalbreak the single-vendor dependency and get a genuine information edge. Replaces FMP's lagged, repackaged data with the real-time primary source (insider Form 4 clusters, material 8-Ks, 13D/G activist stakes). You can't have an edge on data everyone else sees identically. Note the InsiderAnalyst already exists on FMP data — the new part is the ingestion layer, which is deliberately built once and consumed twice: as a confluence feature now, and as T3's first trigger source later (EDGAR's real edge is latency, which only the trigger path realizes).
  • Conversational research copilot — let users interrogate their own data instead of hunting across screens ("why did you flag AAPL?", "what's my energy exposure?"). The surface that makes Redhound feel like an analyst you talk to, not a set of dashboards.
  • Event-driven scanning + trigger center (spine T3) — shrink time-to-alert from overnight batch to minutes after it happens. Fires a targeted deep-dive the moment an event lands (gap, filing, unusual print); thesis watchers track the user's thesis so they don't have to. It consumes the EDGAR filing stream rather than building its own — see the binding note on both items.
  • Unified daily brief (full) — give the platform a home: one personalized surface the user opens every morning (overnight sweep + catalysts on holdings + fired alerts + regime). Thin v0 shipped 2026-07-17 and already delivers the daily-habit hook; the full version waits for the learning loop and triggers to give it content worth waking up to, and is where real-time (event-consumption + push) delivery lands.

Through-line: the "why now" work makes each opportunity actionable, the living thesis makes every analysis alive (a film, not a picture), exit intelligence closes the other half of the trade loop, the learning loop makes the ranker improve, EDGAR + transcripts make the input non-commodity, the analog finder makes the track record evidence, triggers make it timely, the copilot makes it interrogable, and the daily brief makes it a daily habit — converting a working engine into a product with a defensible, compounding edge.


P2 — Foundation & business (parallel track)

Item Type Effort Impact Verified basis / dependency
Plan granularity — move beyond coarse tiers to 3-tier/usage-based; gate fund, brokers, analysis depth Foundation S–M Med Tiers defined in config/plans.py; do alongside Billing (P0).
Expand universe via tiered funnel — beyond the current large-cap set into small/mid caps Edge M High Universe is 98 symbols (SCAN_SYMBOLS), not the ~600 previously assumed. Cheap deterministic screen on a wide universe → confluence rank → deep swarm on top N. Do after the learning loop makes the ranker trustworthy.
Verify & close Signal Intelligence dashboard — confidence calibration + per-analyst P&L attribution Surface S (verify) Med–High Frontend components exist (components/signals/: ConfidenceCalibrationChart, AnalystLeaderboardTable, AccuracyTimelineChart, ProfitabilityChart, YourEdgePanel, wired into app/signals/page.tsx) alongside backend routes (/track-record, /equity-curve, /leaderboard). QA must confirm the leaderboard actually populates — its query read never-written keys until fixed 2026-07-08 (fix/signal-pipeline-integrity-bugs); an end-to-end pass would have caught it. Verify with real post-fix outcome data, then close.
Strategy quick-validation replay — replay the last ~90 days of sweep snapshots under a strategy's risk/execution rules so users get same-day feedback instead of waiting weeks of paper-tracking Surface M Med–High Backtesting was removed (f42da1e85, correctly — it was dead), leaving forward paper-track (track_service.py) as the only validation. A cheap historical replay over stored sweep/screener snapshots closes the "is my strategy any good?" gap without resurrecting a backtest engine. 2026-07-14 reframe, confirmed by the shipped repoint (PR #474, 2026-07-15): analyst weights are platform-owned (PR #473), so replay scores a strategy under its four user rules (min-confidence, stop-loss, take-profit, max-hold) — replaying the exact knobs paper_portfolio_service now applies live via strategy_config_snapshot (_resolve_rules, user pct bounds ATR). The 2026-07-11 pairing rule is resolved: the dead debate.*/weight controls are deleted and the builder is folded into Proving Ground, so this replay is a clean add-on to a live rules editor rather than a crutch for a dead-end builder. Reuse, don't reimplement: replay must call the same _resolve_rules/_size_position/_compute_levels path the live track uses, or the two will drift and the replay stops predicting the paper record.
First-run onboarding / time-to-first-wow — define and instrument what a brand-new user sees in their first 5 minutes Surface S–M Med–High Signup is closed (REDHOUND_AUTH_SIGNUP_OPEN=false), so the funnel is untested. Must be defined before public launch; PostHog is already wired for measuring it (pending consent banner, P0). 2026-07-11 cap: the beginner-onramp polish track (glossary tooltips, quick-start chooser, renames — feat/beginner-onramp-quickwins) treats symptoms of the wide IA + missing front door; cap further investment there — the daily-brief front door teaches the product by using it.
PWA installability + mobile pass — web manifest, icons, audit of key pages on mobile Surface S Med–High frontend/public/sw.js exists (push) but there is no web manifest — the app is not installable and there's no native app. Alert-driven users are mobile-first; this is the cheapest way to be there for beta.
Expanded alert types — %-move, volume spike, earnings-proximity Surface S–M Med–High alert_service.py supports only price_above/price_below + signal-fired alerts. Table stakes vs competitors; this is the user-defined side of the trigger center (P1) and can ship before it.
Journal ↔ signal linkage — tighten & surface Foundation S Med (compounds) Corrected 2026-07-08: a session link already existsTradeJournalEntry.linked_session_id (bare nullable UUID) is read/written by journal_service.py and followed_signal is captured. Real gaps: (a) no FK constraint (bare UUID, no referential integrity/cascade), (b) session-only granularity — no signal/opportunity-level link, © frontend surfaces none of it (no linked_session_id usage in frontend/src). Cheap to tighten now so data accrues; the "followed vs overrode" comparison surface is post-beta. Also feeds the learning loop.
Unified notification delivery layer — email/push/webhook/Discord fanout + per-user prefs, digests, quiet hours Foundation M Med Resend + suppression + unsubscribe exist; no unified multi-channel fanout.
Personalized / portfolio-aware ranking — down-weight correlated/concentration-adding names Edge M Med–High Genuine differentiation; depends on portfolio data already tracked.
Portfolio-anchored position sizing — join risk-overlay output to the user's actual portfolio value, exposure, and concentration ("max 12 shares given your risk budget; this takes tech to 47%") Surface S–M Med–High Verified 2026-07-08: the risk overlay outputs abstract fractions only (NONE/QUARTER/HALF/FULL, risk_overlay.py:246) — never joined to tracked portfolio data. The trading-OS design (2026-03-04) promised exactly this and the data exists; it's the connective tissue between the signal engine and the portfolio surface. Pairs with personalized ranking above.
Canonical discovery surface (IA consolidation) — make Opportunities the single front door for "where do I find ideas?"; scanner + catalyst become inputs that feed it, screener a power tool linked from it Surface S–M Med–High Phase A shipped 2026-07-08 (nav + narrative): sidebar/command-palette "Research"→"Discover" group led by Opportunities; Screener/Scanner given contrasting page descriptions ("run now" vs "runs automatically and alerts you") to disambiguate the near-synonyms; mobile bottom-nav discovery slot repointed to Opportunities; opportunities empty state rewritten to teach the funnel with Scanner/Screener CTAs. Deferred (needs backend): a "promote this screen to a scan" bridge button — the screener filters ~25 range metrics (P/E, market cap, margins, growth…) while scanner custom-conditions use a different indicator vocabulary overlapping on only RSI, so a faithful bridge requires unifying the two filter vocabularies first, not just a UI button. Also still open: per-opportunity source attribution beyond the existing preset-type badge (needs scanner name on OpportunityItem — ties into preset→signal attribution for the learning loop, P1 #4). Phase B — SHIPPED 2026-07-16 (PR #476): spec docs/specs/2026-07-16-ia-phase-b-design.md. Catalyst folded into Opportunities as an indexed source facet (opportunities.source, migration 044; /catalyst/opportunities?source=catalyst); Sessions/Analyses archive demoted out of nav; three drifted nav definitions consolidated into frontend/src/config/nav.tsx11→9 destinations. Catalyst fan-out runs as a second activity on CatalystScanWorkflow (scan→discovery), reusing the thesis-discovery idempotency/never-raise discipline. Accepted trade-off: market-wide catalyst browse is gone — the feed is strictly per-user (holders ∪ watchers), so catalysts on unheld/unwatched symbols aren't browsable from nav. Still open from Phase B's original direction: merging Signals (intelligence dashboard) with Fund/Proving Ground into a single track-record surface was evaluated and deferred, not done. The Home-widget cuts Phase B deliberately deferred (ActiveSignals, OpportunitiesPreview, WatchlistMovers) were absorbed and deleted by the daily-brief band (PR #477, 2026-07-17) — cutting them before the brief existed would have removed function with no replacement.
Per-user / per-endpoint rate limiting Foundation S–M Med middleware/rate_limit.py is per-IP only (key = rate_limit:{client_ip}).
CSRF tokens for sensitive mutations Foundation S Med No CSRF found; relies on SameSite=lax alone today.
GDPR data export / portability Foundation S–M Med Account deletion exists (account_deletion_service.py); export/portability doesn't.
Bot protection on signup/waitlist Foundation S Med Plan exists (docs/plans/2026-05-30-signup-bot-protection.md); no code yet — verify.

P3 — Strategic / later

Item Type Effort Impact Verified basis / dependency
Price feed: polling → streaming Foundation M Med WS is heartbeat-polling (HEARTBEAT_INTERVAL = 30, websockets.py); not tick-level, scales with universe not usage.
Execution end of the funnel — order tickets / broker actions / live (non-paper) fund Surface L High but strategic Strategy + regulatory decision, not just a build.
Stock-profile system rewrite Foundation L Med Known debt — CLAUDE.md already says stop patching StockProfile*; design new work forward-compatible.
Multi-asset (options/crypto/futures) Edge L Strategic Caps TAM; fine if intentionally deferred.
Team / multi-seat — orgs, shared portfolios/watchlists, seat-based plans Foundation L Strategic Net-new tenancy model.
User-facing data out — personal API keys, CSV/JSON export, outbound webhooks Surface M Med–High routes/webhooks.py is inbound-only (Resend events); no user tokens or exports exist. Power-user/paid-tier feature — deliberately post-beta (scope discipline); pair with plan granularity when billing lands.
Custom screen-condition builder — expose arbitrary conditions in the UI Surface M Med Backend already accepts arbitrary condition lists (screener_engine.screen_symbols), only the 7 presets are exposed. The engine is free; the UI + support burden isn't — presets suffice for beta.

Release engineering & security depth (backlog)

Item Priority Verified basis / scope
⚠️ CI is dead — GitHub Actions billing failure P0 (ops) Verified 2026-07-15. Every run since ~2026-07-14 fails in 3–6s with "The job was not started because recent account payments have failed or your spending limit needs to be increased" (Detect changes + Notification fail; all real jobs — Backend/Frontend/Test Suite/Migration Validation — then skip). Confirmed on push dev runs and on merged PRs #472/#473/#474, so the last several merges have zero CI signal and were human-verified only (PR #474 was gated on a local make ci-check instead). Not a workflow bug — the actions/checkout@v7.0.0 pin is valid (v7.0.0 is the current release); an early diagnosis blaming the pin was wrong. Fix is in GitHub org Billing & plans, not in the repo. Until then, run make ci-check locally before every merge and treat green checkmarks as meaningless.
CD last-mile automation (deploy job) P1 Only ci.yml + dependabot-notifications.yml in .github/workflows/no deploy job. Plan: 2026-05-22-cicd-redesign.md. Blocked in practice by the CI billing outage above — there is no point automating deploys off a pipeline that cannot start a job.
Email deliverability — bounce/complaint webhooks P1 routes/webhooks.py exists — verify whether it already handles Resend bounce/complaint before building. Unsubscribe + suppression ✓.
Load / perf testing (k6/Locust) P1 Absent; establish p95 under concurrency on the analysis path.
Pre-prod verification (ephemeral CI smoke + on-demand staging) P1 Folds into CD redesign.
On-call escalation + runbook P1 Alertmanager Slack-only.
Extend audit log to state-changing actions P2 Only user.py / signal.py reference audit — no dedicated audit trail.
External dead-man's switch P2 Internal absent() alerts only; nothing pages if the obs host dies.
FMP data-redistribution licensing P2 Undocumented legal question — clarify before publishing FMP-derived data.
Configure PostHog for feature-flag kill-switches P2 Kill-switch system shipped (backend/flags/) but inert in prod until POSTHOG_PROJECT_TOKEN + POSTHOG_HOST + POSTHOG_PERSONAL_API_KEY are set (API + analyst-worker) and the four flags (debate, feed-fred, feed-news, frontend-catalyst) are created in the dashboard. Runbook: docs/content/config.md → "Operating the kill-switches".

This is the queue. Priority tables above group by importance; this is what to actually do next. Shipped items leave the queue and are logged in the history, so the numbers below are renumbered over time — never cite them from a dated document.

The ordering principle: fix what's broken → make opportunities actionable → bring the thesis to life (the spine: T1 watch → T2 diff → T3 trigger → T4 exit calls) → become chargeable → make the ranker provably improve → widen the data edge → build the habit surfaces that consume all of the above. (T1 and T2 are done; the queue picks up mid-spine.)

One deliberate exception to that principle, already taken: the daily brief's thin v0 was pulled forward and shipped early (2026-07-17, PR #477) rather than waiting for the learning loop and triggers. The reason it earned the exception: T1/T2/slice A had each shipped events with no subscriber and deferred surfaces, so the spine was computing user-visible facts that reached nobody — a habit surface with some content beat a correct-order surface with no reader. The full brief still obeys the principle and stays at #9.

🔨 In progress

Nothing actively in flight. The last two merges closed out the T3 (triggered re-analysis) milestone; the queue below picks up at the learning loop and T4.

Recently shipped (was here): - T3.1 — filing-triggered deep analysis (PR #483, 2026-07-18). A stateless filing-trigger Kafka consumer over insider.filings + sec.filings: per event, gate on attention (held ∪ watched ∪ live-thesis), a deterministic rule (8-K/13D fire directly; Form 4 only above an insider-conviction threshold), and a per-symbol cooldown, then dispatch one system-scoped full-swarm re-analysis via create_and_dispatch; the T1 result-consumer attaches the result as the next thesis frame. Spec docs/specs/2026-07-18-t3-filing-triggers-design.md. - T3.2 — earnings-reaction triggers (PR #491, 2026-07-19). A tier-selection step inside the overnight sweep: a held/watched symbol that reported within a 5-day lookback (stateless FMP /stable/earnings read) gets its cheap re-run upgraded to a full swarm — an upgrade, never a duplicate. Reuses the T3.1 cooldown/cap machinery; cooldown bypassed on read but set on write; a dedup-join leaves the idempotency key unburned. Kill-switch earnings-triggers, earnings_triggers_total{result} metric. Spec docs/specs/2026-07-19-t3-2-earnings-reaction-triggers-design.md. Post-merge watch item: the first real fire — confirm the session carries earnings extra_metadata and T1 attached it as a frame. Deferred to later T3 slices: real-time price/gap/flow triggers, per-user frames, the thesis-watcher UI.

⚠️ Corpus-volume bottleneck (surfaced 2026-07-17). The shared blocker under the whole learning side of the roadmap (#1 slice B, #2 analog finder, track record, the full brief) is setup/outcome volume: the live dev DB has 96 signals, only 10 graded at 20d and 0 at 60d. A near-neighbor base rate needs thousands. This is why #2 is shelved and why #1's slice-B validation cannot be done yet. Note the local worker stack is running and accruing (the "nothing deployed" caveat is production-only) — but slowly, and young (first data 2026-05-05). Run the graded-signal volume check before starting any corpus-consuming feature.

⚠️ Open watch item (not a task, but do not lose it): the first live 11:00 UTC daily-brief fire. If "theses moved" reads 0 while deltas exist, the window/offset relationship is wrong — and it fails silently, with no error. See the offset constraint in P1a before touching either schedule.

→ Next (forward order)

  1. Outcome→scanner learning loopHALF DONE, second half corpus-blocked. Slice A (read-only per-preset expectancy) SHIPPED (PR #479, 286a4e9c5) — a query measuring realized expectancy per preset × regime via sweep_results.session_id → signals.session_id. It only measures; it changes no scoring. Slice B — the actual loop — is NOT built: there is no preset_weights table/service and the sweep/screener reads no outcome-derived weight (still the static hardcoded condition weights + confluence_weight). Slice B (preset weights fed back to the sweep, mirroring weight_service/PR #473) is blocked on the corpus-volume bottleneck, not merely "gated on inspecting numbers": you cannot trust slice A's per-preset expectancy to gate slice B when the dev DB has ~10 graded-at-20d setups and 0 at 60d (see the bottleneck note above). Publishing per-preset hit rates to users comes after slice B — a published hit rate is very hard to walk back. ⚠️ Do not retune REDHOUND_MARKET_CONTEXT_VIX_* while validating: regimes are bucketed at read time, so a retune silently re-buckets every historical cell.
  2. Historical analog finder (P1b, S–M) — ⛔ SHELVED 2026-07-17. Design is sound (docs/specs/2026-07-17-historical-analog-finder-design.md) but the gating corpus spike is a hard NO-GO: the dev DB holds 10 graded-at-20d setups, and a near-neighbor base rate needs thousands. The prior "unblocked, mostly a retrieval query" basis was wrong on volume — the 2026-07-14 regeneration produced ~50 signals, 10 matured to 20d, 0 to 60d. Blocked on the corpus-volume bottleneck (above), not on code. Revisit when graded volume is deep.
  3. EDGAR ingestion + confluence signal (P1b, M) — ✅ SHIPPED 2026-07-18. Form 4 (PR #481) + material 8-K / 13D-G (PR #482). Firehose→filter-to-universe poller + pure parsers + insider_transactions store, InsiderAnalyst repointed to EDGAR-primary / FMP-fallback (reproducing the exact 5-field metric shape _score reads, analyst unchanged); 8-K/13D-G land as deterministic catalysts. Both insider.filings + sec.filings streams emit from day one, still unconsumed — that is what #4 (T3) subscribes to, so the ingestion is built once, not twice. First non-commodity signal; breaks the FMP-only dependency. (PR #473: insider is already the platform's best analyst at a 1.00 hit rate on FMP data — primary-source filings should sharpen it.) 3b. Earnings-call transcript intelligence — the second non-commodity signal; FMP already serves transcripts and the LLM pipeline exists, so this can trail EDGAR closely or run in parallel.
  4. T3 — Event-driven scanning + trigger center (spine) — ✅ First two slices SHIPPED. T3.1 filing-triggered deep analysis (PR #483, 2026-07-18) — a stateless filing-trigger consumer over the shipped insider.filings/sec.filings streams → attention + rule + cooldown gates → one system-scoped full-swarm re-analysis via create_and_dispatch → T1 attaches the next thesis frame. The consumer of #3's filing stream, not a second ingester; shrinks time-to-alert from overnight batch to minutes and realizes EDGAR's latency edge. T3.2 earnings-reaction (PR #491, 2026-07-19) — a tier upgrade inside the overnight sweep: a symbol that reported within a 5-day lookback gets its cheap re-run upgraded to a full swarm (docs/specs/2026-07-19-t3-2-earnings-reaction-triggers-design.md). Still open in T3: real-time gap/flow triggers, per-user triggered frames, and the thesis-watcher UI surface. Price-proximity/exit-reactive triggers are deliberately T4 (exit judgment, not a bare re-run).
  5. T4 — Position-health / exit intelligence (spine) — the trim/exit/add engine, as position-level triggers on T3. Closes the exit side of the loop. Note (2026-07-15): the user's four rules now define per-track exit mechanics (stop/target/max-hold) in paper trading; T4 is the judgment layer above them (thesis deterioration → trim/exit/add), not a second stop-loss engine. Read the rules, don't re-invent them.
  6. Universe expansion via tiered funnel (~100 symbols → small/mid caps) — only after the learning loop (#1) makes the wide-funnel ranker trustworthy; deep swarm stays on top N.
  7. Strategy quick-validation replay (P2, M) — same-day strategy feedback over stored sweep snapshots; pairs with universe expansion (both enrich the sweep archive). Unblocked by PR #474 — the four rules it replays now exist and are live in paper_portfolio_service.
  8. First-run onboarding / time-to-first-wow (P2, S–M) — define + instrument (PostHog) the first 5 minutes before opening signup. Its prerequisite is now met — the IA pass shipped (PR #476) and the front door exists (PR #477). Note (2026-07-16): thin v0 removed three Home widgets whose empty states were the only onboarding CTAs ("Run an analysis", "Set Up Scanner", "Create Watchlist"). The band's all-quiet state now carries Set Up Scanner / Create Watchlist, so the regression is closed — but that is a patch, not a first-run design. Do not cite "the daily brief is the front door" (the P2 first-run-onboarding row's 2026-07-11 cap on the beginner-onramp track) as the reason this item stays small while also deferring the brief's own onboarding gaps to it — that argument is circular. An empty account's brief says "All quiet overnight", which teaches nothing.
  9. Unified daily brief (full) — thin v0 shipped 2026-07-17 (PR #477); the full version stays late, once the learning loop (#1) and triggers (#4) give it content worth waking up to. It is also where true Kafka event-consumption + push lands (v0 is a once-daily DB-sourced batch by deliberate decision — see the P1b row). Bring the unified notification layer + portfolio-anchored sizing (P2) along so brief items arrive pre-sized. Inherits two gaps thin v0 knowingly left (2026-07-16, user-approved): (a) no intraday live personal state on Home — thin v0's band is an overnight snapshot frozen at generation, its window is [prev close, ~11:00 UTC], so the trading session itself appears in no brief; the three widgets it replaced (ActiveSignals/OpportunitiesPreview/WatchlistMovers) were live, and from ~07:00 ET Home now carries no live personal state but PortfolioSnapshot. This item's notification/trigger content is what makes a live surface worth rebuilding. (b) fired alerts have no home to link to — the brief's alerts_fired row is deliberately non-clickable because no /alerts route exists (they live in the NotificationCenter layout panel, no URL); the nullable href means giving them a real destination is a payload change, no migration.
  10. Conversational research copilot — last of the big rocks; by now it has real substance (track record, attribution, analogs, triggers, briefs) to interrogate.

⇉ Parallel tracks (do not block the sequence above)

  • P0 launch blockers — Billing/Stripe + cookie consent + PII redaction + prompt-injection guardrails + MFA. With no production environment yet, these are the gate to having prod — not a fast-follow. Start Stripe now, alongside whatever heads the queue (it was pulled alongside the front-door work, which has since shipped): it has the longest lead time (webhooks, tier lifecycle, grandfathering beta users) and nothing about it depends on the spine.
  • Beta-inclusiveness small items — PWA manifest + mobile pass; expanded alert types (%-move, volume, earnings-proximity); journal→signal schema link. Each S/S–M; slot between spine milestones. Deferred counterparts (API keys/exports, custom screen builder, full thesis timeline, followed-vs-overrode journal surface) are explicitly post-beta — see P3.
  • Foundation/security-depth backlog — CD automation, load testing, audit log, CSRF, per-user rate limiting, GDPR export, bot protection. Continuous; none of it blocks the value loop.