Skip to main content

tap_node/storage/
agent_storage_manager.rs

1//! Agent-specific storage management
2//!
3//! This module provides the AgentStorageManager that handles per-agent storage instances,
4//! ensuring that each agent's data is isolated in its own SQLite database.
5
6use crate::error::Result as NodeResult;
7use crate::storage::Storage;
8use dashmap::DashMap;
9use std::path::PathBuf;
10use std::sync::Arc;
11use tracing::{debug, error, info};
12
13/// Manages storage instances for multiple agents
14///
15/// Each agent gets its own isolated SQLite database located at:
16/// `{tap_root}/{sanitized_did}/transactions.db`
17#[derive(Clone)]
18pub struct AgentStorageManager {
19    /// Cache of agent storage instances (DID -> Storage)
20    agent_storages: DashMap<String, Arc<Storage>>,
21    /// TAP root directory for storage
22    tap_root: Option<PathBuf>,
23}
24
25impl AgentStorageManager {
26    /// Create a new agent storage manager
27    pub fn new(tap_root: Option<PathBuf>) -> Self {
28        info!("Creating AgentStorageManager with TAP root: {:?}", tap_root);
29        Self {
30            agent_storages: DashMap::new(),
31            tap_root,
32        }
33    }
34
35    /// Get or create storage for an agent
36    ///
37    /// This method maintains a cache of storage instances to avoid recreating
38    /// databases for the same agent. If the storage doesn't exist, it creates
39    /// a new one using the agent's DID for the database path.
40    pub async fn get_agent_storage(&self, agent_did: &str) -> NodeResult<Arc<Storage>> {
41        // Check cache first
42        if let Some(storage) = self.agent_storages.get(agent_did) {
43            debug!("Using cached storage for agent: {}", agent_did);
44            return Ok(storage.clone());
45        }
46
47        // Create new storage for this agent
48        debug!("Creating new storage for agent: {}", agent_did);
49        let storage = Storage::new_with_did(agent_did, self.tap_root.clone())
50            .await
51            .map_err(|e| {
52                crate::Error::Storage(format!(
53                    "Failed to create storage for agent {}: {}",
54                    agent_did, e
55                ))
56            })?;
57
58        let storage_arc = Arc::new(storage);
59
60        // Cache it
61        self.agent_storages
62            .insert(agent_did.to_string(), storage_arc.clone());
63        info!("Created and cached storage for agent: {}", agent_did);
64
65        Ok(storage_arc)
66    }
67
68    /// Get storage for an agent if it exists in cache (doesn't create new one)
69    pub fn get_cached_agent_storage(&self, agent_did: &str) -> Option<Arc<Storage>> {
70        self.agent_storages.get(agent_did).map(|s| s.clone())
71    }
72
73    /// Remove an agent's storage from the cache
74    ///
75    /// This doesn't delete the database files, just removes the instance from memory.
76    /// Useful when an agent is unregistered.
77    pub fn remove_agent_storage(&self, agent_did: &str) -> Option<Arc<Storage>> {
78        debug!("Removing storage cache for agent: {}", agent_did);
79        self.agent_storages
80            .remove(agent_did)
81            .map(|(_, storage)| storage)
82    }
83
84    /// Get count of cached storage instances
85    pub fn cached_storage_count(&self) -> usize {
86        self.agent_storages.len()
87    }
88
89    /// List all agent DIDs that have cached storage
90    pub fn cached_agent_dids(&self) -> Vec<String> {
91        self.agent_storages
92            .iter()
93            .map(|entry| entry.key().clone())
94            .collect()
95    }
96
97    /// Clear all cached storage instances
98    ///
99    /// This forces recreation of storage instances on next access.
100    /// Useful for testing or when storage configuration changes.
101    pub fn clear_cache(&self) {
102        info!("Clearing all cached agent storage instances");
103        self.agent_storages.clear();
104    }
105
106    /// Ensure storage exists for an agent (creates if needed but doesn't cache)
107    ///
108    /// This is useful during agent registration to ensure the storage directory
109    /// and database are properly initialized.
110    pub async fn ensure_agent_storage(&self, agent_did: &str) -> NodeResult<()> {
111        match Storage::new_with_did(agent_did, self.tap_root.clone()).await {
112            Ok(_) => {
113                info!("Ensured storage exists for agent: {}", agent_did);
114                Ok(())
115            }
116            Err(e) => {
117                error!("Failed to ensure storage for agent {}: {}", agent_did, e);
118                Err(crate::Error::Storage(format!(
119                    "Failed to ensure storage for agent {}: {}",
120                    agent_did, e
121                )))
122            }
123        }
124    }
125}
126
127#[cfg(test)]
128mod tests {
129    use super::*;
130    use tempfile::TempDir;
131
132    #[tokio::test]
133    async fn test_agent_storage_manager_creation() {
134        let temp_dir = TempDir::new().unwrap();
135        let manager = AgentStorageManager::new(Some(temp_dir.path().to_path_buf()));
136
137        assert_eq!(manager.cached_storage_count(), 0);
138        assert!(manager.cached_agent_dids().is_empty());
139    }
140
141    #[tokio::test]
142    async fn test_get_agent_storage() {
143        let temp_dir = TempDir::new().unwrap();
144        let manager = AgentStorageManager::new(Some(temp_dir.path().to_path_buf()));
145
146        let agent_did = "did:example:test-agent";
147
148        // First call should create storage
149        let storage1 = manager.get_agent_storage(agent_did).await.unwrap();
150        assert_eq!(manager.cached_storage_count(), 1);
151
152        // Second call should return cached storage
153        let storage2 = manager.get_agent_storage(agent_did).await.unwrap();
154        assert_eq!(manager.cached_storage_count(), 1);
155
156        // Should be the same instance
157        assert!(Arc::ptr_eq(&storage1, &storage2));
158    }
159
160    #[tokio::test]
161    async fn test_remove_agent_storage() {
162        let temp_dir = TempDir::new().unwrap();
163        let manager = AgentStorageManager::new(Some(temp_dir.path().to_path_buf()));
164
165        let agent_did = "did:example:test-agent";
166
167        // Create storage
168        let _storage = manager.get_agent_storage(agent_did).await.unwrap();
169        assert_eq!(manager.cached_storage_count(), 1);
170
171        // Remove from cache
172        let removed = manager.remove_agent_storage(agent_did);
173        assert!(removed.is_some());
174        assert_eq!(manager.cached_storage_count(), 0);
175    }
176
177    #[tokio::test]
178    async fn test_multiple_agents() {
179        let temp_dir = TempDir::new().unwrap();
180        let manager = AgentStorageManager::new(Some(temp_dir.path().to_path_buf()));
181
182        let agent1 = "did:example:agent1";
183        let agent2 = "did:example:agent2";
184
185        // Create storage for both agents
186        let _storage1 = manager.get_agent_storage(agent1).await.unwrap();
187        let _storage2 = manager.get_agent_storage(agent2).await.unwrap();
188
189        assert_eq!(manager.cached_storage_count(), 2);
190
191        let cached_dids = manager.cached_agent_dids();
192        assert!(cached_dids.contains(&agent1.to_string()));
193        assert!(cached_dids.contains(&agent2.to_string()));
194    }
195}