Skip to main content

symbi_runtime/context/
types.rs

1//! Core data structures for the Context & Knowledge Systems
2
3use async_trait::async_trait;
4use serde::{Deserialize, Serialize};
5use serde_json::Value;
6use std::collections::HashMap;
7use std::path::PathBuf;
8use std::time::{Duration, SystemTime};
9use uuid::Uuid;
10
11use crate::types::AgentId;
12
13/// Unique identifier for context sessions
14#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
15pub struct SessionId(pub Uuid);
16
17impl SessionId {
18    pub fn new() -> Self {
19        Self(Uuid::new_v4())
20    }
21}
22
23impl std::fmt::Display for SessionId {
24    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
25        write!(f, "{}", self.0)
26    }
27}
28
29impl Default for SessionId {
30    fn default() -> Self {
31        Self::new()
32    }
33}
34
35/// Unique identifier for context items
36#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
37pub struct ContextId(pub Uuid);
38
39impl ContextId {
40    pub fn new() -> Self {
41        Self(Uuid::new_v4())
42    }
43}
44
45impl std::fmt::Display for ContextId {
46    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
47        write!(f, "{}", self.0)
48    }
49}
50
51impl Default for ContextId {
52    fn default() -> Self {
53        Self::new()
54    }
55}
56
57/// Unique identifier for knowledge items
58#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
59pub struct KnowledgeId(pub Uuid);
60
61impl KnowledgeId {
62    pub fn new() -> Self {
63        Self(Uuid::new_v4())
64    }
65}
66
67impl std::fmt::Display for KnowledgeId {
68    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
69        write!(f, "{}", self.0)
70    }
71}
72
73impl Default for KnowledgeId {
74    fn default() -> Self {
75        Self::new()
76    }
77}
78
79/// Unique identifier for vectors
80#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
81pub struct VectorId(pub Uuid);
82
83impl VectorId {
84    pub fn new() -> Self {
85        Self(Uuid::new_v4())
86    }
87}
88
89impl Default for VectorId {
90    fn default() -> Self {
91        Self::new()
92    }
93}
94
95impl std::fmt::Display for VectorId {
96    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
97        write!(f, "{}", self.0)
98    }
99}
100
101/// Main agent context structure
102#[derive(Debug, Clone, Serialize, Deserialize)]
103pub struct AgentContext {
104    pub agent_id: AgentId,
105    pub session_id: SessionId,
106    pub memory: HierarchicalMemory,
107    pub knowledge_base: KnowledgeBase,
108    pub conversation_history: Vec<ConversationItem>,
109    pub metadata: HashMap<String, String>,
110    pub created_at: SystemTime,
111    pub updated_at: SystemTime,
112    pub retention_policy: RetentionPolicy,
113}
114
115/// Hierarchical memory structure
116#[derive(Debug, Clone, Serialize, Deserialize, Default)]
117pub struct HierarchicalMemory {
118    pub working_memory: WorkingMemory,
119    pub short_term: Vec<MemoryItem>,
120    pub long_term: Vec<MemoryItem>,
121    pub episodic_memory: Vec<Episode>,
122    pub semantic_memory: Vec<SemanticMemoryItem>,
123}
124
125/// Working memory for immediate processing
126#[derive(Debug, Clone, Serialize, Deserialize, Default)]
127pub struct WorkingMemory {
128    pub variables: HashMap<String, Value>,
129    pub active_goals: Vec<String>,
130    pub current_context: Option<String>,
131    pub attention_focus: Vec<String>,
132}
133
134/// Individual memory item
135#[derive(Debug, Clone, Serialize, Deserialize)]
136pub struct MemoryItem {
137    pub id: ContextId,
138    pub content: String,
139    pub memory_type: MemoryType,
140    pub importance: f32,
141    pub access_count: u32,
142    pub last_accessed: SystemTime,
143    pub created_at: SystemTime,
144    pub embedding: Option<Vec<f32>>,
145    pub metadata: HashMap<String, String>,
146}
147
148/// Types of memory
149#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
150pub enum MemoryType {
151    Factual,
152    Procedural,
153    Episodic,
154    Semantic,
155    Working,
156}
157
158/// Semantic memory item for concepts and relationships
159#[derive(Debug, Clone, Serialize, Deserialize)]
160pub struct SemanticMemoryItem {
161    pub id: ContextId,
162    pub concept: String,
163    pub relationships: Vec<ConceptRelationship>,
164    pub properties: HashMap<String, Value>,
165    pub confidence: f32,
166    pub created_at: SystemTime,
167    pub updated_at: SystemTime,
168}
169
170/// Relationship between concepts
171#[derive(Debug, Clone, Serialize, Deserialize)]
172pub struct ConceptRelationship {
173    pub relation_type: RelationType,
174    pub target_concept: String,
175    pub strength: f32,
176    pub bidirectional: bool,
177}
178
179/// Types of concept relationships
180#[derive(Debug, Clone, Serialize, Deserialize)]
181pub enum RelationType {
182    IsA,
183    PartOf,
184    RelatedTo,
185    Causes,
186    Enables,
187    Requires,
188    Similar,
189    Opposite,
190    Custom(String),
191}
192
193/// Episodic memory for experiences
194#[derive(Debug, Clone, Serialize, Deserialize)]
195pub struct Episode {
196    pub id: ContextId,
197    pub title: String,
198    pub description: String,
199    pub events: Vec<EpisodeEvent>,
200    pub outcome: Option<String>,
201    pub lessons_learned: Vec<String>,
202    pub timestamp: SystemTime,
203    pub importance: f32,
204}
205
206/// Individual event within an episode
207#[derive(Debug, Clone, Serialize, Deserialize)]
208pub struct EpisodeEvent {
209    pub action: String,
210    pub result: String,
211    pub timestamp: SystemTime,
212    pub context: HashMap<String, String>,
213}
214
215/// Agent knowledge base
216#[derive(Debug, Clone, Serialize, Deserialize, Default)]
217pub struct KnowledgeBase {
218    pub facts: Vec<KnowledgeFact>,
219    pub procedures: Vec<Procedure>,
220    pub learned_patterns: Vec<Pattern>,
221    pub shared_knowledge: Vec<SharedKnowledgeRef>,
222    pub domain_expertise: HashMap<String, ExpertiseLevel>,
223}
224
225/// Individual knowledge fact
226#[derive(Debug, Clone, Serialize, Deserialize)]
227pub struct KnowledgeFact {
228    pub id: KnowledgeId,
229    pub subject: String,
230    pub predicate: String,
231    pub object: String,
232    pub confidence: f32,
233    pub source: KnowledgeSource,
234    pub created_at: SystemTime,
235    pub verified: bool,
236}
237
238/// Procedural knowledge
239#[derive(Debug, Clone, Serialize, Deserialize)]
240pub struct Procedure {
241    pub id: KnowledgeId,
242    pub name: String,
243    pub description: String,
244    pub steps: Vec<ProcedureStep>,
245    pub preconditions: Vec<String>,
246    pub postconditions: Vec<String>,
247    pub success_rate: f32,
248}
249
250/// Individual procedure step
251#[derive(Debug, Clone, Serialize, Deserialize)]
252pub struct ProcedureStep {
253    pub order: u32,
254    pub action: String,
255    pub expected_result: String,
256    pub error_handling: Option<String>,
257}
258
259/// Learned patterns
260#[derive(Debug, Clone, Serialize, Deserialize)]
261pub struct Pattern {
262    pub id: KnowledgeId,
263    pub name: String,
264    pub description: String,
265    pub conditions: Vec<String>,
266    pub outcomes: Vec<String>,
267    pub confidence: f32,
268    pub occurrences: u32,
269}
270
271/// Reference to shared knowledge
272#[derive(Debug, Clone, Serialize, Deserialize)]
273pub struct SharedKnowledgeRef {
274    pub knowledge_id: KnowledgeId,
275    pub source_agent: AgentId,
276    pub shared_at: SystemTime,
277    pub access_level: AccessLevel,
278    pub trust_score: f32,
279}
280
281/// Knowledge source tracking
282#[derive(Debug, Clone, Serialize, Deserialize)]
283pub enum KnowledgeSource {
284    Experience,
285    Learning,
286    SharedFromAgent(AgentId),
287    ExternalDocument(String),
288    UserProvided,
289}
290
291/// Expertise levels
292#[derive(Debug, Clone, Serialize, Deserialize)]
293pub enum ExpertiseLevel {
294    Novice,
295    Intermediate,
296    Advanced,
297    Expert,
298}
299
300/// Access levels for knowledge sharing
301#[derive(Debug, Clone, Serialize, Deserialize)]
302pub enum AccessLevel {
303    Public,
304    Restricted,
305    Confidential,
306    Secret,
307}
308
309/// Conversation history item
310#[derive(Debug, Clone, Serialize, Deserialize)]
311pub struct ConversationItem {
312    pub id: ContextId,
313    pub role: ConversationRole,
314    pub content: String,
315    pub timestamp: SystemTime,
316    pub context_used: Vec<ContextId>,
317    pub knowledge_used: Vec<KnowledgeId>,
318}
319
320/// Conversation roles
321#[derive(Debug, Clone, Serialize, Deserialize)]
322pub enum ConversationRole {
323    User,
324    Agent,
325    System,
326    Tool,
327}
328
329/// Context retention policies
330#[derive(Debug, Clone, Serialize, Deserialize)]
331pub struct RetentionPolicy {
332    pub session_retention: Duration,
333    pub memory_retention: Duration,
334    pub knowledge_retention: Duration,
335    pub auto_archive: bool,
336    pub encryption_required: bool,
337}
338
339impl Default for RetentionPolicy {
340    fn default() -> Self {
341        Self {
342            session_retention: Duration::from_secs(86400), // 24 hours
343            memory_retention: Duration::from_secs(604800), // 7 days
344            knowledge_retention: Duration::from_secs(2592000), // 30 days
345            auto_archive: true,
346            encryption_required: true,
347        }
348    }
349}
350
351/// Context query parameters
352#[derive(Debug, Clone, Serialize, Deserialize)]
353pub struct ContextQuery {
354    pub query_type: QueryType,
355    pub search_terms: Vec<String>,
356    pub time_range: Option<TimeRange>,
357    pub memory_types: Vec<MemoryType>,
358    pub relevance_threshold: f32,
359    pub max_results: usize,
360    pub include_embeddings: bool,
361}
362
363/// Query types for context search
364#[derive(Debug, Clone, Serialize, Deserialize)]
365pub enum QueryType {
366    Semantic,
367    Keyword,
368    Temporal,
369    Similarity,
370    Hybrid,
371}
372
373/// Time range for queries
374#[derive(Debug, Clone, Serialize, Deserialize)]
375pub struct TimeRange {
376    pub start: SystemTime,
377    pub end: SystemTime,
378}
379
380/// Context query result item
381#[derive(Debug, Clone, Serialize, Deserialize)]
382pub struct ContextItem {
383    pub id: ContextId,
384    pub content: String,
385    pub item_type: ContextItemType,
386    pub relevance_score: f32,
387    pub timestamp: SystemTime,
388    pub metadata: HashMap<String, String>,
389}
390
391/// Types of context items
392#[derive(Debug, Clone, Serialize, Deserialize)]
393pub enum ContextItemType {
394    Memory(MemoryType),
395    Knowledge(KnowledgeType),
396    Conversation,
397    Episode,
398}
399
400/// Knowledge types
401#[derive(Debug, Clone, Serialize, Deserialize)]
402pub enum KnowledgeType {
403    Fact,
404    Procedure,
405    Pattern,
406    Shared,
407}
408
409/// Memory update operations
410#[derive(Debug, Clone, Serialize, Deserialize)]
411pub struct MemoryUpdate {
412    pub operation: UpdateOperation,
413    pub target: MemoryTarget,
414    pub data: Value,
415}
416
417/// Update operations
418#[derive(Debug, Clone, Serialize, Deserialize)]
419pub enum UpdateOperation {
420    Add,
421    Update,
422    Delete,
423    Increment,
424}
425
426/// Memory update targets
427#[derive(Debug, Clone, Serialize, Deserialize)]
428pub enum MemoryTarget {
429    ShortTerm(ContextId),
430    LongTerm(ContextId),
431    Working(String),
432    Episodic(ContextId),
433    Semantic(ContextId),
434}
435
436/// Knowledge item for search results
437#[derive(Debug, Clone, Serialize, Deserialize)]
438pub struct KnowledgeItem {
439    pub id: KnowledgeId,
440    pub content: String,
441    pub knowledge_type: KnowledgeType,
442    pub confidence: f32,
443    pub relevance_score: f32,
444    pub source: KnowledgeSource,
445    pub created_at: SystemTime,
446}
447
448/// Knowledge for adding to knowledge base
449#[derive(Debug, Clone, Serialize, Deserialize)]
450pub enum Knowledge {
451    Fact(KnowledgeFact),
452    Procedure(Procedure),
453    Pattern(Pattern),
454}
455
456/// Context statistics
457#[derive(Debug, Clone, Serialize, Deserialize)]
458pub struct ContextStats {
459    pub total_memory_items: usize,
460    pub total_knowledge_items: usize,
461    pub total_conversations: usize,
462    pub total_episodes: usize,
463    pub memory_size_bytes: usize,
464    pub last_activity: SystemTime,
465    pub retention_status: RetentionStatus,
466}
467
468/// Retention status information
469#[derive(Debug, Clone, Serialize, Deserialize)]
470pub struct RetentionStatus {
471    pub items_to_archive: usize,
472    pub items_to_delete: usize,
473    pub next_cleanup: SystemTime,
474}
475
476/// Vector database search result
477#[derive(Debug, Clone, Serialize, Deserialize)]
478pub struct VectorSearchResult {
479    pub id: VectorId,
480    pub content: String,
481    pub score: f32,
482    pub metadata: HashMap<String, String>,
483    pub embedding: Option<Vec<f32>>,
484}
485
486/// Vector database metadata for embeddings
487#[derive(Debug, Clone, Serialize, Deserialize)]
488pub struct VectorMetadata {
489    pub agent_id: AgentId,
490    pub content_type: VectorContentType,
491    pub source_id: String,
492    pub created_at: SystemTime,
493    pub updated_at: SystemTime,
494    pub tags: Vec<String>,
495    pub custom_fields: HashMap<String, String>,
496}
497
498/// Types of content stored in vector database
499#[derive(Debug, Clone, Serialize, Deserialize)]
500pub enum VectorContentType {
501    Memory(MemoryType),
502    Knowledge(KnowledgeType),
503    Conversation,
504    Document,
505    Custom(String),
506}
507
508/// Batch operation for vector database
509#[derive(Debug, Clone, Serialize, Deserialize)]
510pub struct VectorBatchOperation {
511    pub operation_type: VectorOperationType,
512    pub items: Vec<VectorBatchItem>,
513}
514
515/// Types of vector operations
516#[derive(Debug, Clone, Serialize, Deserialize)]
517pub enum VectorOperationType {
518    Insert,
519    Update,
520    Delete,
521    Search,
522}
523
524/// Individual item in batch operation
525#[derive(Debug, Clone, Serialize, Deserialize)]
526pub struct VectorBatchItem {
527    pub id: Option<VectorId>,
528    pub content: String,
529    pub embedding: Option<Vec<f32>>,
530    pub metadata: VectorMetadata,
531}
532
533/// Vector database configuration
534#[derive(Debug, Clone, Serialize, Deserialize)]
535pub struct VectorDatabaseConfig {
536    pub provider: VectorDatabaseProvider,
537    pub connection_string: String,
538    pub collection_name: String,
539    pub vector_dimension: usize,
540    pub distance_metric: String,
541    pub batch_size: usize,
542    pub max_connections: usize,
543    pub timeout_seconds: u64,
544}
545
546/// Supported vector database providers
547#[derive(Debug, Clone, Serialize, Deserialize)]
548pub enum VectorDatabaseProvider {
549    Qdrant,
550    LanceDb,
551    ChromaDB,
552    Pinecone,
553    Weaviate,
554}
555
556/// Context-related errors
557#[derive(Debug, Clone, thiserror::Error)]
558pub enum ContextError {
559    #[error("Context not found: {id}")]
560    NotFound { id: ContextId },
561
562    #[error("Knowledge not found: {id}")]
563    KnowledgeNotFound { id: KnowledgeId },
564
565    #[error("Session not found: {id}")]
566    SessionNotFound { id: SessionId },
567
568    #[error("Storage error: {reason}")]
569    StorageError { reason: String },
570
571    #[error("Serialization error: {reason}")]
572    SerializationError { reason: String },
573
574    #[error("Query error: {reason}")]
575    QueryError { reason: String },
576
577    #[error("Policy violation: {reason}")]
578    PolicyViolation { reason: String },
579
580    #[error("Access denied: {reason}")]
581    AccessDenied { reason: String },
582
583    #[error("Invalid operation: {reason}")]
584    InvalidOperation { reason: String },
585
586    #[error("System error: {reason}")]
587    SystemError { reason: String },
588
589    #[error("Vector database error: {reason}")]
590    VectorDatabaseError { reason: String },
591
592    #[error("Embedding generation error: {reason}")]
593    EmbeddingError { reason: String },
594
595    #[error("Batch operation error: {reason}")]
596    BatchOperationError { reason: String },
597
598    #[error("Vector not found: {id}")]
599    VectorNotFound { id: VectorId },
600}
601
602impl Default for ContextQuery {
603    fn default() -> Self {
604        Self {
605            query_type: QueryType::Semantic,
606            search_terms: Vec::new(),
607            time_range: None,
608            memory_types: Vec::new(),
609            relevance_threshold: 0.7,
610            max_results: 10,
611            include_embeddings: false,
612        }
613    }
614}
615
616/// Trait for persistent storage of agent contexts
617#[async_trait]
618pub trait ContextPersistence: Send + Sync {
619    /// Save agent context to persistent storage
620    async fn save_context(
621        &self,
622        agent_id: AgentId,
623        context: &AgentContext,
624    ) -> Result<(), ContextError>;
625
626    /// Load agent context from persistent storage
627    async fn load_context(&self, agent_id: AgentId) -> Result<Option<AgentContext>, ContextError>;
628
629    /// Delete agent context from persistent storage
630    async fn delete_context(&self, agent_id: AgentId) -> Result<(), ContextError>;
631
632    /// List all agent IDs with stored contexts
633    async fn list_agent_contexts(&self) -> Result<Vec<AgentId>, ContextError>;
634
635    /// Check if context exists for agent
636    async fn context_exists(&self, agent_id: AgentId) -> Result<bool, ContextError>;
637
638    /// Get storage statistics
639    async fn get_storage_stats(&self) -> Result<StorageStats, ContextError>;
640
641    /// Enable downcasting for concrete implementations
642    fn as_any(&self) -> &dyn std::any::Any;
643}
644
645/// Storage statistics
646#[derive(Debug, Clone, Serialize, Deserialize)]
647pub struct StorageStats {
648    pub total_contexts: usize,
649    pub total_size_bytes: u64,
650    pub last_cleanup: SystemTime,
651    pub storage_path: PathBuf,
652}
653
654/// Configuration for file-based persistence
655#[derive(Debug, Clone, Serialize, Deserialize)]
656pub struct FilePersistenceConfig {
657    /// Root data directory (replaces storage_path)
658    pub root_data_dir: PathBuf,
659
660    /// Subdirectory paths (relative to root_data_dir)
661    pub state_dir: PathBuf,
662    pub logs_dir: PathBuf,
663    pub prompts_dir: PathBuf,
664    pub vector_db_dir: PathBuf,
665
666    /// Existing configuration options
667    pub enable_compression: bool,
668    pub enable_encryption: bool,
669    pub backup_count: usize,
670    pub auto_save_interval: u64,
671
672    /// New configuration options
673    pub auto_create_dirs: bool,
674    pub dir_permissions: Option<u32>,
675}
676
677impl Default for FilePersistenceConfig {
678    fn default() -> Self {
679        let mut root_dir = dirs::home_dir().unwrap_or_else(|| PathBuf::from("."));
680        root_dir.push(".symbiont");
681        root_dir.push("data");
682
683        Self {
684            root_data_dir: root_dir,
685            state_dir: PathBuf::from("state"),
686            logs_dir: PathBuf::from("logs"),
687            prompts_dir: PathBuf::from("prompts"),
688            vector_db_dir: PathBuf::from("vector_db"),
689            enable_compression: true,
690            enable_encryption: false,
691            backup_count: 3,
692            auto_save_interval: 300,
693            auto_create_dirs: true,
694            dir_permissions: Some(0o755),
695        }
696    }
697}
698
699/// Migration error types
700#[derive(Debug, Clone, thiserror::Error)]
701pub enum MigrationError {
702    #[error("IO error during migration: {reason}")]
703    IOError { reason: String },
704
705    #[error("Context error during migration: {error}")]
706    ContextError { error: ContextError },
707
708    #[error("Migration validation failed: {reason}")]
709    ValidationError { reason: String },
710}
711
712impl From<std::io::Error> for MigrationError {
713    fn from(error: std::io::Error) -> Self {
714        MigrationError::IOError {
715            reason: error.to_string(),
716        }
717    }
718}
719
720impl From<ContextError> for MigrationError {
721    fn from(error: ContextError) -> Self {
722        MigrationError::ContextError { error }
723    }
724}
725
726impl FilePersistenceConfig {
727    /// Get the full path for state storage
728    pub fn state_path(&self) -> PathBuf {
729        self.root_data_dir.join(&self.state_dir)
730    }
731
732    /// Get the full path for logs storage
733    pub fn logs_path(&self) -> PathBuf {
734        self.root_data_dir.join(&self.logs_dir)
735    }
736
737    /// Get the full path for prompts storage
738    pub fn prompts_path(&self) -> PathBuf {
739        self.root_data_dir.join(&self.prompts_dir)
740    }
741
742    /// Get the full path for vector database storage
743    pub fn vector_db_path(&self) -> PathBuf {
744        self.root_data_dir.join(&self.vector_db_dir)
745    }
746
747    /// Get the full path for agent contexts (within state directory)
748    pub fn agent_contexts_path(&self) -> PathBuf {
749        self.state_path().join("agents")
750    }
751
752    /// Get the full path for sessions (within state directory)
753    pub fn sessions_path(&self) -> PathBuf {
754        self.state_path().join("sessions")
755    }
756
757    /// Create all configured directories if they don't exist
758    pub async fn ensure_directories(&self) -> Result<(), std::io::Error> {
759        if self.auto_create_dirs {
760            tokio::fs::create_dir_all(self.state_path()).await?;
761            tokio::fs::create_dir_all(self.logs_path()).await?;
762            tokio::fs::create_dir_all(self.prompts_path()).await?;
763            tokio::fs::create_dir_all(self.vector_db_path()).await?;
764
765            // Create subdirectories
766            tokio::fs::create_dir_all(self.agent_contexts_path()).await?;
767            tokio::fs::create_dir_all(self.sessions_path()).await?;
768            tokio::fs::create_dir_all(self.logs_path().join("system")).await?;
769            tokio::fs::create_dir_all(self.logs_path().join("agents")).await?;
770            tokio::fs::create_dir_all(self.logs_path().join("audit")).await?;
771            tokio::fs::create_dir_all(self.prompts_path().join("templates")).await?;
772            tokio::fs::create_dir_all(self.prompts_path().join("history")).await?;
773            tokio::fs::create_dir_all(self.prompts_path().join("cache")).await?;
774            tokio::fs::create_dir_all(self.vector_db_path().join("collections")).await?;
775            tokio::fs::create_dir_all(self.vector_db_path().join("indexes")).await?;
776            tokio::fs::create_dir_all(self.vector_db_path().join("metadata")).await?;
777        }
778        Ok(())
779    }
780
781    /// Migrate from legacy storage_path to new structure
782    pub async fn migrate_from_legacy(legacy_path: PathBuf) -> Result<Self, MigrationError> {
783        let config = Self::default();
784
785        // Copy existing context files to new state directory
786        if legacy_path.exists() {
787            let agents_path = config.agent_contexts_path();
788            config.ensure_directories().await?;
789
790            // Move existing context files
791            let mut entries = tokio::fs::read_dir(&legacy_path).await?;
792            while let Some(entry) = entries.next_entry().await? {
793                let file_path = entry.path();
794                if file_path
795                    .extension()
796                    .is_some_and(|ext| ext == "json" || ext == "gz")
797                {
798                    let dest_path = agents_path.join(entry.file_name());
799                    tokio::fs::copy(&file_path, &dest_path).await?;
800                }
801            }
802        }
803
804        Ok(config)
805    }
806
807    /// Check if this is a legacy configuration (has storage_path but not root_data_dir)
808    pub fn is_legacy(&self) -> bool {
809        // This method is conceptual - in practice we'd need to handle this differently
810        // since the struct doesn't have storage_path anymore. This would be used
811        // during config loading to detect legacy configs.
812        false
813    }
814}