Saltar al contenido principal

ADR 018 — Canonical execution aggregate

  • Status: Accepted-in-direction (2026-08-03) via the ARP review: the implemented OrderIntentService + domain_orders path stays the persisted truth path; the bots frozen model is promoted as wire/contract shape only, inside the Trading & Ledger context on the alphaswarm#212 outbox-unification track; no service extraction. Supersedes the Proposed (2026-06-19) E5/Wave-2 gating. See alphaswarm_internal docs/architecture/agent-first-research-platform/02-context-map-and-ownership.md §6 (binding disposition table) and 10-adrs.md.
  • Authors: Platform team
  • Related: Enhancement Guide §6/E5, ADR 008, ADR 015; Hard Rule 1 (Symbol/vt_symbol)

Context​

Two order models coexist:

alphaswarm_botsmonolith alphaswarm
OrderNewOrder / OrderRef (schemas/trading.py, msgspec.Struct(frozen=True))DomainOrder dataclass (core/domain/orders.py) + Pydantic OrderIntent (trading/order_intent.py)
StateGuarded OrderFSM (execution/lifecycle.py, _VALID_FORWARD, idempotent)Unguarded apply_report() fold (trading/execution/order_state.py)
FillFill (mandatory exec_id)implicit
Portfolioimplicit PositionProjection / PnLProjectioncore/domain/positions.py
Execution policyExecutionLayerSpec + ExecutionAlgorithm ABC—

The result: OrderIntent → ExecutionOrder → Fill is split across two stacks with two grammars; there is no single canonical aggregate, no explicit Portfolio aggregate (it is an emergent projection), and no ExecutionPolicy aggregate (the closest artifact is a config object, ExecutionLayerSpec). This is the memo's "single canonical aggregate" gap.

Decision​

Adopt one canonical execution aggregate, promoting the stronger model and demoting the weaker fold.

  1. ExecutionOrder (canonical). Promote the alphaswarm_bots frozen, guarded, idempotent model (OrderFSM + msgspec structs, post-Wave-1 exec_id idempotency and injected clock) as the canonical venue-routable order. Replace the monolith trading/execution/order_state.py fold with the guarded OrderFSM.
  2. OrderIntent as pre-trade intent. Keep the monolith Pydantic OrderIntent as the desire to trade (sizing mode + rationale) that a risk-approved ExecutionOrder is derived from — the LEAN Insight → PortfolioTarget → Order separation.
  3. Explicit Portfolio aggregate. Wrap the existing PositionProjection / PnLProjection in a Portfolio aggregate with an apply_fill command that carries design-by-contract invariants (Enhancement E4): positions reconcile to fills, no money created/destroyed, exposure within configured limits.
  4. Explicit ExecutionPolicy value object. Wrap ExecutionLayerSpec + ExecutionAlgorithm selection (TWAP/VWAP/POV/IS/Iceberg) as a first-class policy with a deterministic plan(intent, state) → ExecutionPlan contract.
  5. One definition, two stacks. Extract the canonical types into a shared boundary (alphaswarm_core or a dedicated execution package) so both the bots runtime and the monolith import one definition; deprecate the monolith fold behind a shim.

Consequences​

Positive

  • One vocabulary from signal to fill; the guarded, idempotent FSM becomes the only order state machine; Portfolio and ExecutionPolicy become testable aggregates with invariants rather than emergent behavior.
  • Unblocks E4 (contracts on Portfolio.apply_fill) and E1/E2 (a single FSM to model and dedup).

Negative / risks

  • Touches both stacks; must proceed one axis at a time (unify types before unifying runtimes) and behind deprecation shims, per ADR 015.
  • msgspec (bots hot path) vs. Pydantic (monolith) interop at the boundary needs a thin adapter; keep the hot-path type msgspec and expose a Pydantic view for API/persistence.

Explicitly rejected

  • Maintaining two parallel order models indefinitely.
  • Promoting the unguarded monolith fold (it lacks transition safety).

Rollout order​

  1. Extract canonical ExecutionOrder + OrderFSM to the shared boundary (types only).
  2. Introduce Portfolio and ExecutionPolicy aggregates over existing projections/specs.
  3. Replace the monolith fold with the guarded FSM behind a shim; deprecate.