agent-runtime-core 0.3.0__py3-none-any.whl → 0.4.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -6,6 +6,8 @@ This module provides pluggable storage backends for:
6
6
  - Conversation history (full conversation state including tool calls)
7
7
  - Task state (task lists and progress)
8
8
  - Preferences (user and agent configuration)
9
+ - Knowledge base (facts, summaries, embeddings) - optional
10
+ - Audit/history (logs, errors, metrics) - optional
9
11
 
10
12
  Example usage:
11
13
  from agent_runtime_core.persistence import (
@@ -16,33 +18,51 @@ Example usage:
16
18
  PersistenceManager,
17
19
  Scope,
18
20
  )
19
-
21
+
20
22
  # Use the high-level manager
21
23
  manager = PersistenceManager()
22
-
24
+
23
25
  # Store global memory
24
26
  await manager.memory.set("user_name", "Alice", scope=Scope.GLOBAL)
25
-
27
+
26
28
  # Store project-specific memory
27
29
  await manager.memory.set("project_type", "python", scope=Scope.PROJECT)
28
-
30
+
29
31
  # Save a conversation
30
32
  await manager.conversations.save(conversation)
31
33
  """
32
34
 
33
35
  from agent_runtime_core.persistence.base import (
36
+ # Core stores
34
37
  MemoryStore,
35
38
  ConversationStore,
36
39
  TaskStore,
37
40
  PreferencesStore,
41
+ # Optional stores
42
+ KnowledgeStore,
43
+ AuditStore,
44
+ # Enums
38
45
  Scope,
46
+ TaskState,
47
+ FactType,
48
+ AuditEventType,
49
+ ErrorSeverity,
50
+ # Conversation models
39
51
  Conversation,
40
52
  ConversationMessage,
41
53
  ToolCall,
42
54
  ToolResult,
55
+ # Task models
43
56
  TaskList,
44
57
  Task,
45
- TaskState,
58
+ # Knowledge models
59
+ Fact,
60
+ Summary,
61
+ Embedding,
62
+ # Audit models
63
+ AuditEntry,
64
+ ErrorRecord,
65
+ PerformanceMetric,
46
66
  )
47
67
 
48
68
  from agent_runtime_core.persistence.file import (
@@ -60,20 +80,36 @@ from agent_runtime_core.persistence.manager import (
60
80
  )
61
81
 
62
82
  __all__ = [
63
- # Abstract interfaces
83
+ # Abstract interfaces - core
64
84
  "MemoryStore",
65
85
  "ConversationStore",
66
86
  "TaskStore",
67
87
  "PreferencesStore",
88
+ # Abstract interfaces - optional
89
+ "KnowledgeStore",
90
+ "AuditStore",
91
+ # Enums
68
92
  "Scope",
69
- # Data classes
93
+ "TaskState",
94
+ "FactType",
95
+ "AuditEventType",
96
+ "ErrorSeverity",
97
+ # Conversation models
70
98
  "Conversation",
71
99
  "ConversationMessage",
72
100
  "ToolCall",
73
101
  "ToolResult",
102
+ # Task models
74
103
  "TaskList",
75
104
  "Task",
76
- "TaskState",
105
+ # Knowledge models
106
+ "Fact",
107
+ "Summary",
108
+ "Embedding",
109
+ # Audit models
110
+ "AuditEntry",
111
+ "ErrorRecord",
112
+ "PerformanceMetric",
77
113
  # File implementations
78
114
  "FileMemoryStore",
79
115
  "FileConversationStore",
@@ -83,48 +83,56 @@ class ToolResult:
83
83
  @dataclass
84
84
  class ConversationMessage:
85
85
  """A message in a conversation with full state."""
86
-
86
+
87
87
  id: UUID
88
88
  role: str # system, user, assistant, tool
89
89
  content: str | dict | list
90
90
  timestamp: datetime = field(default_factory=datetime.utcnow)
91
-
91
+
92
92
  # For assistant messages with tool calls
93
93
  tool_calls: list[ToolCall] = field(default_factory=list)
94
-
94
+
95
95
  # For tool result messages
96
96
  tool_call_id: Optional[str] = None
97
-
97
+
98
98
  # Metadata
99
99
  model: Optional[str] = None
100
- usage: dict = field(default_factory=dict) # token counts
100
+ usage: dict = field(default_factory=dict) # token counts: {prompt_tokens, completion_tokens, total_tokens}
101
101
  metadata: dict = field(default_factory=dict)
102
102
 
103
+ # Branching support
104
+ parent_message_id: Optional[UUID] = None # For branched/edited messages
105
+ branch_id: Optional[UUID] = None # Groups messages in same branch
106
+
103
107
 
104
108
  @dataclass
105
109
  class Conversation:
106
110
  """A complete conversation with all state."""
107
-
111
+
108
112
  id: UUID
109
113
  title: Optional[str] = None
110
114
  messages: list[ConversationMessage] = field(default_factory=list)
111
-
115
+
112
116
  # Metadata
113
117
  created_at: datetime = field(default_factory=datetime.utcnow)
114
118
  updated_at: datetime = field(default_factory=datetime.utcnow)
115
119
  metadata: dict = field(default_factory=dict)
116
-
120
+
117
121
  # Associated agent
118
122
  agent_key: Optional[str] = None
119
-
123
+
120
124
  # Summary for long conversations
121
125
  summary: Optional[str] = None
122
126
 
127
+ # Branching support
128
+ parent_conversation_id: Optional[UUID] = None # For forked conversations
129
+ active_branch_id: Optional[UUID] = None # Currently active branch
130
+
123
131
 
124
132
  @dataclass
125
133
  class Task:
126
134
  """A task in a task list."""
127
-
135
+
128
136
  id: UUID
129
137
  name: str
130
138
  description: str = ""
@@ -134,17 +142,31 @@ class Task:
134
142
  updated_at: datetime = field(default_factory=datetime.utcnow)
135
143
  metadata: dict = field(default_factory=dict)
136
144
 
145
+ # Dependencies and scheduling
146
+ dependencies: list[UUID] = field(default_factory=list) # Task IDs this depends on
147
+ priority: int = 0 # Higher = more important
148
+ due_at: Optional[datetime] = None
149
+ completed_at: Optional[datetime] = None
150
+
151
+ # Checkpoint for resumable long-running operations
152
+ checkpoint_data: dict = field(default_factory=dict)
153
+ checkpoint_at: Optional[datetime] = None
154
+
155
+ # Execution tracking
156
+ attempts: int = 0
157
+ last_error: Optional[str] = None
158
+
137
159
 
138
160
  @dataclass
139
161
  class TaskList:
140
162
  """A list of tasks."""
141
-
163
+
142
164
  id: UUID
143
165
  name: str
144
166
  tasks: list[Task] = field(default_factory=list)
145
167
  created_at: datetime = field(default_factory=datetime.utcnow)
146
168
  updated_at: datetime = field(default_factory=datetime.utcnow)
147
-
169
+
148
170
  # Associated conversation/run
149
171
  conversation_id: Optional[UUID] = None
150
172
  run_id: Optional[UUID] = None
@@ -330,3 +352,386 @@ class PreferencesStore(ABC):
330
352
  """Close any connections. Override if needed."""
331
353
  pass
332
354
 
355
+
356
+ # =============================================================================
357
+ # Knowledge Base Models and Store
358
+ # =============================================================================
359
+
360
+
361
+ class FactType(str, Enum):
362
+ """Type of fact stored in knowledge base."""
363
+
364
+ USER = "user" # Facts about the user
365
+ PROJECT = "project" # Facts about the project
366
+ PREFERENCE = "preference" # Learned preferences
367
+ CONTEXT = "context" # Contextual information
368
+ CUSTOM = "custom" # Custom fact type
369
+
370
+
371
+ @dataclass
372
+ class Fact:
373
+ """A learned fact about user, project, or context."""
374
+
375
+ id: UUID
376
+ key: str # Unique identifier for the fact
377
+ value: Any # The fact content
378
+ fact_type: FactType = FactType.CUSTOM
379
+ confidence: float = 1.0 # 0.0 to 1.0
380
+ source: Optional[str] = None # Where this fact came from
381
+
382
+ created_at: datetime = field(default_factory=datetime.utcnow)
383
+ updated_at: datetime = field(default_factory=datetime.utcnow)
384
+ expires_at: Optional[datetime] = None # Optional expiration
385
+
386
+ metadata: dict = field(default_factory=dict)
387
+
388
+
389
+ @dataclass
390
+ class Summary:
391
+ """A summary of a conversation or set of interactions."""
392
+
393
+ id: UUID
394
+ content: str # The summary text
395
+
396
+ # What this summarizes
397
+ conversation_id: Optional[UUID] = None
398
+ conversation_ids: list[UUID] = field(default_factory=list) # For multi-conversation summaries
399
+
400
+ # Time range covered
401
+ start_time: Optional[datetime] = None
402
+ end_time: Optional[datetime] = None
403
+
404
+ created_at: datetime = field(default_factory=datetime.utcnow)
405
+ metadata: dict = field(default_factory=dict)
406
+
407
+
408
+ @dataclass
409
+ class Embedding:
410
+ """
411
+ A vector embedding for semantic search.
412
+
413
+ Note: This is optional and requires additional dependencies
414
+ for vector operations (e.g., numpy, faiss, pgvector).
415
+ """
416
+
417
+ id: UUID
418
+ vector: list[float] # The embedding vector
419
+
420
+ # What this embedding represents
421
+ content: str # Original text
422
+ content_type: str = "text" # text, summary, fact, etc.
423
+ source_id: Optional[UUID] = None # ID of source object
424
+
425
+ model: Optional[str] = None # Embedding model used
426
+ dimensions: int = 0 # Vector dimensions
427
+
428
+ created_at: datetime = field(default_factory=datetime.utcnow)
429
+ metadata: dict = field(default_factory=dict)
430
+
431
+
432
+ class KnowledgeStore(ABC):
433
+ """
434
+ Abstract interface for knowledge base storage.
435
+
436
+ Knowledge stores handle facts, summaries, and optionally
437
+ embeddings for semantic search. This is optional - agents
438
+ can function without a knowledge store.
439
+ """
440
+
441
+ # Fact operations
442
+ @abstractmethod
443
+ async def save_fact(self, fact: Fact, scope: Scope = Scope.PROJECT) -> None:
444
+ """Save or update a fact."""
445
+ ...
446
+
447
+ @abstractmethod
448
+ async def get_fact(self, fact_id: UUID, scope: Scope = Scope.PROJECT) -> Optional[Fact]:
449
+ """Get a fact by ID."""
450
+ ...
451
+
452
+ @abstractmethod
453
+ async def get_fact_by_key(self, key: str, scope: Scope = Scope.PROJECT) -> Optional[Fact]:
454
+ """Get a fact by its key."""
455
+ ...
456
+
457
+ @abstractmethod
458
+ async def list_facts(
459
+ self,
460
+ scope: Scope = Scope.PROJECT,
461
+ fact_type: Optional[FactType] = None,
462
+ limit: int = 100,
463
+ ) -> list[Fact]:
464
+ """List facts, optionally filtered by type."""
465
+ ...
466
+
467
+ @abstractmethod
468
+ async def delete_fact(self, fact_id: UUID, scope: Scope = Scope.PROJECT) -> bool:
469
+ """Delete a fact. Returns True if it existed."""
470
+ ...
471
+
472
+ # Summary operations
473
+ @abstractmethod
474
+ async def save_summary(self, summary: Summary, scope: Scope = Scope.PROJECT) -> None:
475
+ """Save or update a summary."""
476
+ ...
477
+
478
+ @abstractmethod
479
+ async def get_summary(self, summary_id: UUID, scope: Scope = Scope.PROJECT) -> Optional[Summary]:
480
+ """Get a summary by ID."""
481
+ ...
482
+
483
+ @abstractmethod
484
+ async def get_summaries_for_conversation(
485
+ self,
486
+ conversation_id: UUID,
487
+ scope: Scope = Scope.PROJECT,
488
+ ) -> list[Summary]:
489
+ """Get all summaries for a conversation."""
490
+ ...
491
+
492
+ @abstractmethod
493
+ async def delete_summary(self, summary_id: UUID, scope: Scope = Scope.PROJECT) -> bool:
494
+ """Delete a summary. Returns True if it existed."""
495
+ ...
496
+
497
+ # Embedding operations (optional - can raise NotImplementedError)
498
+ async def save_embedding(self, embedding: Embedding, scope: Scope = Scope.PROJECT) -> None:
499
+ """Save an embedding. Optional - may raise NotImplementedError."""
500
+ raise NotImplementedError("Embeddings not supported by this store")
501
+
502
+ async def search_similar(
503
+ self,
504
+ query_vector: list[float],
505
+ limit: int = 10,
506
+ scope: Scope = Scope.PROJECT,
507
+ content_type: Optional[str] = None,
508
+ ) -> list[tuple[Embedding, float]]:
509
+ """
510
+ Search for similar embeddings. Returns (embedding, score) tuples.
511
+ Optional - may raise NotImplementedError.
512
+ """
513
+ raise NotImplementedError("Embeddings not supported by this store")
514
+
515
+ async def delete_embedding(self, embedding_id: UUID, scope: Scope = Scope.PROJECT) -> bool:
516
+ """Delete an embedding. Optional - may raise NotImplementedError."""
517
+ raise NotImplementedError("Embeddings not supported by this store")
518
+
519
+ async def close(self) -> None:
520
+ """Close any connections. Override if needed."""
521
+ pass
522
+
523
+
524
+ # =============================================================================
525
+ # Audit/History Models and Store
526
+ # =============================================================================
527
+
528
+
529
+ class AuditEventType(str, Enum):
530
+ """Type of audit event."""
531
+
532
+ # Conversation events
533
+ CONVERSATION_START = "conversation_start"
534
+ CONVERSATION_END = "conversation_end"
535
+ MESSAGE_SENT = "message_sent"
536
+ MESSAGE_RECEIVED = "message_received"
537
+
538
+ # Tool events
539
+ TOOL_CALL = "tool_call"
540
+ TOOL_RESULT = "tool_result"
541
+ TOOL_ERROR = "tool_error"
542
+
543
+ # Agent events
544
+ AGENT_START = "agent_start"
545
+ AGENT_END = "agent_end"
546
+ AGENT_ERROR = "agent_error"
547
+
548
+ # System events
549
+ CHECKPOINT_SAVED = "checkpoint_saved"
550
+ CHECKPOINT_RESTORED = "checkpoint_restored"
551
+
552
+ CUSTOM = "custom"
553
+
554
+
555
+ class ErrorSeverity(str, Enum):
556
+ """Severity level for errors."""
557
+
558
+ DEBUG = "debug"
559
+ INFO = "info"
560
+ WARNING = "warning"
561
+ ERROR = "error"
562
+ CRITICAL = "critical"
563
+
564
+
565
+ @dataclass
566
+ class AuditEntry:
567
+ """An audit log entry for tracking interactions."""
568
+
569
+ id: UUID
570
+ event_type: AuditEventType
571
+ timestamp: datetime = field(default_factory=datetime.utcnow)
572
+
573
+ # Context
574
+ conversation_id: Optional[UUID] = None
575
+ run_id: Optional[UUID] = None
576
+ agent_key: Optional[str] = None
577
+
578
+ # Event details
579
+ action: str = "" # Human-readable action description
580
+ details: dict = field(default_factory=dict) # Event-specific data
581
+
582
+ # Actor information
583
+ actor_type: str = "agent" # agent, user, system
584
+ actor_id: Optional[str] = None
585
+
586
+ # Request/response tracking
587
+ request_id: Optional[str] = None
588
+ parent_event_id: Optional[UUID] = None # For nested events
589
+
590
+ metadata: dict = field(default_factory=dict)
591
+
592
+
593
+ @dataclass
594
+ class ErrorRecord:
595
+ """A record of an error for debugging."""
596
+
597
+ id: UUID
598
+ timestamp: datetime = field(default_factory=datetime.utcnow)
599
+ severity: ErrorSeverity = ErrorSeverity.ERROR
600
+
601
+ # Error details
602
+ error_type: str = "" # Exception class name
603
+ message: str = ""
604
+ stack_trace: Optional[str] = None
605
+
606
+ # Context
607
+ conversation_id: Optional[UUID] = None
608
+ run_id: Optional[UUID] = None
609
+ agent_key: Optional[str] = None
610
+
611
+ # What was happening when error occurred
612
+ context: dict = field(default_factory=dict)
613
+
614
+ # Resolution tracking
615
+ resolved: bool = False
616
+ resolved_at: Optional[datetime] = None
617
+ resolution_notes: Optional[str] = None
618
+
619
+ metadata: dict = field(default_factory=dict)
620
+
621
+
622
+ @dataclass
623
+ class PerformanceMetric:
624
+ """A performance metric for monitoring."""
625
+
626
+ id: UUID
627
+ name: str # Metric name (e.g., "llm_latency", "tool_execution_time")
628
+ value: float # Metric value
629
+ unit: str = "" # Unit of measurement (ms, tokens, etc.)
630
+
631
+ timestamp: datetime = field(default_factory=datetime.utcnow)
632
+
633
+ # Context
634
+ conversation_id: Optional[UUID] = None
635
+ run_id: Optional[UUID] = None
636
+ agent_key: Optional[str] = None
637
+
638
+ # Additional dimensions for grouping/filtering
639
+ tags: dict = field(default_factory=dict)
640
+
641
+ metadata: dict = field(default_factory=dict)
642
+
643
+
644
+ class AuditStore(ABC):
645
+ """
646
+ Abstract interface for audit and history storage.
647
+
648
+ Audit stores handle interaction logs, error history, and
649
+ performance metrics. This is optional - agents can function
650
+ without an audit store.
651
+ """
652
+
653
+ # Audit entry operations
654
+ @abstractmethod
655
+ async def log_event(self, entry: AuditEntry, scope: Scope = Scope.PROJECT) -> None:
656
+ """Log an audit event."""
657
+ ...
658
+
659
+ @abstractmethod
660
+ async def get_events(
661
+ self,
662
+ scope: Scope = Scope.PROJECT,
663
+ conversation_id: Optional[UUID] = None,
664
+ run_id: Optional[UUID] = None,
665
+ event_types: Optional[list[AuditEventType]] = None,
666
+ start_time: Optional[datetime] = None,
667
+ end_time: Optional[datetime] = None,
668
+ limit: int = 100,
669
+ ) -> list[AuditEntry]:
670
+ """Get audit events with optional filters."""
671
+ ...
672
+
673
+ # Error operations
674
+ @abstractmethod
675
+ async def log_error(self, error: ErrorRecord, scope: Scope = Scope.PROJECT) -> None:
676
+ """Log an error."""
677
+ ...
678
+
679
+ @abstractmethod
680
+ async def get_errors(
681
+ self,
682
+ scope: Scope = Scope.PROJECT,
683
+ severity: Optional[ErrorSeverity] = None,
684
+ resolved: Optional[bool] = None,
685
+ start_time: Optional[datetime] = None,
686
+ end_time: Optional[datetime] = None,
687
+ limit: int = 100,
688
+ ) -> list[ErrorRecord]:
689
+ """Get errors with optional filters."""
690
+ ...
691
+
692
+ @abstractmethod
693
+ async def resolve_error(
694
+ self,
695
+ error_id: UUID,
696
+ resolution_notes: Optional[str] = None,
697
+ scope: Scope = Scope.PROJECT,
698
+ ) -> bool:
699
+ """Mark an error as resolved. Returns True if error existed."""
700
+ ...
701
+
702
+ # Performance metric operations
703
+ @abstractmethod
704
+ async def record_metric(self, metric: PerformanceMetric, scope: Scope = Scope.PROJECT) -> None:
705
+ """Record a performance metric."""
706
+ ...
707
+
708
+ @abstractmethod
709
+ async def get_metrics(
710
+ self,
711
+ name: str,
712
+ scope: Scope = Scope.PROJECT,
713
+ start_time: Optional[datetime] = None,
714
+ end_time: Optional[datetime] = None,
715
+ tags: Optional[dict] = None,
716
+ limit: int = 1000,
717
+ ) -> list[PerformanceMetric]:
718
+ """Get metrics by name with optional filters."""
719
+ ...
720
+
721
+ @abstractmethod
722
+ async def get_metric_summary(
723
+ self,
724
+ name: str,
725
+ scope: Scope = Scope.PROJECT,
726
+ start_time: Optional[datetime] = None,
727
+ end_time: Optional[datetime] = None,
728
+ ) -> dict:
729
+ """
730
+ Get summary statistics for a metric.
731
+ Returns: {count, min, max, avg, sum, p50, p95, p99}
732
+ """
733
+ ...
734
+
735
+ async def close(self) -> None:
736
+ """Close any connections. Override if needed."""
737
+ pass