Workflows & Jobs — Execution Reference¶
Last verified: 2026-04-02 (end-to-end run against local Docker stack)
Overview¶
The Redhound production pipeline runs as isolated Temporal workers communicating through Kafka.
There are two Temporal namespaces:
| Namespace | Purpose |
|---|---|
trading |
On-demand analysis workflows (triggered by API/Kafka) |
batch |
Recurring scheduled jobs (10 Temporal Schedules, created by job-worker on startup) |
Analysis Pipeline (trading namespace)¶
AnalysisWorkflow¶
Worker: analyst-worker
Task Queue: analyst-task-queue
Trigger: Kafka analysis.trigger topic → trigger-consumer calls client.start_workflow()
Workflow ID pattern: analysis:<SYMBOL>:<event_id> (deterministic — deduplicates duplicate triggers)
Pipeline steps¶
1. FetchMarketData → data-worker (data-task-queue) max 2 min
2. 9× Analyst Activities → analyst-worker (parallel fan-out) max 3 min each
3. SignalAggregator → analyst-worker max 1 min
4. AccuracyInjector → analyst-worker max 30 sec
5. LangGraph Debate → langgraph-worker max 8 min (failure = partial result)
6. PersistResult → result-consumer (result-task-queue) max 2 min
7. PublishResult → result-consumer max 1 min
8. UpdateSession → result-consumer max 30 sec
Step details¶
| Step | Activity name | Worker | Purpose |
|---|---|---|---|
| 1 | run_fetch_market_data |
data-worker | Download OHLCV, fundamentals, news, insider, sector data; write to Redis cache |
| 2a | run_technical_analyst |
analyst-worker | Technical analysis: RSI, MACD, Bollinger, momentum, trend |
| 2b | run_sentiment_analyst |
analyst-worker | Social/news sentiment via model-server (DeBERTa) |
| 2c | run_news_analyst |
analyst-worker | News events, sentiment score, event count |
| 2d | run_fundamentals_analyst |
analyst-worker | P/E, ROE, Piotroski score, DCF valuation |
| 2e | run_market_context_analyst |
analyst-worker | Macro regime, rates, and VIX context |
| 2f | run_insider_analyst |
analyst-worker | Insider buying/selling patterns |
| 2g | run_earnings_revisions_analyst |
analyst-worker | EPS/revenue estimate revision trends |
| 2h | run_sector_analyst |
analyst-worker | Sector momentum relative to broad market |
| 2i | run_valuation_comparables_analyst |
analyst-worker | Relative valuation against peer comparables |
| 3 | run_signal_aggregator |
analyst-worker | Merge 9 analyst scores using entropy-based confidence, VIX interpolation, RSI divergence, OBV confirmation |
| 4 | run_accuracy_injector |
analyst-worker | Weight each analyst contribution by historical accuracy from leaderboard |
| 5 | run_langgraph |
langgraph-worker | Bull/Bear LLM debate (1 round if confidence ≥75%, else 2; Starter plan skips it) |
| 6 | run_persist_result |
result-consumer | Write final signal, decision, confidence to PostgreSQL + TimescaleDB |
| 7 | run_publish_result |
result-consumer | Publish analysis.result Kafka event for downstream consumers |
| 8 | run_update_session |
result-consumer | Update analysis session record in DB (status, completion time) |
Output fields¶
| Field | Type | Example |
|---|---|---|
signal |
float [-1.0, 1.0] | -0.6 (bearish) |
confidence |
float [0.0, 1.0] | 1.0 |
decision |
string | BUY / SELL / HOLD |
agents_completed |
list[str] | ["news", "sector", ...] |
agents_failed |
list[str] | ["technical"] (rate-limited, etc.) |
status |
string | complete / partial |
Partial result: If any analysts fail or LangGraph fails, the workflow continues with available data and sets status=partial. It never silently drops the entire run.
API-side result delivery¶
Once run_publish_result emits AnalysisCompletedEvent to Kafka, the API process closes the loop back to the browser:
trigger-consumer(Kafka consumer, not a Temporal worker) bridgesanalysis.trigger→client.start_workflow(AnalysisWorkflow, ...).result-consumerpersists the result and publishesAnalysisCompletedEvent→analysis.completed.backend/api/workers_event_consumer.py(runs inside the API process) consumesanalysis.completed, waits for the DB commit, then pushes WebSocketOUTPUT+STATUS_CHANGE:COMPLETEDto connected frontend clients. It also records debate votes and evaluates signal alerts (best-effort).
The API never runs the pipeline itself — it only publishes triggers and consumes result events.
Recurring Jobs (batch namespace)¶
All job Temporal Schedules are created by job-worker at startup (_ensure_schedules()).
Overlap policy: SKIP — if a run is still executing when the next interval fires, the new run is skipped.
Retry policy: 3 attempts, initial backoff 5 s, max 10 min per execution.
Schedule Summary¶
| Schedule ID | Workflow | Interval | Purpose |
|---|---|---|---|
price-checks |
PriceCheckWorkflow | every 1 min | Price alerts |
scanner-checks |
ScannerCheckWorkflow | every 1 min | Scanner conditions |
outcome-tracking |
OutcomeTrackingWorkflow | every 5 min | Signal outcome classification |
index-intraday |
IndexIntradayWorkflow | every 5 min | Intraday index constituent refresh |
hotlist-monitor |
HotlistMonitorWorkflow | every 15 min | Open paper position monitoring |
catalyst-scan |
CatalystScanWorkflow | every 30 min | Options flow + short squeeze + earnings scan |
weight-update |
WeightUpdateWorkflow | every 60 min | Adaptive analyst weight recalculation |
universe-sweep |
UniverseSweepWorkflow | every 24 h | Full symbol universe pre-market sweep |
action-plan-eval |
ActionPlanEvalWorkflow | every 24 h | Daily action plan evaluation |
index-eod |
IndexEodWorkflow | every 24 h | End-of-day full index constituent download |
Job Details¶
price-checks — PriceCheckWorkflow (every 1 min)¶
Activity: run_price_checks
Checks all active price-type alerts against live market prices. Fires notifications when a configured price threshold is crossed (e.g., "alert me when NVDA > $900").
scanner-checks — ScannerCheckWorkflow (every 1 min)¶
Activity: run_scanner_checks
Polls all active scanner configs and executes their scan cycles. Scanners define multi-condition filters (volume spike + RSI oversold, etc.) over the symbol universe; matches are surfaced to the user.
outcome-tracking — OutcomeTrackingWorkflow (every 5 min)¶
Activity: run_outcome_tracking
Fetches current prices for all pending (unresolved) signals and classifies their outcome as WIN / LOSS / NEUTRAL based on price movement since signal issuance. Results feed the analyst performance leaderboard.
index-intraday — IndexIntradayWorkflow (every 5 min)¶
Activity: run_index_intraday
Refreshes index constituent data (S&P 500, NASDAQ-100, etc.) during market hours. Keeps the tracked symbol universe current for intraday scanning.
hotlist-monitor — HotlistMonitorWorkflow (every 15 min)¶
Activity: run_hotlist_monitor
Monitors open paper trading positions against live prices. Checks stop-loss and take-profit levels, emits alerts when positions approach or breach those thresholds.
catalyst-scan — CatalystScanWorkflow (every 30 min)¶
Activity: run_catalyst_scan
Scans for high-impact catalysts across the universe: unusual options flow (large call/put sweeps), potential short-squeeze setups (high short interest + borrow rate), and upcoming earnings events within the next 7 days.
weight-update — WeightUpdateWorkflow (every 60 min)¶
Activity: run_weight_update
Recalculates each analyst's adaptive weight using the performance leaderboard. Analysts with higher historical accuracy on WIN classifications get higher weight in signal aggregation, improving overall signal quality over time.
universe-sweep — UniverseSweepWorkflow (every 24 h)¶
Activity: run_universe_sweep
Pre-market sweep of the full symbol universe. Identifies qualifying symbols (volume, market cap, momentum filters) and dispatches AnalysisTriggerEvent Kafka messages for each, seeding the daily analysis queue.
action-plan-eval — ActionPlanEvalWorkflow (every 24 h)¶
Activity: run_action_plan_eval
Evaluates all open ActionPlans against live prices. ActionPlans represent structured trade setups (entry, target, stop) generated by the LLM debate; this job checks whether plan conditions have been met or invalidated.
index-eod — IndexEodWorkflow (every 24 h)¶
Activity: run_index_eod
End-of-day download of full index constituent lists (S&P 500, NASDAQ-100, Russell 2000, etc.) from authoritative sources. Overwrites stale membership data to reflect rebalances and additions/removals.
Worker → Task Queue Mapping¶
| Worker | Task Queue | Namespace | Workflows registered | Activities registered |
|---|---|---|---|---|
analyst-worker |
analyst-task-queue |
trading |
AnalysisWorkflow |
9 analyst activities + signal aggregator + accuracy injector |
langgraph-worker |
langgraph-task-queue |
trading |
— | run_langgraph |
data-worker |
data-task-queue |
trading |
— | run_fetch_market_data |
result-consumer |
result-task-queue |
trading |
— | run_persist_result, run_publish_result, run_update_session |
job-worker |
job-task-queue |
batch |
All 10 job workflows | All 10 job activities |
trigger-consumer |
(Kafka consumer) | — | — | Bridges analysis.trigger Kafka → trading namespace AnalysisWorkflow |
Verification Results (2026-04-02)¶
| Component | Status | Evidence |
|---|---|---|
| Backend API | Healthy | GET /health → {"status":"healthy","dependencies":{"database":"healthy","redis":"healthy"}} |
Temporal (trading) |
Running | AnalysisWorkflow for NVDA completed: status=COMPLETED, decision=HOLD, signal=-0.6, confidence=1.0 |
Temporal (batch) |
Running | All 10 schedules active, recent execution times confirmed in Temporal UI |
| Kafka | Running | analysis.trigger topic reachable at kafka:9092 (internal) / localhost:29092 (host) |
| LangGraph debate | Working | run_langgraph completed and returned LangGraphActivityOutput correctly |
| Result persistence | Working | NVDA analysis written to DB + published to Kafka analysis.result topic |
| Model server (gRPC) | Running | Sentiment analyst connected via REDHOUND_MODEL_SERVER_ADDR |
| 9 analyst activities | completed | A single analyst failing on a transient vendor rate limit is expected and retried on the next run; the workflow proceeds with status=partial |