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}