TradingAgents/docs/architecture/application-boundary.md

229 lines
8.1 KiB
Markdown

# TradingAgents architecture convergence draft: application boundary
Status: draft
Audience: backend/dashboard/orchestrator maintainers
Scope: define the boundary between HTTP/WebSocket delivery, application service orchestration, and the quant+LLM merge kernel
## Current status snapshot (2026-04)
This document is still the **target boundary** document, but several convergence pieces are already landed on the mainline:
- `web_dashboard/backend/services/job_service.py` now owns public task/job projection logic;
- `web_dashboard/backend/services/result_store.py` persists result contracts under `results/<task_id>/result.v1alpha1.json`;
- `web_dashboard/backend/services/analysis_service.py` and `api/portfolio.py` already expose contract-first result payloads by default;
- task lifecycle query/command routing for `status/list/cancel` now sits behind backend task services instead of route-local orchestration in `main.py`;
- `/ws/analysis/{task_id}` and `/ws/orchestrator` already carry `contract_version = "v1alpha1"` and include result/degradation/data-quality metadata.
What is **not** fully finished yet:
- `web_dashboard/backend/main.py` still contains too much orchestration glue and transport-local logic outside the task lifecycle slice;
- route handlers are thinner than before, but the application layer has not fully absorbed reports/export and every remaining lifecycle branch;
- migration flags/modes still coexist with legacy compatibility paths.
## 1. Why this document exists
The current backend mixes three concerns inside `web_dashboard/backend/main.py`:
1. transport concerns: FastAPI routes, headers, WebSocket sessions, task persistence;
2. application orchestration: task lifecycle, stage progress, subprocess wiring, result projection;
3. domain execution: `TradingOrchestrator`, `LiveMode`, quant+LLM signal merge.
For architecture convergence, these concerns should be separated so that:
- the application service remains a no-strategy orchestration and contract layer;
- `orchestrator/` remains the quant+LLM merge kernel;
- transport adapters can migrate without re-embedding business rules.
## 2. Current evidence in repo
### 2.1 Merge kernel already exists
- `orchestrator/orchestrator.py` owns quant runner + LLM runner composition.
- `orchestrator/signals.py` owns `Signal`, `FinalSignal`, and merge math.
- `orchestrator/live_mode.py` owns batch live execution against the orchestrator.
This is the correct place for quant/LLM merge semantics.
### 2.2 Backend currently crosses the boundary
`web_dashboard/backend/main.py` currently also owns:
- analysis subprocess template creation;
- stage-to-progress mapping;
- conversion from `FinalSignal` to UI-oriented fields such as `decision`, `quant_signal`, `llm_signal`, `confidence`;
- report materialization into `results/<ticker>/<date>/complete_report.md`.
This makes the transport layer hard to replace and makes result contracts implicit.
At the same time, current mainline no longer matches the oldest “all logic sits in routes” description exactly. The codebase now sits in a **mid-migration** state:
- merge semantics remain in `orchestrator/`;
- public payload shaping has started moving into backend services;
- task lifecycle query/command paths now route through backend task services;
- legacy compatibility fields still exist for UI safety.
## 3. Target boundary
## 3.1 Layer model
### Transport adapters
Examples:
- FastAPI REST routes
- FastAPI WebSocket endpoints
- future CLI/Tauri/worker adapters
Responsibilities:
- request parsing and auth
- response serialization
- websocket connection management
- mapping application errors to HTTP/WebSocket status
Non-responsibilities:
- no strategy logic
- no quant/LLM weighting logic
- no task-stage business rules beyond rendering application events
### Application service
Suggested responsibility set:
- accept typed command/query inputs from transport
- orchestrate analysis execution lifecycle
- map domain results into stable result contracts
- own task ids, progress events, persistence coordination, and rollback-safe migration switches
- decide which backend implementation to call during migration
Non-responsibilities:
- no rating-to-signal research logic
- no quant/LLM merge math
- no provider-specific data acquisition details
### Domain kernel
Examples:
- `TradingOrchestrator`
- `SignalMerger`
- `QuantRunner`
- `LLMRunner`
- `TradingAgentsGraph`
Responsibilities:
- produce quant signal, LLM signal, merged signal
- expose domain-native dataclasses and metadata
- degrade gracefully when one lane fails
## 3.2 Canonical dependency direction
```text
transport adapter -> application service -> domain kernel
transport adapter -> application service -> persistence adapter
application service -> result contract mapper
```
Forbidden direction:
```text
transport adapter -> domain kernel + ad hoc mapping + ad hoc persistence
```
## 4. Proposed application-service interface
The application service should expose typed use cases instead of letting routes assemble logic inline.
## 4.1 Commands / queries
Suggested surface:
- `start_analysis(request) -> AnalysisTaskAccepted`
- `get_analysis_status(task_id) -> AnalysisTaskStatus`
- `cancel_analysis(task_id) -> AnalysisTaskStatus`
- `run_live_signals(request) -> LiveSignalBatch`
- `list_analysis_tasks() -> AnalysisTaskList`
- `get_report(ticker, date) -> HistoricalReport`
## 4.2 Domain input boundary
Inputs from transport should already be normalized into application DTOs:
- ticker
- trade date
- auth context
- provider/config selection
- execution mode
The application service may choose subprocess/backend/orchestrator execution strategy, but it must not redefine domain semantics.
## 5. Boundary rules for convergence work
### Rule A: result mapping happens once
Current code maps `FinalSignal` to dashboard fields inside the analysis subprocess template. That mapping should move behind a single application mapper so REST, WebSocket, export, and persisted task status share one contract.
### Rule B: stage model belongs to application layer
Stage names such as `analysts`, `research`, `trading`, `risk`, `portfolio` are delivery/progress concepts, not merge-kernel concepts. Keep them outside `orchestrator/`.
### Rule C: orchestrator stays contract-light
`orchestrator/` should continue returning `Signal` / `FinalSignal` and domain metadata. It should not learn about HTTP status, WebSocket payloads, pagination, or UI labels beyond domain rating semantics already present.
### Rule D: transport only renders contracts
Routes should call the application service and return the already-shaped DTO/contract. They should not reconstruct `decision`, `quant_signal`, `llm_signal`, or progress math themselves.
## 6. Suggested module split
One viable split:
```text
web_dashboard/backend/
application/
analysis_service.py
live_signal_service.py
report_service.py
contracts.py
mappers.py
infra/
task_store.py
subprocess_runner.py
report_store.py
api/
fastapi_routes remain thin
```
This keeps convergence local to backend/application without moving merge logic out of `orchestrator/`.
## 7. Non-goals
- Do not move signal merge math into the application service.
- Do not turn the application service into a strategy engine.
- Do not require frontend-specific field naming inside `orchestrator/`.
- Do not block migration on a full rewrite of existing routes.
## 8. Review checklist
A change respects this boundary if all are true:
- route handlers mainly validate/auth/call service/return contract;
- application service owns task lifecycle and contract mapping;
- `orchestrator/` remains the only owner of merge semantics;
- domain dataclasses can still be tested without FastAPI or WebSocket context.
## 9. Current maintainer guidance
When touching backend convergence code, treat these files as the current application-facing boundary:
- `web_dashboard/backend/services/job_service.py`
- `web_dashboard/backend/services/result_store.py`
- `web_dashboard/backend/services/analysis_service.py`
- `web_dashboard/backend/api/portfolio.py`
If a change adds or removes externally visible fields, update `docs/contracts/result-contract-v1alpha1.md` in the same change set.