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    ChromaDB,
551    Pinecone,
552    Weaviate,
553}
554
555/// Context-related errors
556#[derive(Debug, Clone, thiserror::Error)]
557pub enum ContextError {
558    #[error("Context not found: {id}")]
559    NotFound { id: ContextId },
560
561    #[error("Knowledge not found: {id}")]
562    KnowledgeNotFound { id: KnowledgeId },
563
564    #[error("Session not found: {id}")]
565    SessionNotFound { id: SessionId },
566
567    #[error("Storage error: {reason}")]
568    StorageError { reason: String },
569
570    #[error("Serialization error: {reason}")]
571    SerializationError { reason: String },
572
573    #[error("Query error: {reason}")]
574    QueryError { reason: String },
575
576    #[error("Policy violation: {reason}")]
577    PolicyViolation { reason: String },
578
579    #[error("Access denied: {reason}")]
580    AccessDenied { reason: String },
581
582    #[error("Invalid operation: {reason}")]
583    InvalidOperation { reason: String },
584
585    #[error("System error: {reason}")]
586    SystemError { reason: String },
587
588    #[error("Vector database error: {reason}")]
589    VectorDatabaseError { reason: String },
590
591    #[error("Embedding generation error: {reason}")]
592    EmbeddingError { reason: String },
593
594    #[error("Batch operation error: {reason}")]
595    BatchOperationError { reason: String },
596
597    #[error("Vector not found: {id}")]
598    VectorNotFound { id: VectorId },
599}
600
601impl Default for ContextQuery {
602    fn default() -> Self {
603        Self {
604            query_type: QueryType::Semantic,
605            search_terms: Vec::new(),
606            time_range: None,
607            memory_types: Vec::new(),
608            relevance_threshold: 0.7,
609            max_results: 10,
610            include_embeddings: false,
611        }
612    }
613}
614
615/// Trait for persistent storage of agent contexts
616#[async_trait]
617pub trait ContextPersistence: Send + Sync {
618    /// Save agent context to persistent storage
619    async fn save_context(
620        &self,
621        agent_id: AgentId,
622        context: &AgentContext,
623    ) -> Result<(), ContextError>;
624
625    /// Load agent context from persistent storage
626    async fn load_context(&self, agent_id: AgentId) -> Result<Option<AgentContext>, ContextError>;
627
628    /// Delete agent context from persistent storage
629    async fn delete_context(&self, agent_id: AgentId) -> Result<(), ContextError>;
630
631    /// List all agent IDs with stored contexts
632    async fn list_agent_contexts(&self) -> Result<Vec<AgentId>, ContextError>;
633
634    /// Check if context exists for agent
635    async fn context_exists(&self, agent_id: AgentId) -> Result<bool, ContextError>;
636
637    /// Get storage statistics
638    async fn get_storage_stats(&self) -> Result<StorageStats, ContextError>;
639
640    /// Enable downcasting for concrete implementations
641    fn as_any(&self) -> &dyn std::any::Any;
642}
643
644/// Storage statistics
645#[derive(Debug, Clone, Serialize, Deserialize)]
646pub struct StorageStats {
647    pub total_contexts: usize,
648    pub total_size_bytes: u64,
649    pub last_cleanup: SystemTime,
650    pub storage_path: PathBuf,
651}
652
653/// Configuration for file-based persistence
654#[derive(Debug, Clone, Serialize, Deserialize)]
655pub struct FilePersistenceConfig {
656    /// Root data directory (replaces storage_path)
657    pub root_data_dir: PathBuf,
658
659    /// Subdirectory paths (relative to root_data_dir)
660    pub state_dir: PathBuf,
661    pub logs_dir: PathBuf,
662    pub prompts_dir: PathBuf,
663    pub vector_db_dir: PathBuf,
664
665    /// Existing configuration options
666    pub enable_compression: bool,
667    pub enable_encryption: bool,
668    pub backup_count: usize,
669    pub auto_save_interval: u64,
670
671    /// New configuration options
672    pub auto_create_dirs: bool,
673    pub dir_permissions: Option<u32>,
674}
675
676impl Default for FilePersistenceConfig {
677    fn default() -> Self {
678        let mut root_dir = dirs::home_dir().unwrap_or_else(|| PathBuf::from("."));
679        root_dir.push(".symbiont");
680        root_dir.push("data");
681
682        Self {
683            root_data_dir: root_dir,
684            state_dir: PathBuf::from("state"),
685            logs_dir: PathBuf::from("logs"),
686            prompts_dir: PathBuf::from("prompts"),
687            vector_db_dir: PathBuf::from("vector_db"),
688            enable_compression: true,
689            enable_encryption: false,
690            backup_count: 3,
691            auto_save_interval: 300,
692            auto_create_dirs: true,
693            dir_permissions: Some(0o755),
694        }
695    }
696}
697
698/// Migration error types
699#[derive(Debug, Clone, thiserror::Error)]
700pub enum MigrationError {
701    #[error("IO error during migration: {reason}")]
702    IOError { reason: String },
703
704    #[error("Context error during migration: {error}")]
705    ContextError { error: ContextError },
706
707    #[error("Migration validation failed: {reason}")]
708    ValidationError { reason: String },
709}
710
711impl From<std::io::Error> for MigrationError {
712    fn from(error: std::io::Error) -> Self {
713        MigrationError::IOError {
714            reason: error.to_string(),
715        }
716    }
717}
718
719impl From<ContextError> for MigrationError {
720    fn from(error: ContextError) -> Self {
721        MigrationError::ContextError { error }
722    }
723}
724
725impl FilePersistenceConfig {
726    /// Get the full path for state storage
727    pub fn state_path(&self) -> PathBuf {
728        self.root_data_dir.join(&self.state_dir)
729    }
730
731    /// Get the full path for logs storage
732    pub fn logs_path(&self) -> PathBuf {
733        self.root_data_dir.join(&self.logs_dir)
734    }
735
736    /// Get the full path for prompts storage
737    pub fn prompts_path(&self) -> PathBuf {
738        self.root_data_dir.join(&self.prompts_dir)
739    }
740
741    /// Get the full path for vector database storage
742    pub fn vector_db_path(&self) -> PathBuf {
743        self.root_data_dir.join(&self.vector_db_dir)
744    }
745
746    /// Get the full path for agent contexts (within state directory)
747    pub fn agent_contexts_path(&self) -> PathBuf {
748        self.state_path().join("agents")
749    }
750
751    /// Get the full path for sessions (within state directory)
752    pub fn sessions_path(&self) -> PathBuf {
753        self.state_path().join("sessions")
754    }
755
756    /// Create all configured directories if they don't exist
757    pub async fn ensure_directories(&self) -> Result<(), std::io::Error> {
758        if self.auto_create_dirs {
759            tokio::fs::create_dir_all(self.state_path()).await?;
760            tokio::fs::create_dir_all(self.logs_path()).await?;
761            tokio::fs::create_dir_all(self.prompts_path()).await?;
762            tokio::fs::create_dir_all(self.vector_db_path()).await?;
763
764            // Create subdirectories
765            tokio::fs::create_dir_all(self.agent_contexts_path()).await?;
766            tokio::fs::create_dir_all(self.sessions_path()).await?;
767            tokio::fs::create_dir_all(self.logs_path().join("system")).await?;
768            tokio::fs::create_dir_all(self.logs_path().join("agents")).await?;
769            tokio::fs::create_dir_all(self.logs_path().join("audit")).await?;
770            tokio::fs::create_dir_all(self.prompts_path().join("templates")).await?;
771            tokio::fs::create_dir_all(self.prompts_path().join("history")).await?;
772            tokio::fs::create_dir_all(self.prompts_path().join("cache")).await?;
773            tokio::fs::create_dir_all(self.vector_db_path().join("collections")).await?;
774            tokio::fs::create_dir_all(self.vector_db_path().join("indexes")).await?;
775            tokio::fs::create_dir_all(self.vector_db_path().join("metadata")).await?;
776        }
777        Ok(())
778    }
779
780    /// Migrate from legacy storage_path to new structure
781    pub async fn migrate_from_legacy(legacy_path: PathBuf) -> Result<Self, MigrationError> {
782        let config = Self::default();
783
784        // Copy existing context files to new state directory
785        if legacy_path.exists() {
786            let agents_path = config.agent_contexts_path();
787            config.ensure_directories().await?;
788
789            // Move existing context files
790            let mut entries = tokio::fs::read_dir(&legacy_path).await?;
791            while let Some(entry) = entries.next_entry().await? {
792                let file_path = entry.path();
793                if file_path
794                    .extension()
795                    .is_some_and(|ext| ext == "json" || ext == "gz")
796                {
797                    let dest_path = agents_path.join(entry.file_name());
798                    tokio::fs::copy(&file_path, &dest_path).await?;
799                }
800            }
801        }
802
803        Ok(config)
804    }
805
806    /// Check if this is a legacy configuration (has storage_path but not root_data_dir)
807    pub fn is_legacy(&self) -> bool {
808        // This method is conceptual - in practice we'd need to handle this differently
809        // since the struct doesn't have storage_path anymore. This would be used
810        // during config loading to detect legacy configs.
811        false
812    }
813}