2323//! cargo run --example trading_pipeline
2424//!
2525//! KVM mode (requires kernel + initramfs):
26- //! VOID_BOX_KERNEL=/boot/vmlinuz-$(uname -r) \
27- //! VOID_BOX_INITRAMFS=/tmp/void-box-test-rootfs.cpio.gz \
28- //! cargo run --example trading_pipeline
26+ //! 1. Build the guest initramfs:
27+ //! ```
28+ //! CLAUDE_CODE_BIN=$(which claude) BUSYBOX=/usr/bin/busybox \
29+ //! scripts/build_guest_image.sh
30+ //! ```
31+ //! 2. Run with Anthropic API:
32+ //! ```
33+ //! ANTHROPIC_API_KEY=sk-ant-xxx \
34+ //! VOID_BOX_KERNEL=/boot/vmlinuz-$(uname -r) \
35+ //! VOID_BOX_INITRAMFS=/tmp/void-box-rootfs.cpio.gz \
36+ //! cargo run --example trading_pipeline
37+ //! ```
38+ //! 3. Or run with Ollama (local LLM):
39+ //! ```
40+ //! ollama pull phi4-mini
41+ //! OLLAMA_MODEL=phi4-mini \
42+ //! VOID_BOX_KERNEL=/boot/vmlinuz-$(uname -r) \
43+ //! VOID_BOX_INITRAMFS=/tmp/void-box-rootfs.cpio.gz \
44+ //! cargo run --example trading_pipeline
45+ //! ```
2946
3047use std:: error:: Error ;
3148use std:: path:: PathBuf ;
3249
3350use void_box:: agent_box:: AgentBox ;
51+ use void_box:: llm:: LlmProvider ;
3452use void_box:: pipeline:: Pipeline ;
3553use void_box:: skill:: Skill ;
3654
@@ -49,6 +67,11 @@ async fn main() -> Result<(), Box<dyn Error>> {
4967 println ! ( "╚══════════════════════════════════════════════════════════════╝" ) ;
5068 println ! ( ) ;
5169
70+ // ---- LLM Provider: Claude (default) or Ollama (opt-in) ----
71+
72+ let llm = detect_llm_provider ( ) ;
73+ println ! ( "[llm] {}" , llm) ;
74+
5275 // ---- Skills: declared capabilities ----
5376
5477 let reasoning = Skill :: agent ( "claude-code" )
@@ -79,60 +102,78 @@ async fn main() -> Result<(), Box<dyn Error>> {
79102 println ! ( "--- Defining Boxes ---" ) ;
80103 println ! ( ) ;
81104
82- let data_box = make_box ( "data_analyst" , use_kvm)
105+ let data_box = make_box ( "data_analyst" , use_kvm, & llm )
83106 . skill ( data_skill)
84107 . skill ( reasoning. clone ( ) )
85108 . prompt (
86- "You are a financial data analyst. Generate realistic 30-day OHLCV data \
87- for AAPL, NVDA, MSFT, and GOOGL. Include mock news headlines for each symbol. \
88- Write a Python script to generate the data and run it. \
89- Follow the schema from your financial-data-analysis skill."
109+ "You are a financial data analyst. Here is recent market data (Feb 2026):\n \n \
110+ AAPL: price $227, P/E 34, RSI 62, 52w range $170-$243, EPS $2.40 beat est. $2.36, \
111+ Services revenue missed ($23.1B vs $23.5B est.), iPhone revenue +4% YoY.\n \
112+ NVDA: price $138, P/E 55, RSI 71, 52w range $78-$153, data center revenue +95% YoY, \
113+ new Blackwell GPU ramping, China export restrictions tightening.\n \
114+ MSFT: price $442, P/E 36, RSI 58, 52w range $385-$470, Azure grew +29%, \
115+ Copilot revenue accelerating, gaming flat YoY.\n \
116+ GOOGL: price $192, P/E 24, RSI 55, 52w range $152-$207, Search +12%, \
117+ Cloud +28%, DOJ antitrust ruling pending.\n \n \
118+ For each symbol, write a brief data summary with key metrics and recent catalysts.\n \
119+ Do NOT write or run code. Do NOT output JSON or templates.\n \
120+ Write plain text with clear sections per symbol."
90121 )
91122 . build ( ) ?;
92123
93124 println ! ( " [1] {} -- {} skills" , data_box. name, data_box. skills. len( ) ) ;
94125
95126 // ---- Box 2: Quant Analyst ----
96127
97- let quant_box = make_box ( "quant_analyst" , use_kvm)
128+ let quant_box = make_box ( "quant_analyst" , use_kvm, & llm )
98129 . skill ( quant_skill)
99130 . skill ( reasoning. clone ( ) )
100131 . prompt (
101- "You are a quantitative analyst. Read the market data from /workspace/input.json. \
102- Compute technical indicators (SMA, RSI, MACD, Bollinger Bands) for each symbol. \
103- Generate composite trading signals. \
104- Follow the methodology from your quant-technical-analysis skill."
132+ "You are a quantitative analyst. Read the data summary from /workspace/input.json.\n \n \
133+ For each symbol (AAPL, NVDA, MSFT, GOOGL), provide:\n \
134+ - RSI interpretation (overbought >70, neutral 30-70, oversold <30)\n \
135+ - P/E relative to sector average (Tech sector avg ~28)\n \
136+ - A composite signal: BULLISH, NEUTRAL, or BEARISH\n \n \
137+ Write plain text. Do NOT output JSON or templates.\n \
138+ Use the actual numbers from the input data."
105139 )
106140 . build ( ) ?;
107141
108142 println ! ( " [2] {} -- {} skills" , quant_box. name, quant_box. skills. len( ) ) ;
109143
110144 // ---- Box 3: Research Analyst (pure reasoning, no special skills) ----
111145
112- let sentiment_box = make_box ( "research_analyst" , use_kvm)
146+ let sentiment_box = make_box ( "research_analyst" , use_kvm, & llm )
113147 . skill ( reasoning. clone ( ) )
114148 . prompt (
115- "You are a research analyst. Read the technical signals from /workspace/input.json. \
116- For each symbol, assess the market sentiment considering the technical indicators, \
117- recent price action, and any news context. Score sentiment from -1.0 (very bearish) \
118- to +1.0 (very bullish). Provide brief reasoning for each score."
149+ "You are a research analyst. Read the quant analysis from /workspace/input.json.\n \n \
150+ For each symbol (AAPL, NVDA, MSFT, GOOGL):\n \
151+ - Score sentiment from -1.0 (very bearish) to +1.0 (very bullish)\n \
152+ - Write 2 sentences explaining your score\n \n \
153+ Consider the technical signals, fundamentals, and catalysts from the input.\n \
154+ Write plain text. Do NOT output JSON or templates.\n \
155+ Example: AAPL: +0.3 (mildly bullish). The earnings beat suggests..."
119156 )
120157 . build ( ) ?;
121158
122159 println ! ( " [3] {} -- {} skills (pure reasoning)" , sentiment_box. name, sentiment_box. skills. len( ) ) ;
123160
124161 // ---- Box 4: Portfolio Strategist ----
125162
126- let strategy_box = make_box ( "portfolio_strategist" , use_kvm)
163+ let strategy_box = make_box ( "portfolio_strategist" , use_kvm, & llm )
127164 . skill ( risk_skill)
128165 . skill ( reasoning. clone ( ) )
129- . memory_mb ( 512 )
130166 . prompt (
131- "You are a portfolio strategist. Read the analysis from /workspace/input.json \
132- which contains technical signals and sentiment scores. \
133- Generate specific trade recommendations with position sizing, entry/exit prices, \
134- stop loss levels, and risk management. \
135- Follow the framework from your portfolio-risk-management skill."
167+ "You are a portfolio strategist managing a $100,000 portfolio.\n \
168+ Read the sentiment analysis from /workspace/input.json.\n \n \
169+ For each symbol (AAPL, NVDA, MSFT, GOOGL) produce a trade recommendation:\n \
170+ - ACTION: BUY, SELL, or HOLD\n \
171+ - ALLOCATION: percentage of portfolio (must sum to <=100%)\n \
172+ - ENTRY PRICE: target buy price\n \
173+ - STOP LOSS: price to cut losses (set 5-10% below entry)\n \
174+ - RATIONALE: one sentence\n \n \
175+ Keep at least 20% in cash. Write plain text. Do NOT output JSON or templates.\n \
176+ Use real numbers from the analysis, not placeholders."
136177 )
137178 . build ( ) ?;
138179
@@ -180,8 +221,8 @@ async fn main() -> Result<(), Box<dyn Error>> {
180221 r. total_cost_usd,
181222 ) ;
182223 if !r. result_text . is_empty ( ) {
183- let preview = if r. result_text . len ( ) > 120 {
184- format ! ( "{}..." , & r. result_text[ ..120 ] )
224+ let preview = if r. result_text . len ( ) > 500 {
225+ format ! ( "{}..." , & r. result_text[ ..500 ] )
185226 } else {
186227 r. result_text . clone ( )
187228 } ;
@@ -207,8 +248,15 @@ async fn main() -> Result<(), Box<dyn Error>> {
207248}
208249
209250/// Create an AgentBox builder pre-configured for the current environment.
210- fn make_box ( name : & str , use_kvm : bool ) -> AgentBox {
211- let mut ab = AgentBox :: new ( name) ;
251+ fn make_box ( name : & str , use_kvm : bool , llm : & LlmProvider ) -> AgentBox {
252+ let mut ab = AgentBox :: new ( name) . llm ( llm. clone ( ) ) . memory_mb ( 1024 ) ;
253+
254+ // Allow per-stage timeout override via STAGE_TIMEOUT_SECS env var
255+ if let Ok ( secs) = std:: env:: var ( "STAGE_TIMEOUT_SECS" ) {
256+ if let Ok ( s) = secs. parse :: < u64 > ( ) {
257+ ab = ab. timeout_secs ( s) ;
258+ }
259+ }
212260
213261 if use_kvm {
214262 if let Some ( kernel) = kvm_kernel ( ) {
@@ -224,6 +272,37 @@ fn make_box(name: &str, use_kvm: bool) -> AgentBox {
224272 ab
225273}
226274
275+ /// Detect the LLM provider from environment variables.
276+ ///
277+ /// - `OLLAMA_MODEL=qwen3-coder` -> Ollama with that model
278+ /// - `LLM_BASE_URL=...` -> Custom provider
279+ /// - Otherwise -> Claude (default)
280+ fn detect_llm_provider ( ) -> LlmProvider {
281+ // Check for Ollama
282+ if let Ok ( model) = std:: env:: var ( "OLLAMA_MODEL" ) {
283+ if !model. is_empty ( ) {
284+ return LlmProvider :: ollama ( model) ;
285+ }
286+ }
287+
288+ // Check for custom endpoint
289+ if let Ok ( base_url) = std:: env:: var ( "LLM_BASE_URL" ) {
290+ if !base_url. is_empty ( ) {
291+ let mut provider = LlmProvider :: custom ( base_url) ;
292+ if let Ok ( key) = std:: env:: var ( "LLM_API_KEY" ) {
293+ provider = provider. api_key ( key) ;
294+ }
295+ if let Ok ( model) = std:: env:: var ( "LLM_MODEL" ) {
296+ provider = provider. model ( model) ;
297+ }
298+ return provider;
299+ }
300+ }
301+
302+ // Default: Claude
303+ LlmProvider :: Claude
304+ }
305+
227306/// Check if KVM artifacts are available.
228307fn is_kvm_available ( ) -> bool {
229308 std:: path:: Path :: new ( "/dev/kvm" ) . exists ( )
0 commit comments