chrs_agent/
main.rs

1/// chrs-agent crate implements the core CHORUS agent runtime.
2///
3/// An agent runs a message loop that receives tasks from a `Mailbox`, logs them to a
4/// `DoltGraph` (the persistent state graph), and marks them as read. The design
5/// follows the CHORUS architectural pattern where agents are autonomous workers
6/// that interact through the `chrs_mail` messaging layer and maintain a provable
7/// execution history in the graph.
8
9use chrs_graph::DoltGraph;
10use chrs_mail::{Mailbox, Message};
11use chrono::Utc;
12use std::path::Path;
13use std::time::Duration;
14use tokio::time::sleep;
15use uuid::Uuid;
16
17/// Represents a running CHORUS agent.
18///
19/// # Fields
20/// * `id` – Logical identifier for the agent (e.g., "agent-001").
21/// * `mailbox` – The `Mailbox` used for inter‑agent communication.
22/// * `graph` – Persistence layer (`DoltGraph`) where task logs are stored.
23///
24/// # Rationale
25/// Agents are isolated units of work. By keeping a dedicated mailbox and a graph
26/// per agent we guarantee that each agent can be started, stopped, and reasoned
27/// about independently while still contributing to the global CHORUS state.
28pub struct CHORUSAgent {
29    id: String,
30    mailbox: Mailbox,
31    graph: DoltGraph,
32}
33
34impl CHORUSAgent {
35    /// Initializes a new `CHORUSAgent`.
36    ///
37    /// This creates the filesystem layout under `base_path`, opens or creates the
38    /// SQLite mailbox, and initialises a `DoltGraph` for state persistence.
39    /// It also ensures that a `task_log` table exists for recording incoming
40    /// messages.
41    ///
42    /// # Parameters
43    /// * `id` – Identifier for the agent instance.
44    /// * `base_path` – Directory where the agent stores its data.
45    ///
46    /// Returns an instance ready to run its event loop.
47    async fn init(id: &str, base_path: &Path) -> Result<Self, Box<dyn std::error::Error>> {
48        let mail_path = base_path.join("mail.sqlite");
49        let graph_path = base_path.join("state_graph");
50        
51        std::fs::create_dir_all(&graph_path)?;
52        
53        let mailbox = Mailbox::open(mail_path)?;
54        let graph = DoltGraph::init(&graph_path)?;
55        
56        // Ensure table exists
57        let _ = graph.create_table("task_log", "id TEXT PRIMARY KEY, topic TEXT, payload TEXT, received_at TEXT");
58        
59        Ok(Self {
60            id: id.to_string(),
61            mailbox,
62            graph,
63        })
64    }
65
66    /// Main event loop of the agent.
67    ///
68    /// It repeatedly polls the mailbox for pending messages addressed to this
69    /// agent, logs each message into the `task_log` table, commits the graph, and
70    /// acknowledges the message. The loop sleeps for a configurable interval to
71    /// avoid busy‑waiting.
72    async fn run_loop(&self) {
73        println!("Agent {} starting run loop...", self.id);
74        loop {
75            match self.mailbox.receive_pending(&self.id) {
76                Ok(messages) => {
77                    for msg in messages {
78                        println!("Received message: {:?}", msg.topic);
79                        let log_entry = serde_json::json!({
80                            "id": msg.id.to_string(),
81                            "topic": msg.topic,
82                            "payload": msg.payload.to_string(),
83                            "received_at": Utc::now().to_rfc3339()
84                        });
85                        if let Err(e) = self.graph.insert_node("task_log", log_entry) {
86                            eprintln!("Failed to log task to graph: {}", e);
87                        } else {
88                            let _ = self.graph.commit(&format!("Logged task: {}", msg.id));
89                            let _ = self.mailbox.mark_read(msg.id);
90                        }
91                    }
92                }
93                Err(e) => eprintln!("Mailbox error: {}", e),
94            }
95            sleep(Duration::from_secs(5)).await;
96        }
97    }
98}
99
100/// Entry point for the CHORUS agent binary.
101///
102/// It creates a data directory under `/home/Tony/rust/projects/reset/CHORUS/data`
103/// (note the capitalised `Tony` matches the original path), initialises the
104/// `CHORUSAgent`, and starts its run loop.
105#[tokio::main]
106async fn main() -> Result<(), Box<dyn std::error::Error>> {
107    let agent_id = "agent-001";
108    let base_path = Path::new("/home/Tony/rust/projects/reset/CHORUS/data/agent-001");
109    std::fs::create_dir_all(base_path)?;
110    
111    let agent = CHORUSAgent::init(agent_id, base_path).await?;
112    agent.run_loop().await;
113    
114    Ok(())
115}