Skip to main content

agent_loop/
agent_loop.rs

1// Copyright (c) 2026 Aprio One AB
2// Author: Kenneth Pernyer, kenneth@pernyer.se
3
4use anyhow::Result;
5use converge_analytics::{
6    engine::FeatureAgent,
7    model::InferenceAgent,
8};
9use converge_core::{Agent, Context, ContextKey, Fact};
10use std::fs::File;
11use std::io::Write;
12use std::path::Path;
13
14fn download_dataset_if_missing(path: &Path) -> Result<()> {
15    if path.exists() {
16        println!("Dataset exists at {:?}", path);
17        return Ok(());
18    }
19
20    println!("Downloading dataset to {:?}...", path);
21    // URL for California Housing parquet snippet from HuggingFace (train/0000.parquet)
22    let url = "https://huggingface.co/datasets/gvlassis/california_housing/resolve/refs%2Fconvert%2Fparquet/default/train/0000.parquet";
23
24    let response = reqwest::blocking::get(url)?;
25    let content = response.bytes()?;
26
27    let mut file = File::create(path)?;
28    file.write_all(&content)?;
29
30    println!("Download complete.");
31    Ok(())
32}
33
34fn main() -> Result<()> {
35    println!("Initializing Converge Analytics loop...");
36
37    // 0. Setup Dataset
38    let data_path = Path::new("california_housing_train.parquet");
39    // Ensure we can write to CWD or use a temp dir.
40    // In most CI/dev envs, CWD is writable.
41    download_dataset_if_missing(data_path)?;
42
43    // 1. Initialize Context
44    let mut ctx = Context::new();
45
46    // Seed with intent to analyze
47    ctx.add_fact(Fact::new(
48        ContextKey::Seeds,
49        "job-1",
50        "Analyze housing data",
51    ))?;
52
53    // 2. Initialize Agents
54    let feature_agent = FeatureAgent::new(Some(data_path.to_path_buf()));
55    let inference_agent = InferenceAgent::new();
56
57    let agents: Vec<Box<dyn Agent>> = vec![Box::new(feature_agent), Box::new(inference_agent)];
58
59    // 3. Run Loop (Simplified Engine)
60    // Real engine runs until convergence. Here we run fixed steps for demo.
61    println!("Starting execution loop...");
62    for cycle in 1..=3 {
63        println!("\n--- Cycle {} ---", cycle);
64        let mut changes = 0;
65
66        for agent in &agents {
67            if agent.accepts(&ctx) {
68                println!("Agent {} is active", agent.name());
69                let effect = agent.execute(&ctx);
70
71                if !effect.is_empty() {
72                    println!(
73                        "Agent {} produced {} facts, {} proposals",
74                        agent.name(),
75                        effect.facts.len(),
76                        effect.proposals.len()
77                    );
78
79                    // Emulate engine merging
80                    for fact in effect.facts {
81                        if ctx.add_fact(fact).unwrap_or(false) {
82                            changes += 1;
83                        }
84                    }
85
86                    // Emulate proposal validation (auto-promote for demo)
87                    for proposal in effect.proposals {
88                        // In real engine: validation logic.
89                        // Here: naive promotion.
90                        if let Ok(fact) = Fact::try_from(proposal) {
91                            if ctx.add_fact(fact).unwrap_or(false) {
92                                changes += 1;
93                            }
94                        }
95                    }
96                }
97            } else {
98                println!("Agent {} skipped", agent.name());
99            }
100        }
101
102        // Print state
103        for key in ctx.all_keys() {
104            let count = ctx.get(key).len();
105            println!("Context[{:?}] = {} facts", key, count);
106        }
107
108        if changes == 0 {
109            println!("Converged!");
110            break;
111        }
112    }
113
114    // 4. Verify Results
115    let hypotheses = ctx.get(ContextKey::Hypotheses);
116    if let Some(hypo) = hypotheses.first() {
117        println!("\nFinal Result: {}", hypo.content);
118    } else {
119        println!("\nNo hypothesis generated.");
120    }
121
122    Ok(())
123}