multi-agent research: parallel LLM-powered investigation
new research tool spawns 3-25 micro-agents (ministral-3b) in parallel via futures::join_all. each agent gets its own Mistral conversation with full tool access. recursive spawning up to depth 4 — agents can spawn sub-agents. research sessions persisted in SQLite (survive reboots). thread UX: 🔍 reaction, per-agent progress posts, ✅ when done. cost: ~$0.03 per research task (20 micro-agents on ministral-3b).
This commit is contained in:
@@ -83,6 +83,18 @@ impl Store {
|
||||
PRIMARY KEY (localpart, service)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS research_sessions (
|
||||
session_id TEXT PRIMARY KEY,
|
||||
room_id TEXT NOT NULL,
|
||||
event_id TEXT NOT NULL,
|
||||
status TEXT NOT NULL DEFAULT 'running',
|
||||
query TEXT NOT NULL,
|
||||
plan_json TEXT,
|
||||
findings_json TEXT,
|
||||
depth INTEGER NOT NULL DEFAULT 0,
|
||||
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
||||
completed_at TEXT
|
||||
);
|
||||
",
|
||||
)?;
|
||||
|
||||
@@ -272,6 +284,91 @@ impl Store {
|
||||
}
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// Research Sessions
|
||||
// =========================================================================
|
||||
|
||||
/// Create a new research session.
|
||||
pub fn create_research_session(
|
||||
&self,
|
||||
session_id: &str,
|
||||
room_id: &str,
|
||||
event_id: &str,
|
||||
query: &str,
|
||||
plan_json: &str,
|
||||
) {
|
||||
let conn = self.conn.lock().unwrap();
|
||||
if let Err(e) = conn.execute(
|
||||
"INSERT INTO research_sessions (session_id, room_id, event_id, query, plan_json, findings_json)
|
||||
VALUES (?1, ?2, ?3, ?4, ?5, '[]')",
|
||||
params![session_id, room_id, event_id, query, plan_json],
|
||||
) {
|
||||
warn!("Failed to create research session: {e}");
|
||||
}
|
||||
}
|
||||
|
||||
/// Append a finding to a research session.
|
||||
pub fn append_research_finding(&self, session_id: &str, finding_json: &str) {
|
||||
let conn = self.conn.lock().unwrap();
|
||||
// Append to the JSON array
|
||||
if let Err(e) = conn.execute(
|
||||
"UPDATE research_sessions
|
||||
SET findings_json = json_insert(findings_json, '$[#]', json(?1))
|
||||
WHERE session_id = ?2",
|
||||
params![finding_json, session_id],
|
||||
) {
|
||||
warn!("Failed to append research finding: {e}");
|
||||
}
|
||||
}
|
||||
|
||||
/// Mark a research session as complete.
|
||||
pub fn complete_research_session(&self, session_id: &str) {
|
||||
let conn = self.conn.lock().unwrap();
|
||||
if let Err(e) = conn.execute(
|
||||
"UPDATE research_sessions SET status = 'complete', completed_at = datetime('now')
|
||||
WHERE session_id = ?1",
|
||||
params![session_id],
|
||||
) {
|
||||
warn!("Failed to complete research session: {e}");
|
||||
}
|
||||
}
|
||||
|
||||
/// Mark a research session as failed.
|
||||
pub fn fail_research_session(&self, session_id: &str) {
|
||||
let conn = self.conn.lock().unwrap();
|
||||
if let Err(e) = conn.execute(
|
||||
"UPDATE research_sessions SET status = 'failed', completed_at = datetime('now')
|
||||
WHERE session_id = ?1",
|
||||
params![session_id],
|
||||
) {
|
||||
warn!("Failed to mark research session failed: {e}");
|
||||
}
|
||||
}
|
||||
|
||||
/// Load all running research sessions (for crash recovery on startup).
|
||||
pub fn load_running_research_sessions(&self) -> Vec<(String, String, String, String)> {
|
||||
let conn = self.conn.lock().unwrap();
|
||||
let mut stmt = match conn.prepare(
|
||||
"SELECT session_id, room_id, query, findings_json
|
||||
FROM research_sessions WHERE status = 'running'",
|
||||
) {
|
||||
Ok(s) => s,
|
||||
Err(_) => return Vec::new(),
|
||||
};
|
||||
|
||||
stmt.query_map([], |row| {
|
||||
Ok((
|
||||
row.get::<_, String>(0)?,
|
||||
row.get::<_, String>(1)?,
|
||||
row.get::<_, String>(2)?,
|
||||
row.get::<_, String>(3)?,
|
||||
))
|
||||
})
|
||||
.ok()
|
||||
.map(|rows| rows.filter_map(|r| r.ok()).collect())
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
/// Load all agent mappings (for startup recovery).
|
||||
pub fn load_all_agents(&self) -> Vec<(String, String)> {
|
||||
let conn = self.conn.lock().unwrap();
|
||||
|
||||
Reference in New Issue
Block a user