docs: update docs/agent/ with AgentOS architecture, components, conventions, and ADR 013

Co-authored-by: aguzererler <6199053+aguzererler@users.noreply.github.com>
Agent-Logs-Url: https://github.com/aguzererler/TradingAgents/sessions/7cf1fa1a-8cb0-46b4-bc20-525aa3d38d7d
This commit is contained in:
copilot-swe-agent[bot] 2026-03-23 10:48:49 +00:00
parent 1b5aee572a
commit e808031aa6
7 changed files with 282 additions and 26 deletions

View File

@ -1,34 +1,31 @@
# Current Milestone
Portfolio Manager feature fully implemented (Phases 110). All 588 tests passing (14 skipped).
AgentOS visual observability layer shipped. Portfolio Manager fully implemented (Phases 110). All 725 tests passing (14 skipped).
# Recent Progress
- **AgentOS (current PR)**: Full-stack visual observability layer for agent execution
- `agent_os/backend/` — FastAPI backend (port 8088) with REST + WebSocket streaming
- `agent_os/frontend/` — React + Vite 8 + Chakra UI + ReactFlow dashboard
- `agent_os/backend/services/langgraph_engine.py` — LangGraph event mapping engine (4 run types: scan, pipeline, portfolio, auto)
- `agent_os/backend/routes/websocket.py` — WebSocket streaming endpoint (`/ws/stream/{run_id}`)
- `agent_os/backend/routes/runs.py` — REST run triggers (`POST /api/run/{type}`)
- `agent_os/backend/routes/portfolios.py` — Portfolio REST API with field mapping (backend models → frontend shape)
- `agent_os/frontend/src/Dashboard.tsx` — 2-page layout (dashboard + portfolio), agent graph + terminal + controls
- `agent_os/frontend/src/components/AgentGraph.tsx` — ReactFlow live graph visualization
- `agent_os/frontend/src/components/PortfolioViewer.tsx` — Holdings, trade history, summary views
- `agent_os/frontend/src/components/MetricHeader.tsx` — Top-3 metrics (Sharpe, regime, drawdown)
- `agent_os/frontend/src/hooks/useAgentStream.ts` — WebSocket hook with status tracking
- `tests/unit/test_langgraph_engine_extraction.py` — 14 tests for event mapping
- Pipeline recursion limit fix: passes `config={"recursion_limit": propagator.max_recur_limit}` to `astream_events()`
- Portfolio field mapping fix: shares→quantity, portfolio_id→id, cash→cash_balance, trade_date→executed_at
- **PR #32 merged**: Portfolio Manager data foundation — models, SQL schema, module scaffolding
- `tradingagents/portfolio/` — full module: models, config, exceptions, supabase_client (psycopg2), report_store, repository
- `migrations/001_initial_schema.sql` — 4 tables (portfolios, holdings, trades, snapshots) with constraints, indexes, triggers
- `tests/portfolio/` — 51 tests: 20 model, 15 report_store, 12 repository unit, 4 integration
- Uses `psycopg2` direct PostgreSQL via Supabase pooler (`aws-1-eu-west-1.pooler.supabase.com:6543`)
- Business logic: avg cost basis, cash accounting, trade recording, snapshots
- **PR #22 merged**: Unified report paths, structured observability logging, memory system update
- **feat/daily-digest-notebooklm** (shipped): Daily digest consolidation + NotebookLM source sync
- **Portfolio Manager Phases 2-5** (implemented):
- `tradingagents/portfolio/risk_evaluator.py` — pure-Python risk metrics (log returns, Sharpe, Sortino, VaR, max drawdown, beta, sector concentration, constraint checking)
- `tradingagents/portfolio/candidate_prioritizer.py` — conviction × thesis × diversification × held_penalty scoring
- `tradingagents/portfolio/trade_executor.py` — executes BUY/SELL (SELLs first), constraint pre-flight, EOD snapshot
- `tradingagents/agents/portfolio/holding_reviewer.py` — LLM holding review agent (run_tool_loop pattern)
- `tradingagents/agents/portfolio/pm_decision_agent.py` — pure-reasoning PM decision agent (no tools)
- `tradingagents/portfolio/portfolio_states.py` — PortfolioManagerState (MessagesState + reducers)
- `tradingagents/graph/portfolio_setup.py` — PortfolioGraphSetup (sequential 6-node workflow)
- `tradingagents/graph/portfolio_graph.py` — PortfolioGraph (mirrors ScannerGraph pattern)
- 48 new tests (28 risk_evaluator + 10 candidate_prioritizer + 10 trade_executor)
- **Portfolio Manager Phases 2-5** (implemented): risk_evaluator, candidate_prioritizer, trade_executor, holding_reviewer, pm_decision_agent, portfolio_states, portfolio_setup, portfolio_graph
- **Portfolio CLI integration**: `portfolio`, `check-portfolio`, `auto` commands in `cli/main.py`
- **Documentation updated**: Flow diagram in `docs/portfolio/00_overview.md` aligned with actual 6-node sequential implementation; token estimation per model added; CLI & test commands added to README.md
# In Progress
- Refinement of macro scan synthesis prompts (ongoing)
- End-to-end integration testing with live LLM + Supabase
- None — PR ready for merge
# Active Blockers

View File

@ -1,4 +1,4 @@
<!-- Last verified: 2026-03-19 -->
<!-- Last verified: 2026-03-23 -->
# Architecture
@ -131,6 +131,70 @@ Source: `tradingagents/observability.py`
Source: `cli/main.py`, `cli/stats_handler.py`
## AgentOS — Visual Observability Layer
Full-stack web UI for monitoring and controlling agent execution in real-time.
### Architecture
```
┌──────────────────────────────────┐ ┌───────────────────────────────────┐
│ Frontend (React + Vite 8) │ │ Backend (FastAPI) │
│ localhost:5173 │◄─WS──►│ 127.0.0.1:8088 │
│ │ │ │
│ Dashboard (2 pages via sidebar) │ │ POST /api/run/{type} — queue run │
│ ├─ dashboard: graph+terminal │ │ WS /ws/stream/{run_id} — execute │
│ └─ portfolio: PortfolioViewer │ │ GET /api/portfolios/* — data │
│ │ │ │
│ ReactFlow (live agent graph) │ │ LangGraphEngine │
│ Terminal (event stream) │ │ ├─ run_scan() │
│ MetricHeader (Sharpe/regime) │ │ ├─ run_pipeline() │
│ Param panel (date/ticker/id) │ │ ├─ run_portfolio() │
│ │ │ └─ run_auto() [scan→pipe→port] │
└──────────────────────────────────┘ └───────────────────────────────────┘
```
### Run Types
| Type | REST Trigger | WebSocket Executor | Description |
|------|-------------|-------------------|-------------|
| `scan` | `POST /api/run/scan` | `run_scan()` | 3-phase macro scanner |
| `pipeline` | `POST /api/run/pipeline` | `run_pipeline()` | Per-ticker trading analysis |
| `portfolio` | `POST /api/run/portfolio` | `run_portfolio()` | Portfolio manager workflow |
| `auto` | `POST /api/run/auto` | `run_auto()` | Sequential: scan → pipeline → portfolio |
REST endpoints only queue runs (in-memory store). WebSocket is the sole executor — streaming LangGraph events to the frontend in real-time.
### Event Streaming
`LangGraphEngine._map_langgraph_event()` maps LangGraph v2 events to 4 frontend event types:
| Event | LangGraph Trigger | Content |
|-------|------------------|---------|
| `thought` | `on_chat_model_start` | Prompt text, model name |
| `tool` | `on_tool_start` | Tool name, arguments |
| `tool_result` | `on_tool_end` | Tool output |
| `result` | `on_chat_model_end` | Response text, token counts, latency |
Each event includes optional `prompt` and `response` full-text fields. Model name extraction uses 3 fallbacks: `invocation_params` → serialized kwargs → `metadata.ls_model_name`. Event mapping uses try/except per type and `_safe_dict()` helper to prevent crashes from non-dict metadata.
### Portfolio API
| Endpoint | Description |
|----------|-------------|
| `GET /api/portfolios/` | List all portfolios |
| `GET /api/portfolios/{id}` | Get portfolio details |
| `GET /api/portfolios/{id}/summary` | Top-3 metrics (Sharpe, regime, drawdown) |
| `GET /api/portfolios/{id}/latest` | Holdings, trades, snapshot with field mapping |
The `/latest` endpoint maps backend model fields to frontend shape: `Holding.shares``quantity`, `Portfolio.portfolio_id``id`, `cash``cash_balance`, `Trade.trade_date``executed_at`. Computed runtime fields (`market_value`, `unrealized_pnl`) are included from enriched Holding properties.
### Pipeline Recursion Limit
`run_pipeline()` passes `config={"recursion_limit": propagator.max_recur_limit}` (default 100) to `astream_events()`. Without it, LangGraph defaults to 25 which is too low for the debate + risk cycles.
Source: `agent_os/backend/`, `agent_os/frontend/`
## Key Source Files
| File | Purpose |
@ -138,8 +202,10 @@ Source: `cli/main.py`, `cli/stats_handler.py`
| `tradingagents/default_config.py` | All config keys, defaults, env var override pattern |
| `tradingagents/graph/trading_graph.py` | `TradingAgentsGraph` class, LLM wiring, tool nodes |
| `tradingagents/graph/scanner_graph.py` | `ScannerGraph` class, 3-phase workflow |
| `tradingagents/graph/portfolio_graph.py` | `PortfolioGraph` class, 6-node portfolio workflow |
| `tradingagents/graph/setup.py` | `GraphSetup` — agent node creation, graph compilation |
| `tradingagents/graph/scanner_setup.py` | `ScannerGraphSetup` — scanner graph compilation |
| `tradingagents/graph/portfolio_setup.py` | `PortfolioGraphSetup` — portfolio graph compilation |
| `tradingagents/dataflows/interface.py` | `route_to_vendor`, `VENDOR_METHODS`, `FALLBACK_ALLOWED` |
| `tradingagents/agents/utils/tool_runner.py` | `run_tool_loop()`, `MAX_TOOL_ROUNDS=5`, `MIN_REPORT_LENGTH=2000` |
| `tradingagents/agents/utils/agent_states.py` | `AgentState`, `InvestDebateState`, `RiskDebateState` |
@ -150,3 +216,13 @@ Source: `cli/main.py`, `cli/stats_handler.py`
| `tradingagents/report_paths.py` | Unified report path helpers (`get_market_dir`, `get_ticker_dir`, etc.) |
| `tradingagents/observability.py` | `RunLogger`, `_LLMCallbackHandler`, structured event logging |
| `tradingagents/dataflows/config.py` | `set_config()`, `get_config()`, `initialize_config()` |
| `agent_os/backend/main.py` | FastAPI app, CORS, route mounting, health check |
| `agent_os/backend/services/langgraph_engine.py` | `LangGraphEngine` — run orchestration, LangGraph event mapping |
| `agent_os/backend/routes/websocket.py` | WebSocket streaming endpoint (`/ws/stream/{run_id}`) |
| `agent_os/backend/routes/runs.py` | REST run triggers (`POST /api/run/{type}`) |
| `agent_os/backend/routes/portfolios.py` | Portfolio REST API with field mapping |
| `agent_os/frontend/src/Dashboard.tsx` | 2-page dashboard, graph + terminal + controls |
| `agent_os/frontend/src/hooks/useAgentStream.ts` | WebSocket hook, `AgentEvent` type, status tracking |
| `agent_os/frontend/src/components/AgentGraph.tsx` | ReactFlow live agent graph visualization |
| `agent_os/frontend/src/components/PortfolioViewer.tsx` | Holdings table, trade history, snapshot summary |
| `agent_os/frontend/src/components/MetricHeader.tsx` | Top-3 portfolio metrics display |

View File

@ -1,4 +1,4 @@
<!-- Last verified: 2026-03-19 -->
<!-- Last verified: 2026-03-23 -->
# Components
@ -93,6 +93,41 @@ tradingagents/
cli/
└── main.py # Typer app, MessageBuffer, Rich UI, 3 commands
agent_os/
├── __init__.py
├── DESIGN.md # Visual observability design document
├── README.md # AgentOS overview and setup instructions
├── backend/
│ ├── __init__.py
│ ├── main.py # FastAPI app, CORS, route mounting (port 8088)
│ ├── dependencies.py # get_current_user() (V1 hardcoded), get_db_client()
│ ├── store.py # In-memory run store (Dict[str, Dict])
│ ├── routes/
│ │ ├── __init__.py
│ │ ├── runs.py # POST /api/run/{scan,pipeline,portfolio,auto}
│ │ ├── websocket.py # WS /ws/stream/{run_id} — sole executor
│ │ └── portfolios.py # GET /api/portfolios/* — CRUD + summary + latest
│ └── services/
│ ├── __init__.py
│ └── langgraph_engine.py # LangGraphEngine: run_scan/pipeline/portfolio/auto, event mapping
└── frontend/
├── package.json # React 18 + Vite 8 + Chakra UI + ReactFlow
├── tsconfig.json
├── vite.config.ts
├── index.html
└── src/
├── main.tsx # React entry point
├── App.tsx # ChakraProvider wrapper
├── Dashboard.tsx # 2-page layout: dashboard (graph+terminal) / portfolio
├── theme.ts # Dark theme customization
├── index.css # Global styles
├── hooks/
│ └── useAgentStream.ts # WebSocket hook, AgentEvent type, status ref
└── components/
├── AgentGraph.tsx # ReactFlow live graph with incremental nodes
├── MetricHeader.tsx # Top-3 metrics: Sharpe, regime, drawdown
└── PortfolioViewer.tsx # Holdings table, trade history, snapshot view
```
## Agent Factory Inventory (17 factories + 1 utility)
@ -161,6 +196,27 @@ cli/
| `scan` | `run_scan(date)` | 3-phase macro scanner, saves 5 report files |
| `pipeline` | `run_pipeline()` | Full pipeline: scan JSON → filter by conviction → per-ticker deep dive |
## AgentOS Frontend Components
| Component | File | Description |
|-----------|------|-------------|
| `Dashboard` | `agent_os/frontend/src/Dashboard.tsx` | 2-page layout with sidebar (dashboard/portfolio), run buttons, param panel |
| `AgentGraph` | `agent_os/frontend/src/components/AgentGraph.tsx` | ReactFlow live graph — incremental node addition via useRef(Set) dedup |
| `MetricHeader` | `agent_os/frontend/src/components/MetricHeader.tsx` | Top-3 metrics: Sharpe ratio, market regime+beta, drawdown+VaR |
| `PortfolioViewer` | `agent_os/frontend/src/components/PortfolioViewer.tsx` | 3-tab view: holdings table, trade history, snapshot summary |
| `useAgentStream` | `agent_os/frontend/src/hooks/useAgentStream.ts` | WebSocket hook with `statusRef` to avoid stale closures |
## AgentOS Backend Services
| Service | File | Description |
|---------|------|-------------|
| `LangGraphEngine` | `agent_os/backend/services/langgraph_engine.py` | Orchestrates 4 run types, maps LangGraph v2 events to frontend events |
| `runs` router | `agent_os/backend/routes/runs.py` | REST triggers: `POST /api/run/{type}` — queues runs in memory store |
| `websocket` router | `agent_os/backend/routes/websocket.py` | `WS /ws/stream/{run_id}` — sole executor, streams events to frontend |
| `portfolios` router | `agent_os/backend/routes/portfolios.py` | Portfolio CRUD, summary metrics, holdings/trades with field mapping |
| `dependencies` | `agent_os/backend/dependencies.py` | `get_current_user()` (V1 hardcoded), `get_db_client()` |
| `store` | `agent_os/backend/store.py` | In-memory `Dict[str, Dict]` run store (demo, not persisted) |
## Test Organization
| Test File | Type | What It Covers | Markers |
@ -190,5 +246,6 @@ cli/
| `test_ttm_analysis.py` | Mixed | TTM metrics computation, report format | `integration` on live test |
| `test_vendor_failfast.py` | Unit | ADR 011 fail-fast behavior, error chaining | — |
| `test_yfinance_integration.py` | Unit | Full yfinance data layer (all mocked) | — |
| `test_langgraph_engine_extraction.py` | Unit | LangGraph event mapping, model/prompt extraction, _safe_dict helper | — |
Pytest markers: `integration` (live API), `paid_tier` (Finnhub paid subscription), `slow` (long-running). Defined in `conftest.py`.

View File

@ -1,4 +1,4 @@
<!-- Last verified: 2026-03-19 -->
<!-- Last verified: 2026-03-23 -->
# Conventions
@ -94,3 +94,17 @@
- Fail-fast by default — no silent fallback unless method is in `FALLBACK_ALLOWED`. (ADR 011)
- Alpha Vantage hierarchy: `AlphaVantageError``APIKeyInvalidError`, `RateLimitError`, `ThirdPartyError`, `ThirdPartyTimeoutError`, `ThirdPartyParseError`. (`alpha_vantage_common.py`)
- Finnhub hierarchy: `FinnhubError``APIKeyInvalidError`, `RateLimitError`, `ThirdPartyError`, `ThirdPartyTimeoutError`, `ThirdPartyParseError`. (`finnhub_common.py`)
## AgentOS Patterns
- **REST endpoints only queue runs** — WebSocket is the sole executor. POST `/api/run/{type}` writes to in-memory store, WS `/ws/stream/{run_id}` picks up and executes. (`runs.py`, `websocket.py`)
- **Event mapping is crash-proof**`_map_langgraph_event()` wraps each event type branch in try/except. `_safe_dict()` helper converts non-dict metadata to empty dict. (`langgraph_engine.py`)
- **Model name extraction** uses 3 fallbacks: `invocation_params` → serialized kwargs → `metadata.ls_model_name`. (`langgraph_engine.py`)
- **Prompt extraction** tries 5 locations: `data.messages``data.input.messages``data.input``data.kwargs.messages` → raw dump. (`langgraph_engine.py`)
- **ReactFlow nodes are incremental** — never rebuilt from scratch. `useRef(Set)` deduplication prevents duplicates. (`AgentGraph.tsx`)
- **useAgentStream uses statusRef** to avoid stale closures in WebSocket callbacks. Status is not a useCallback dependency. (`useAgentStream.ts`)
- **Pipeline recursion limit**`run_pipeline()` must pass `config={"recursion_limit": propagator.max_recur_limit}` to `astream_events()`. Default LangGraph limit of 25 is too low for debate+risk cycles. (`langgraph_engine.py`)
- **Portfolio field mapping**`/latest` endpoint maps backend model fields to frontend shape: `shares``quantity`, `portfolio_id``id`, `cash``cash_balance`, `trade_date``executed_at`. Computed fields (`market_value`, `unrealized_pnl`) included from runtime properties. (`portfolios.py`)
- **Dashboard drawer has 2 modes**`'event'` (single event detail from terminal click) and `'node'` (all events for a graph node from ReactFlow click). (`Dashboard.tsx`)
- **Run buttons track activeRunType** — only the triggered button spins, others disabled during run. (`Dashboard.tsx`)
- **Collapsible param panel** — date/ticker/portfolio_id with per-run-type validation. (`Dashboard.tsx`)

View File

@ -1,4 +1,4 @@
<!-- Last verified: 2026-03-19 -->
<!-- Last verified: 2026-03-23 -->
# Glossary
@ -109,3 +109,21 @@
| Finnhub _RATE_LIMIT | `60` calls/min | `dataflows/finnhub_common.py` |
| AV API_BASE_URL | `"https://www.alphavantage.co/query"` | `dataflows/alpha_vantage_common.py` |
| Finnhub API_BASE_URL | `"https://finnhub.io/api/v1"` | `dataflows/finnhub_common.py` |
| _MAX_CONTENT_LEN | `300` (event message truncation) | `agent_os/backend/services/langgraph_engine.py` |
| _MAX_FULL_LEN | `50_000` (full prompt/response cap) | `agent_os/backend/services/langgraph_engine.py` |
## AgentOS
| Term | Definition | Source |
|------|-----------|--------|
| AgentOS | Full-stack visual observability layer for agent execution — FastAPI backend + React frontend | `agent_os/` |
| LangGraphEngine | Backend service that orchestrates run execution and maps LangGraph v2 events to frontend events | `agent_os/backend/services/langgraph_engine.py` |
| Run Type | One of 4 execution modes: `scan`, `pipeline`, `portfolio`, `auto` | `agent_os/backend/routes/runs.py` |
| AgentEvent | TypeScript interface for frontend events: `thought`, `tool`, `tool_result`, `result`, `log`, `system` | `agent_os/frontend/src/hooks/useAgentStream.ts` |
| useAgentStream | React hook that connects to `/ws/stream/{run_id}` and provides events + status | `agent_os/frontend/src/hooks/useAgentStream.ts` |
| AgentGraph | ReactFlow-based live graph visualization of agent workflow nodes | `agent_os/frontend/src/components/AgentGraph.tsx` |
| PortfolioViewer | 3-tab portfolio view: holdings, trade history, snapshot summary | `agent_os/frontend/src/components/PortfolioViewer.tsx` |
| MetricHeader | Top-3 dashboard metrics: Sharpe ratio, market regime+beta, drawdown+VaR | `agent_os/frontend/src/components/MetricHeader.tsx` |
| _safe_dict | Helper that converts non-dict metadata to empty dict to prevent crashes | `agent_os/backend/services/langgraph_engine.py` |
| Inspector Drawer | Side panel showing full prompt/response content for an event or node | `agent_os/frontend/src/Dashboard.tsx` |
| Field Mapping | `/latest` endpoint translates backend model fields to frontend shape (shares→quantity, etc.) | `agent_os/backend/routes/portfolios.py` |

View File

@ -1,4 +1,4 @@
<!-- Last verified: 2026-03-19 -->
<!-- Last verified: 2026-03-23 -->
# Tech Stack
@ -73,3 +73,40 @@ From `[dependency-groups]`:
- Version: `0.2.1`
- Entry point: `tradingagents = cli.main:app`
- Package discovery: `tradingagents*`, `cli*`
## AgentOS Frontend Dependencies
From `agent_os/frontend/package.json`:
| Package | Constraint | Purpose |
|---------|-----------|---------|
| `react` | `^18.3.0` | UI framework |
| `react-dom` | `^18.3.0` | React DOM rendering |
| `@chakra-ui/react` | `^2.10.0` | Component library (dark theme) |
| `@emotion/react` | `^11.13.0` | CSS-in-JS for Chakra |
| `@emotion/styled` | `^11.13.0` | Styled components for Chakra |
| `framer-motion` | `^10.18.0` | Animation library (Chakra dependency) |
| `reactflow` | `^11.11.0` | Graph/DAG visualization for agent workflow |
| `axios` | `^1.13.5` | HTTP client for REST API calls |
| `lucide-react` | `^0.460.0` | Icon library |
Dev dependencies: TypeScript `^5.6.0`, Vite `^8.0.1`, ESLint `^8.57.0`, TailwindCSS `^3.4.0`.
## AgentOS Backend Dependencies
From `pyproject.toml` (additions for agent_os):
| Package | Purpose |
|---------|---------|
| `fastapi` | Web framework for REST + WebSocket backend |
| `uvicorn` | ASGI server (port 8088) |
| `httpx` | Async HTTP client (used by FastAPI test client) |
## AgentOS Build & Run
| Command | Description |
|---------|-------------|
| `uvicorn agent_os.backend.main:app --host 0.0.0.0 --port 8088` | Start backend |
| `cd agent_os/frontend && npm run dev` | Start frontend (Vite dev server, port 5173) |
| `cd agent_os/frontend && npx vite build` | Production build |
| `cd agent_os/frontend && node_modules/.bin/tsc --noEmit` | TypeScript check |

View File

@ -0,0 +1,57 @@
# ADR 013: AgentOS WebSocket Streaming Architecture
## Status
Accepted
## Context
TradingAgents needed a visual observability layer to monitor agent execution in real-time. The CLI (Rich-based) works well for terminal users but doesn't provide graph visualization or persistent portfolio views. Key requirements:
1. Stream LangGraph events to a web UI in real-time
2. Visualize the agent workflow as a live graph
3. Show portfolio holdings, trades, and metrics
4. Support all 4 run types (scan, pipeline, portfolio, auto)
## Decision
### REST + WebSocket Split
REST endpoints (`POST /api/run/{type}`) **only queue** runs to an in-memory store. The WebSocket endpoint (`WS /ws/stream/{run_id}`) is the **sole executor** — it picks up queued runs, calls the appropriate LangGraph engine method, and streams events back to the frontend.
This avoids the complexity of background task coordination. The frontend triggers a REST call, gets a `run_id`, then connects via WebSocket to that `run_id` to receive all events.
### Event Mapping
LangGraph v2's `astream_events()` produces raw events with varying structures per provider. `LangGraphEngine._map_langgraph_event()` normalizes these into 4 event types: `thought`, `tool`, `tool_result`, `result`. Each event includes:
- `node_id`, `parent_node_id` for graph construction
- `metrics` (model, tokens, latency)
- Optional `prompt` and `response` full-text fields
The mapper uses try/except per event type and a `_safe_dict()` helper to prevent crashes from non-dict metadata (e.g., some providers return strings or lists).
### Field Mapping (Backend → Frontend)
Portfolio models use different field names than the frontend expects. The `/latest` endpoint maps: `shares``quantity`, `portfolio_id``id`, `cash``cash_balance`, `trade_date``executed_at`. Computed runtime fields (`market_value`, `unrealized_pnl`) are included from enriched Holding properties.
### Pipeline Recursion Limit
`run_pipeline()` passes `config={"recursion_limit": propagator.max_recur_limit}` (default 100) to `astream_events()`. Without this, LangGraph defaults to 25, which is insufficient for the debate + risk cycles (up to ~10 iterations).
## Consequences
- **Pro**: Real-time visibility into agent execution with zero CLI changes
- **Pro**: Crash-proof event mapping — one bad event doesn't kill the stream
- **Pro**: Clean separation — frontend can reconnect to ongoing runs
- **Con**: In-memory run store is not persistent (acceptable for V1)
- **Con**: Single-tenant auth (hardcoded user) — needs JWT for production
## Source Files
- `agent_os/backend/services/langgraph_engine.py`
- `agent_os/backend/routes/websocket.py`
- `agent_os/backend/routes/runs.py`
- `agent_os/backend/routes/portfolios.py`
- `agent_os/frontend/src/hooks/useAgentStream.ts`
- `agent_os/frontend/src/Dashboard.tsx`