Skip to content

Data Model

eugnmueller-87 edited this page Jun 24, 2026 · 1 revision

Data Model

Pantheon has two data contracts: the in-process typed contract (core/types.py — the only way agents talk) and the persistence schema (Supabase PostgreSQL + pgvector, plus a local SQLite fallback for dev).


In-process contract — core/types.py

The single source of truth for every inter-agent structure. Adding a field here is the only way to change the pipeline contract.

Dataclass Produced by Carries
RawSignal Icarus signal_id, source_url, headline, summary, published_at, category, severity, affected_tickers, supplier
FilteredSignal Hades wraps RawSignal + compliance_score, esg_flag, ofac_flag, downgraded, notes
MacroContext Artemis regime, vix, sp500_1m_return, sector_momentum, suppress, suppress_reason
SizedSignal Pythia wraps FilteredSignal + macro, confidence, position_size_pct, skip, is_exploration
TradeResult Ares order_id, symbol, side, fill_price, qty, stop_loss_price, take_profit_price, pnl_pct, status
DecisionTrace ZEUS full per-signal audit: every stage outcome + LLM reasoning + kill stage/reason
HealthReport Watchdog agent_name, status, message, error_count, checked_at

Enums: SignalCategory (supplier_disruption, positive_news, earnings_surprise, regulatory_action, macro_shift, neutral), Severity (LOW→CRITICAL), MarketRegime (bull/bear/sideways/unknown), PipelineStatus, AgentHealth (healthy/degraded/failed).

FilteredSignal / SizedSignal expose property pass-throughs (.category, .severity, .headline…) so downstream agents never reach into .original.


Persistence schema — Supabase (PostgreSQL + pgvector)

Initial schema is infra/supabase/001_schema.sql (11 tables); later migrations add seniority, EXP, LLM cost, and milestone state. Each pipeline stage maps to a table.

# Table Owner Purpose
1 signals Hermes Producer / Icarus raw market signals; consumed_by_icarus flag drives polling
2 filtered_signals Hades compliance results (compliance_score, ofac_flag, esg_flag, downgraded)
3 macro_context Artemis regime snapshots (vix, sp500_1m_return, sector_momentum JSONB)
4 trades Pythia + Ares every trade; context_key, confidence, position_pct, pnl_pct, hit (NULL=open)
5 decision_traces ZEUS full pipeline audit per signal, incl. killed_at_stage / kill_reason
6 portfolio_state Argus equity / peak / drawdown (singleton row — fixed from per-tick bloat)
7 portfolio_positions Argus open positions (qty, avg_cost, SL/TP, unrealized P&L)
8 agent_health Watchdog health reports; agent_health_latest view = newest per agent
9 circuit_breakers CB per-agent breaker state snapshots
10 knowledge_documents Apollo / KB pgvector store; embedding vector(1536), HNSW index, collections: knowledge / decisions / literature
11 ticker_map Apollo supplier → ticker + exchange (XETRA / NYSE / NASDAQ)

Later migrations

Migration Adds
004_seniority.sql agent_seniority, agent_seniority_history, system_seniority view, get_seniority_report() / get_promotion_history() RPCs
005_llm_usage_*.sql llm_usage (model, symbol, input/output tokens, cost_usd) + portfolio_state singleton fix
009_agent_exp.sql agent_exp_ledger, agent_exp (the cosmetic real-money XP bar — never affects sizing)
011_seniority_tiers.sql tier model refresh (the inverted, earned-upward ladder)
012_milestone_state.sql milestone_state singleton (vault_balance, total_vaulted, crossed_stages)

(A parallel supabase/migrations/ tree holds the same schema in Supabase CLI timestamped format.)


Pythia's learning view — trade_hit_rates

The heart of position sizing. A materialized view aggregating trades by context key = {category}|{regime}|{vix_band}.

CREATE MATERIALIZED VIEW trade_hit_rates AS
  SELECT context_key, category, regime, vix_band,
         COUNT(*)                                      AS total_trades,
         COUNT(*) FILTER (WHERE hit IS NOT NULL)       AS closed_trades,
         AVG(CASE WHEN hit THEN 1.0 ELSE 0.0 END)
             FILTER (WHERE hit IS NOT NULL)            AS hit_rate,
         AVG(pnl_pct) FILTER (WHERE pnl_pct IS NOT NULL) AS avg_pnl_pct
  FROM trades GROUP BY context_key, category, regime, vix_band;

Refreshed CONCURRENTLY by a trigger after any hit / pnl_pct update. Pythia looks up the row for the current context; the PromotionGate (core/shadow_learning.py) then Bayesian-shrinks the rate toward 0.50 until closed_trades ≥ 10 (see Design-Decisions).

VIX bands: low <15, medium <25, high <35, extreme ≥35.


Outcome lifecycle of a trade row

Pythia/Ares INSERT  →  hit = NULL  (open), pnl_pct = NULL
        │
        ▼  position closes (IB bracket SL/TP fires)
Argus OutcomeResolver:
   pnl_pct = (exit - fill)/fill   (negated for shorts)
   hit     = pnl_pct > 0
   UPDATE trades SET pnl_pct, hit, closed_at
   → trade_hit_rates refreshes  → Pythia's next size uses it
   → ChromaDB decision trace updated with the real outcome
   → win count feeds the seniority ladder

The resolver is restart-safe: it can rebuild open state from the DB and only backfills a close when it has a trusted IB fill price (approximate fallbacks are off by default — protecting hit-rate integrity).


Row Level Security

Every table has RLS enabled (001_schema.sql):

Role Access
service_role (backend: ZEUS, Hermes, dashboard) full read/write on all tables
anon (React dashboard, Grafana) read-only on trades, decision_traces, portfolio_state, portfolio_positions, agent_health, macro_context, signals, ticker_map

Supabase Realtime is enabled on trades, decision_traces, portfolio_state, agent_health so the dashboard updates live. New tables must add explicit GRANTs (template at infra/supabase/TEMPLATE_migration_NNN.sql) or PostgREST returns 403.


Local-dev fallback

When SUPABASE_URL / SUPABASE_SERVICE_ROLE_KEY are unset, Pythia and the seniority evaluator fall back to a local SQLite data/trade_log.db (same trades schema), and the KB uses local persistent ChromaDB. This lets the system run end-to-end with no cloud account. The seniority evaluator reads whichever backend actually holds the trades (a past bug: reading SQLite while prod wrote Supabase left every agent stuck at 0 wins).

Clone this wiki locally