axon-llm::swarm Multi-Agent Orchestration¶
Applies to:
axon-llmv0.6.0+ Status: Implemented (0.6.0 Workflow B + 0.11.0 Voting Consensus) Plan:docs/superpowers/plans/2026-07-18-axon-quant-0.6.0.md§3,docs/superpowers/specs/2026-08-01-axon-quant-0.11.0-multi-agent-design.md
SwarmOrchestrator chains 4 agents (Market / Risk / Execution / Audit) into a runnable pipeline, integrated with HarnessBridge for final adjudication, and TradingBackend for real order submission to MockTradingBackend or live exchanges. Python side can construct agents, start the loop, inject signals, and read stats directly.
1. 4-Agent Architecture¶
┌────────────────────────────┐
│ SwarmOrchestrator │
│ ┌──────────────────────┐ │
│ │ run_loop_arc │ │◀──── inject_market_signal
│ │ dispatch() │ │ inject_vote_response
│ └──────────────────────┘ │
│ ConsensusManager + Stats │
└─────┬──────┬──────┬─────────┘
│ │ │ (mpsc inbox)
┌──────────────┘ │ └────────────┐
│ │ │
┌────▼────┐ ┌─────▼─────┐ ┌────▼────┐
│ Market │ ───▶ │ Risk │ ───▶ │Execution│ ──▶ PlaceOrderTool
│ Agent │ Market │ Agent │ ───▶ │ Agent │ QueryPortfolioTool
│ │ Signal │ │ Risk │ │ TradingBackend
└─────────┘ └───────────┘ Assess└─────────┘
│ │
│ ▼
│ ┌──────────┐
│ │ Audit │
│ │ Agent │
│ └──────────┘
▼
MarketDataSource (Mock / WS / CSV)
Key design points: - Each agent holds Box<dyn DeclarativeAgentRunner>, reusing DeclarativeAgent abstraction - Inter-agent communication via tokio::mpsc (no shared state) - Orchestrator main loop listens to inbox, routes by MessageContent - HarnessBridge held by SwarmOrchestrator + shared with each agent as Arc
2. Message Routing Table (inside run_loop)¶
| Incoming Message | Action |
|---|---|
MarketAnalysis(signal) | Create TradeDecision vote, broadcast VoteRequest to Risk + Execution |
RiskAssessment{approved=true} | Forward to Execution agent for ExecutionRequest |
RiskAssessment{approved=false} | Broadcast to Audit for rejection record |
ExecutionResult(result) | Broadcast to Audit |
VoteResult{passed=true} | Call HarnessBridge.adjudicate() for final decision |
VoteResponse(response) | Cast into ConsensusManager, emit VoteResult on quorum |
Shutdown | Set shutdown_requested=true, exit main loop |
Heartbeat / others | Ignored |
Voting Consensus + Harness Integration¶
VoteResponse → ConsensusManager
│
▼ SimpleMajority (≥2 votes)
VoteResult{passed=true}
│
▼
HarnessBridge.adjudicate(intent, ctx)
│
┌────────────┼────────────┬────────────┐
▼ ▼ ▼ ▼
Approved Rejected CircuitBreak NeedRevision
(execute) (audit) (shutdown) (re-analyze)
When no HarnessBridge is configured, fallback is Adjudication::Approved (zero-intrusion mode: vote passing ⇒ immediate approval).
3. Key Modules¶
3.1 DeclarativeAgentRunner trait¶
#[async_trait]
pub trait DeclarativeAgentRunner: Send + Sync {
fn id(&self) -> &AgentId;
fn role(&self) -> AgentRole;
fn status(&self) -> AgentStatus;
async fn handle_message(&mut self, msg: AgentMessage) -> Result<RunnerOutput, SwarmError>;
}
- Object safety: satisfies object-safety, allows
Arc<dyn DeclarativeAgentRunner>across tasks - Sync bound:
StatusreturnsCopy,handle_messagetakes&mut self+ async, allows concurrency
3.2 Four Agents¶
| Agent | Input | Output | Config |
|---|---|---|---|
MarketAgent | MarketDataSource (tick) | MarketSignal | symbols + price_change_threshold |
RiskAgent | MarketSignal | RiskAssessment | RiskAgentConfig (default thresholds) |
ExecutionAgent | RiskAssessment{approved=true} | ExecutionResult (via PlaceOrderTool) | TradingTools { place_order, query_portfolio } |
AuditAgent | ExecutionResult | (audit record) | AuditAgentConfig (default) |
3.3 PaperTradingBackend¶
PaperTradingBackend implements the Stage K TradingBackend trait, simulating real trading:
- Slippage:
slippage_bps(basis points, price up for buy / down for sell) - Commission:
commission_bps(on notional) - State:
cash+positions: HashMap<symbol, (qty, entry_price)>+last_prices - Price update:
place_orderupdateslast_prices[symbol] = fill_price - Cash flow: Buy deducts
notional * (1 + commission_bps), Sell addsnotional * (1 - commission_bps)
get_balance() returns cash + Σ(qty * last_price) (real-time NAV), get_positions() returns (symbol, qty, entry_price, current_price, unrealized_pnl).
3.4 SwarmOrchestrator::run_loop_arc¶
Arc<TokioMutex<SwarmOrchestrator>> shared across owners. Main loop:
pub async fn run_loop_arc(
orchestrator: Arc<TokioMutex<Self>>,
mut inbox_rx: mpsc::Receiver<AgentMessage>,
) {
let tick = Duration::from_millis(guard.config.loop_tick_ms);
loop {
if guard.shutdown_requested { break; }
let next = timeout(tick, inbox_rx.recv()).await;
match next {
Ok(Some(msg)) => dispatch(msg).await,
Ok(None) => break, // channel closed
Err(_) => continue, // timeout, recheck shutdown
}
}
}
Exit conditions: Shutdown message / request_shutdown() / inbox channel closed (all agent outboxes dropped).
4. Python Integration¶
4.1 Typical Usage¶
from axon_quant.llm import (
SwarmConfig, SwarmOrchestrator, MarketSignal, SignalType,
TradingTools,
)
from axon_quant.trading import (
MockTradingBackend, PlaceOrderTool, QueryPortfolioTool, RiskLimits,
)
# 1. Construct orchestrator
config = SwarmConfig(vote_timeout_ms=5000, loop_tick_ms=100)
orch = SwarmOrchestrator(config)
# 2. Register 4 agents (register_*_agent auto-starts run_loop)
orch.register_market_agent(agent_id="m0", symbols=["BTC-USDT"])
orch.register_risk_agent(agent_id="r0")
# ExecutionAgent must receive tools (otherwise mock mode)
backend = MockTradingBackend()
risk = RiskLimits(allowed_symbols=["BTC-USDT"])
place = PlaceOrderTool(backend=backend, mode="dry_run", risk=risk)
query = QueryPortfolioTool(backend=backend)
tools = TradingTools(place_order=place, query_portfolio=query)
orch.register_execution_agent(agent_id="e0", tools=tools)
orch.register_audit_agent(agent_id="a0")
# 3. Inject MarketSignal → orchestrator triggers vote + forward
orch.inject_market_signal(MarketSignal(
symbol="BTC-USDT",
signal_type=SignalType.Buy,
confidence=0.9,
reasoning="momentum breakout",
))
# 4. Read stats
import time; time.sleep(0.5)
stats = orch.stats()
print(stats["market_signals"], stats["votes_created"])
# 5. Shutdown
orch.stop()
4.2 Full Pipeline Demo¶
See examples/18_harness/swarm_demo.py.
5. Test Coverage¶
| Test File | Count | Content |
|---|---|---|
python/tests/test_swarm_pipeline_e2e.py | 25/25 ✅ | Enums/structs + 4 agent register + lifecycle + inject + stats |
crates/axon-llm/src/swarm/ lib unittests | all pass | DeclarativeAgentRunner / Orchestrator / Vote / 4 agents / market_data |
crates/axon-llm/src/trading/paper_backend.rs lib unittests | all pass | place_order / balance / position with slippage + commission |
Total: 322 lib unittests + 74 integration + 3 doctests + 25 Python E2E = 424 all pass.
6. Current Status and Unimplemented Items (as of 0.6.0)¶
Completed Roadmap Items (0.3.x / 0.4.x)¶
PlaceOrderToolthree modes:DryRun/TwoPhase/Directall implemented asSafetyModevariants.DryRunis the default safety mode (intentional design, prevents LLM from sending orders directly).TwoPhaseusespending: Mutex<HashMap<token, PendingOrder>>to track unconfirmed orders; 4 e2e tests cover: first call returnsconfirm_token/ second call with token sends order / unknown token rejected / token consumed once.Directcalls backend without interception.RiskAgentbasic limits:max_order_notional+quantity > 0checks implemented; compliant orders getapproved=true+risk_score=0.1+ emptyviolations, non-compliant orders include violation list.- Live exchange integration:
ExchangeTradingBackend(crates/axon-llm/src/trading/exchange.rs) fully implemented; adaptsExchangeAdapter(Binance / OKX) toTradingBackend; gated bytrading-exchangefeature, withSymbolMapproviding bidirectional LLM symbol ↔ exchange symbol mapping. Note: 8unimplemented!()sites in the same file are stubs inside a#[cfg(test)]MockAdapter(not invoked by tests), not production code gaps.
Unimplemented (0.5.0+ Roadmap)¶
RiskAgentadvanced risk logic:RiskAgentConfigdefinesmax_position/max_drawdownfields but does not check them;risk_scoreis currently binary (0.1 / 0.9); volatility / VaR / historical drawdown window / position concentration not implemented. Planned for 0.5.0: integrateaxon-risk::DefaultRiskEngine(12ns check chain, circuit breaker, VaR 95/99, real-time metrics) to replace the happy-path logic.- 4-Agent cross-process coordination (distributed swarm): Current
SwarmOrchestratoruses single-process mpsc channels; cross-process coordination,ConsensusManagerstate-machine persistence, and Ray Actor wrapping viaaxon-distributednot implemented. Planned for 0.6.0+ roadmap. - Single-agent cross-process reuse:
MarketAgent/RiskAgent/AuditAgentcurrently run as independenttokio::task::spawninstances; cross-process scheduling / shared LLM client / global prompt cache design still TBD.
7. 0.11.0 Multi-Agent Voting Consensus Layer¶
Added in v0.11.0 Location:
crates/axon-llm/src/swarm/consensus.rs
0.11.0 adds a voting consensus decision layer on top of the existing 4-Agent pipeline, implementing the full flow of independent trader decisions → weighted vote aggregation → risk veto.
7.1 Decision Flow¶
Bar arrives
│
├──→ Trader A (ReAct/rule) ──→ AgentVote{Buy, 0.8}
├──→ Trader B (ReAct/rule) ──→ AgentVote{Buy, 0.6}
└──→ Trader C (ReAct/rule) ──→ AgentVote{Hold, 0.0}
│
▼
┌─────────────────────────┐
│ VotingStrategy │
│ weighted majority: │
│ Buy = 0.8+0.6 = 1.4 │
│ Hold = 0.0 │
│ → aggregated: Buy(0.7) │
└────────────┬────────────┘
▼
┌─────────────────────────┐
│ ConsensusRiskAgent │
│ checks: │
│ - current position │
│ - consecutive losses │
│ - max position per bar │
│ → approve / veto │
└────────────┬────────────┘
▼
Final Action (Buy 0.7) or Hold (vetoed)
7.2 Core Types¶
// Vote
pub struct AgentVote {
pub agent_id: String,
pub action: TraderAction, // Buy / Sell / Hold
pub confidence: f64,
pub reasoning: String,
}
// Voting strategy trait
pub trait VotingStrategy: Send + Sync {
fn aggregate(&self, votes: &[AgentVote]) -> ConsensusDecision;
}
// Built-in strategies
// 1. WeightedMajorityVote — confidence-weighted, wins above threshold (0.5)
// 2. UnanimousVote — all votes must agree (conservative mode)
// Risk review (pure rules, no LLM)
pub struct ConsensusRiskAgent {
pub max_position: f64,
pub max_consecutive_loss: u32,
pub max_drawdown: f64,
}
// Orchestrator
pub struct VotingOrchestrator {
traders: Vec<Box<dyn TraderCallback>>,
risk_agent: ConsensusRiskAgent,
voting: Box<dyn VotingStrategy>,
}
7.3 Python Integration¶
from axon_quant._native import PyVotingOrchestrator
orch = PyVotingOrchestrator(
traders=[trader_a, trader_b, trader_c],
risk_config={"max_position": 0.5, "max_consecutive_loss": 3},
voting="weighted_majority",
)
decision = orch.on_bar(bar_dict)
# decision: {"action": ..., "votes": [...], "risk_verdict": ..., "confidence": ...}
7.4 Relationship with 0.6.0 SwarmOrchestrator¶
| Dimension | 0.6.0 SwarmOrchestrator | 0.11.0 VotingOrchestrator |
|---|---|---|
| Purpose | 4-Agent async message pipeline | Bar-by-bar synchronous voting |
| Communication | tokio mpsc async | Synchronous callback (TraderCallback) |
| Risk | RiskAgent (LLM may intervene) | ConsensusRiskAgent (pure rules) |
| Use case | Production async trading | Backtest / evaluation / multi-strategy comparison |
Both coexist in the swarm/ module without mutual dependency.
7.5 Trajectory Schema v0.11.0¶
Extended from 0.10.0 schema: - Added votes array: each trader's action + confidence + reasoning - Added aggregation field: voting strategy name + aggregated result - Added risk_verdict field: approve/veto + reason - Added token_usage field: per-bar cumulative input/output tokens