Architecture¶
Doxa is composed of a FastAPI backend and a React frontend,
connected via REST and WebSocket. All simulation logic lives in server/engine/.
System Overview¶
graph TD
subgraph API["API Layer · port 5000"]
A1[FastAPI Server]
A2[REST Endpoints]
A3[WebSocket /ws/events]
end
subgraph Engine["Engine · server/engine/"]
B1[DoxaEngine]
B2[SimulationEnvironment]
B3[DoxaAgent]
B4[MarketEngine]
B5[RelationGraph]
B6[WorldEventScheduler]
B7[ConsoleLogger]
B8[DoxaChatbot]
end
subgraph Modules["Sub-modules"]
C1[AgentEconomics]
C2[AgentState]
C3[Market / Order]
C4[WorldEventEffect]
C5[RelationRecord]
end
subgraph External["Storage & External"]
D1[ChromaDB<br>RAG Memory]
D2[LLM Providers<br>OpenAI / Gemini / Grok / Ollama]
end
subgraph Frontend["Frontend · port 3000"]
F1[React + Vite]
end
F1 -->|REST| A2
F1 -->|WS| A3
A1 --> A2
A1 --> A3
A2 --> B1
A3 --> B1
B1 --> B2
B1 --> B3
B1 --> B4
B1 --> B5
B1 --> B6
B1 --> B7
B1 --> B8
B3 --> C1
B3 --> C2
B4 --> C3
B5 --> C5
B6 --> C4
B3 --> D1
B3 --> D2
B2 -- manages --> B3
B2 -- manages --> B4
B2 -- manages --> B5
B2 -- manages --> B6
B6 -- triggers --> B3
B6 -- triggers --> B4
B6 -- triggers --> B5
Simulation Loop¶
flowchart TD
A[Load YAML Scenario] --> B[Validate Schema]
B --> C[Initialize SimulationEnvironment]
C --> D[Instantiate Agents + RAG Memory]
D --> E[Initialize Markets & RelationGraph]
E --> F{Epochs × Steps}
F --> G[Apply Maintenance Costs]
G --> H[Check Kill / Victory Conditions]
H --> I[Agent Turns]
I --> I1[LLM Reasoning + RAG Query]
I1 --> I
I --> J[Market Clearing]
J --> K[Apply World Events]
K --> K1[Shock / Trend / Conditional]
K1 --> K
K --> L[Update Trust & Relations]
L --> M[Update Resource Portfolios]
M --> N{More Steps?}
N -- Yes --> G
N -- No --> O{More Epochs?}
O -- Yes --> F
O -- No --> P[Collect Results & Logs]
P --> Q[Expose via API / WebSocket]
Module Responsibilities¶
| Module | Path | Responsibility |
|---|---|---|
| DoxaEngine | engine/DoxaEngine.py |
Top-level orchestrator. Owns all subsystems; validates config; drives the epoch/step loop. |
| SimulationEnvironment | engine/SimulationEnvironment.py |
Tracks portfolios, pending trades, trade history, and RAG memory handles per agent. |
| DoxaAgent | engine/agents/DoxaAgent.py |
Wraps AutoGen ConversableAgent. Registers tools, applies constraints, holds persona. |
| AgentEconomics | engine/agents/AgentEconomics.py |
CRRA/CARA utility calculation, price-expectation EWA, liquidity-floor advisories. |
| AgentState | engine/agents/AgentState.py |
Serialises agent state into the LLM context prompt. |
| MarketEngine | engine/market/MarketEngine.py |
Manages all Market instances; routes orders; triggers clearing. |
| Market | engine/market/Market.py |
Single LOB instrument: bid/ask book, FIFO matching, price history, market-maker quoting. |
| Order | engine/market/Order.py |
Order dataclass (side, price, quantity, agent, status). |
| RelationGraph | engine/relations/RelationGraph.py |
Directed weighted trust graph; updates, decay, reclassification, serialisation. |
| RelationRecord | engine/relations/RelationRecord.py |
Single directed edge with trust score and label. |
| WorldEventScheduler | engine/events/WorldEventScheduler.py |
Evaluates triggers each tick; dispatches effects via WorldEventEffect. |
| WorldEventEffect | engine/events/WorldEventEffect.py |
Applies shock / trend / conditional effects to markets, portfolios, and trust. |
| MacroTracker | engine/MacroTracker.py |
Computes Gini, HHI, price volatility, system panic; maintains history buffer. |
| DoxaChatbot | engine/DoxaChatbot.py |
RAG-based Q&A assistant that queries the simulation's ChromaDB collections. |
| ConsoleLogger | engine/utils/ConsoleLogger.py |
Structured in-process event bus; feeds the WebSocket event queue. |
Data Flows¶
Agent Decision Cycle¶
DoxaEngine.step()
└─ DoxaAgent.run_turn()
├─ AgentState.build_prompt() → portfolio, prices, relations, tick
├─ LLM call (AutoGen) → tool calls selected by the LLM
└─ ToolDispatch
├─ place_buy_order / place_sell_order → MarketEngine
├─ make_trade_offer / accept / reject → SimulationEnvironment
├─ operate (farm, mine …) → SimulationEnvironment
├─ send_message / broadcast → RelationGraph
├─ save_knowledge / query_knowledge → ChromaDB
└─ think / report → ConsoleLogger
World Event Cascade¶
WorldEventScheduler.tick()
└─ for each event:
├─ shock → immediate one-time effect (price × multiplier, portfolio ± delta)
├─ trend → repeated delta for N ticks
└─ conditional → evaluates predicate; fires once when true
└─ WorldEventEffect.apply()
├─ market → MarketEngine.set_price()
├─ resource → SimulationEnvironment.portfolios[targets]
└─ trust → RelationGraph.update()
Security Design¶
Authentication¶
All endpoints use a single optional API key, controlled by the DOXA_API_KEY environment variable. When the variable is not set the server is fully open (development mode). When it is set, every HTTP endpoint and the WebSocket reject requests without a matching X-API-Key header (or ?api_key= query param for WebSocket). Protection is implemented as a FastAPI Depends(require_admin_api_key) dependency injected into every handler, so it is evaluated per-request and never cached at startup.
Secret Redaction¶
All responses that include parsed YAML configuration pass through sanitize_for_response(), which recursively masks any dict key named api_key, token, secret, or password with ***REDACTED*** before the response is serialised.
Path Traversal Prevention¶
POST /api/config/load resolves the submitted path relative to the repository root, enforces a .yaml/.yml extension check, then asserts the resolved path stays inside the scenarios/ directory using Path.relative_to(). Any path that escapes the directory, points to a non-YAML file, or does not exist is rejected with 400 Bad Request.
CORS¶
Allowed origins are no longer hardcoded to *. They default to a set of localhost development ports and can be overridden at runtime with the DOXA_CORS_ORIGINS environment variable (comma-separated list).
Concurrency Safety¶
DoxaEngine.event_history and resource_history are mutated only under _state_lock. All read paths (get_events, get_events_page, get_global_timeline) acquire the same lock before iterating, eliminating data races between the simulation thread and concurrent API requests. Portfolio mutations from privileged operations (godmode) are performed under env._lock, consistent with the rest of the engine.
Directory Layout¶
server/
├── api.py FastAPI application + all endpoints
└── engine/
├── DoxaEngine.py Orchestrator (≈1 200 lines)
├── SimulationEnvironment.py
├── MacroTracker.py
├── DoxaChatbot.py
├── agents/
│ ├── DoxaAgent.py
│ ├── AgentEconomics.py
│ └── AgentState.py
├── market/
│ ├── Market.py
│ ├── MarketEngine.py
│ └── Order.py
├── relations/
│ ├── RelationGraph.py
│ └── RelationRecord.py
├── events/
│ ├── WorldEventScheduler.py
│ └── WorldEventEffect.py
└── utils/
└── ConsoleLogger.py
client/
├── src/
│ ├── App.tsx
│ ├── api.ts REST + WebSocket client helpers
│ ├── EventContext.tsx Global event stream context
│ └── components/ Panel components
└── vite.config.ts