Skip to main content

lean_ctx/core/agents/
shared.rs

1use super::{AgentRegistry, ScratchpadEntry};
2use crate::core::a2a::message::{MessagePriority, PrivacyLevel};
3use chrono::{DateTime, Utc};
4use serde::{Deserialize, Serialize};
5use std::collections::HashMap;
6use std::path::PathBuf;
7
8#[derive(Debug, Clone, Serialize, Deserialize)]
9pub struct SharedFact {
10    pub from_agent: String,
11    pub category: String,
12    pub key: String,
13    pub value: String,
14    pub timestamp: DateTime<Utc>,
15    #[serde(default)]
16    pub received_by: Vec<String>,
17}
18
19impl AgentRegistry {
20    pub fn share_knowledge(&mut self, from: &str, category: &str, facts: &[(String, String)]) {
21        for (key, value) in facts {
22            self.scratchpad.push(ScratchpadEntry {
23                id: format!("knowledge-{}", chrono::Utc::now().timestamp_millis()),
24                from_agent: from.to_string(),
25                to_agent: None,
26                task_id: None,
27                category: category.to_string(),
28                priority: MessagePriority::default(),
29                privacy: PrivacyLevel::Team,
30                message: format!("[knowledge] {key}={value}"),
31                metadata: HashMap::new(),
32                project_root: None,
33                timestamp: Utc::now(),
34                read_by: Vec::new(),
35                expires_at: None,
36            });
37        }
38        let shared_path = Self::shared_knowledge_path();
39        let mut existing: Vec<SharedFact> = std::fs::read_to_string(&shared_path)
40            .ok()
41            .and_then(|s| serde_json::from_str(&s).ok())
42            .unwrap_or_default();
43
44        for (key, value) in facts {
45            existing.push(SharedFact {
46                from_agent: from.to_string(),
47                category: category.to_string(),
48                key: key.clone(),
49                value: value.clone(),
50                timestamp: Utc::now(),
51                received_by: Vec::new(),
52            });
53        }
54
55        if existing.len() > 500 {
56            existing.drain(..existing.len() - 500);
57        }
58        if let Ok(json) = serde_json::to_string_pretty(&existing) {
59            let _ = std::fs::write(&shared_path, json);
60        }
61    }
62
63    pub fn receive_shared_knowledge(&mut self, agent_id: &str) -> Vec<SharedFact> {
64        let shared_path = Self::shared_knowledge_path();
65        let mut all: Vec<SharedFact> = std::fs::read_to_string(&shared_path)
66            .ok()
67            .and_then(|s| serde_json::from_str(&s).ok())
68            .unwrap_or_default();
69
70        let mut new_facts = Vec::new();
71        for fact in &mut all {
72            if fact.from_agent != agent_id && !fact.received_by.contains(&agent_id.to_string()) {
73                fact.received_by.push(agent_id.to_string());
74                new_facts.push(fact.clone());
75            }
76        }
77
78        if !new_facts.is_empty()
79            && let Ok(json) = serde_json::to_string_pretty(&all)
80        {
81            let _ = std::fs::write(&shared_path, json);
82        }
83        new_facts
84    }
85
86    fn shared_knowledge_path() -> PathBuf {
87        // GH #439: route through the typed data resolver so a post-migration
88        // split install writes to $XDG_DATA_HOME, not a re-created ~/.lean-ctx.
89        crate::core::paths::data_dir()
90            .unwrap_or_else(|_| PathBuf::from("."))
91            .join("shared_knowledge.json")
92    }
93}