8.1 KiB
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.pynow owns public task/job projection logic;web_dashboard/backend/services/result_store.pypersists result contracts underresults/<task_id>/result.v1alpha1.json;web_dashboard/backend/services/analysis_service.pyandapi/portfolio.pyalready expose contract-first result payloads by default;- task lifecycle query/command routing for
status/list/cancelnow sits behind backend task services instead of route-local orchestration inmain.py; /ws/analysis/{task_id}and/ws/orchestratoralready carrycontract_version = "v1alpha1"and include result/degradation/data-quality metadata.
What is not fully finished yet:
web_dashboard/backend/main.pystill 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:
- transport concerns: FastAPI routes, headers, WebSocket sessions, task persistence;
- application orchestration: task lifecycle, stage progress, subprocess wiring, result projection;
- 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.pyowns quant runner + LLM runner composition.orchestrator/signals.pyownsSignal,FinalSignal, and merge math.orchestrator/live_mode.pyowns 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
FinalSignalto UI-oriented fields such asdecision,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:
TradingOrchestratorSignalMergerQuantRunnerLLMRunnerTradingAgentsGraph
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
transport adapter -> application service -> domain kernel
transport adapter -> application service -> persistence adapter
application service -> result contract mapper
Forbidden direction:
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) -> AnalysisTaskAcceptedget_analysis_status(task_id) -> AnalysisTaskStatuscancel_analysis(task_id) -> AnalysisTaskStatusrun_live_signals(request) -> LiveSignalBatchlist_analysis_tasks() -> AnalysisTaskListget_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:
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.pyweb_dashboard/backend/services/result_store.pyweb_dashboard/backend/services/analysis_service.pyweb_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.