agent-runtime-core 0.1.5__tar.gz → 0.2.1__tar.gz

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.
Files changed (45) hide show
  1. {agent_runtime_core-0.1.5 → agent_runtime_core-0.2.1}/PKG-INFO +1 -1
  2. {agent_runtime_core-0.1.5 → agent_runtime_core-0.2.1}/agent_runtime/__init__.py +53 -1
  3. agent_runtime_core-0.2.1/agent_runtime/persistence/__init__.py +88 -0
  4. agent_runtime_core-0.2.1/agent_runtime/persistence/base.py +332 -0
  5. agent_runtime_core-0.2.1/agent_runtime/persistence/file.py +507 -0
  6. agent_runtime_core-0.2.1/agent_runtime/persistence/manager.py +266 -0
  7. {agent_runtime_core-0.1.5 → agent_runtime_core-0.2.1}/pyproject.toml +1 -1
  8. {agent_runtime_core-0.1.5 → agent_runtime_core-0.2.1}/tests/test_imports.py +4 -1
  9. agent_runtime_core-0.2.1/tests/test_persistence.py +404 -0
  10. {agent_runtime_core-0.1.5 → agent_runtime_core-0.2.1}/.gitignore +0 -0
  11. {agent_runtime_core-0.1.5 → agent_runtime_core-0.2.1}/LICENSE +0 -0
  12. {agent_runtime_core-0.1.5 → agent_runtime_core-0.2.1}/README.md +0 -0
  13. {agent_runtime_core-0.1.5 → agent_runtime_core-0.2.1}/agent_runtime/config.py +0 -0
  14. {agent_runtime_core-0.1.5 → agent_runtime_core-0.2.1}/agent_runtime/events/__init__.py +0 -0
  15. {agent_runtime_core-0.1.5 → agent_runtime_core-0.2.1}/agent_runtime/events/base.py +0 -0
  16. {agent_runtime_core-0.1.5 → agent_runtime_core-0.2.1}/agent_runtime/events/memory.py +0 -0
  17. {agent_runtime_core-0.1.5 → agent_runtime_core-0.2.1}/agent_runtime/events/redis.py +0 -0
  18. {agent_runtime_core-0.1.5 → agent_runtime_core-0.2.1}/agent_runtime/events/sqlite.py +0 -0
  19. {agent_runtime_core-0.1.5 → agent_runtime_core-0.2.1}/agent_runtime/interfaces.py +0 -0
  20. {agent_runtime_core-0.1.5 → agent_runtime_core-0.2.1}/agent_runtime/llm/__init__.py +0 -0
  21. {agent_runtime_core-0.1.5 → agent_runtime_core-0.2.1}/agent_runtime/llm/anthropic.py +0 -0
  22. {agent_runtime_core-0.1.5 → agent_runtime_core-0.2.1}/agent_runtime/llm/litellm_client.py +0 -0
  23. {agent_runtime_core-0.1.5 → agent_runtime_core-0.2.1}/agent_runtime/llm/openai.py +0 -0
  24. {agent_runtime_core-0.1.5 → agent_runtime_core-0.2.1}/agent_runtime/queue/__init__.py +0 -0
  25. {agent_runtime_core-0.1.5 → agent_runtime_core-0.2.1}/agent_runtime/queue/base.py +0 -0
  26. {agent_runtime_core-0.1.5 → agent_runtime_core-0.2.1}/agent_runtime/queue/memory.py +0 -0
  27. {agent_runtime_core-0.1.5 → agent_runtime_core-0.2.1}/agent_runtime/queue/redis.py +0 -0
  28. {agent_runtime_core-0.1.5 → agent_runtime_core-0.2.1}/agent_runtime/queue/sqlite.py +0 -0
  29. {agent_runtime_core-0.1.5 → agent_runtime_core-0.2.1}/agent_runtime/registry.py +0 -0
  30. {agent_runtime_core-0.1.5 → agent_runtime_core-0.2.1}/agent_runtime/runner.py +0 -0
  31. {agent_runtime_core-0.1.5 → agent_runtime_core-0.2.1}/agent_runtime/state/__init__.py +0 -0
  32. {agent_runtime_core-0.1.5 → agent_runtime_core-0.2.1}/agent_runtime/state/base.py +0 -0
  33. {agent_runtime_core-0.1.5 → agent_runtime_core-0.2.1}/agent_runtime/state/memory.py +0 -0
  34. {agent_runtime_core-0.1.5 → agent_runtime_core-0.2.1}/agent_runtime/state/redis.py +0 -0
  35. {agent_runtime_core-0.1.5 → agent_runtime_core-0.2.1}/agent_runtime/state/sqlite.py +0 -0
  36. {agent_runtime_core-0.1.5 → agent_runtime_core-0.2.1}/agent_runtime/testing.py +0 -0
  37. {agent_runtime_core-0.1.5 → agent_runtime_core-0.2.1}/agent_runtime/tracing/__init__.py +0 -0
  38. {agent_runtime_core-0.1.5 → agent_runtime_core-0.2.1}/agent_runtime/tracing/langfuse.py +0 -0
  39. {agent_runtime_core-0.1.5 → agent_runtime_core-0.2.1}/agent_runtime/tracing/noop.py +0 -0
  40. {agent_runtime_core-0.1.5 → agent_runtime_core-0.2.1}/tests/__init__.py +0 -0
  41. {agent_runtime_core-0.1.5 → agent_runtime_core-0.2.1}/tests/test_events.py +0 -0
  42. {agent_runtime_core-0.1.5 → agent_runtime_core-0.2.1}/tests/test_queue.py +0 -0
  43. {agent_runtime_core-0.1.5 → agent_runtime_core-0.2.1}/tests/test_state.py +0 -0
  44. {agent_runtime_core-0.1.5 → agent_runtime_core-0.2.1}/tests/test_testing.py +0 -0
  45. {agent_runtime_core-0.1.5 → agent_runtime_core-0.2.1}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: agent-runtime-core
3
- Version: 0.1.5
3
+ Version: 0.2.1
4
4
  Summary: Framework-agnostic Python library for executing AI agents with consistent patterns
5
5
  Project-URL: Homepage, https://github.com/colstrom/agent_runtime_core
6
6
  Project-URL: Repository, https://github.com/colstrom/agent_runtime_core
@@ -34,7 +34,7 @@ Example usage:
34
34
  return RunResult(final_output={"message": "Hello!"})
35
35
  """
36
36
 
37
- __version__ = "0.1.5"
37
+ __version__ = "0.2.1"
38
38
 
39
39
  # Core interfaces
40
40
  from agent_runtime.interfaces import (
@@ -87,6 +87,34 @@ from agent_runtime.testing import (
87
87
  run_agent_test,
88
88
  )
89
89
 
90
+ # Persistence (memory, conversations, tasks, preferences)
91
+ from agent_runtime.persistence import (
92
+ # Abstract interfaces
93
+ MemoryStore,
94
+ ConversationStore,
95
+ TaskStore,
96
+ PreferencesStore,
97
+ Scope,
98
+ # Data classes
99
+ Conversation,
100
+ ConversationMessage,
101
+ ToolCall,
102
+ ToolResult,
103
+ TaskList,
104
+ Task,
105
+ TaskState,
106
+ # File implementations
107
+ FileMemoryStore,
108
+ FileConversationStore,
109
+ FileTaskStore,
110
+ FilePreferencesStore,
111
+ # Manager
112
+ PersistenceManager,
113
+ PersistenceConfig,
114
+ get_persistence_manager,
115
+ configure_persistence,
116
+ )
117
+
90
118
  __all__ = [
91
119
  # Version
92
120
  "__version__",
@@ -125,4 +153,28 @@ __all__ = [
125
153
  "LLMEvaluator",
126
154
  "create_test_context",
127
155
  "run_agent_test",
156
+ # Persistence - Abstract interfaces
157
+ "MemoryStore",
158
+ "ConversationStore",
159
+ "TaskStore",
160
+ "PreferencesStore",
161
+ "Scope",
162
+ # Persistence - Data classes
163
+ "Conversation",
164
+ "ConversationMessage",
165
+ "ToolCall",
166
+ "ToolResult",
167
+ "TaskList",
168
+ "Task",
169
+ "TaskState",
170
+ # Persistence - File implementations
171
+ "FileMemoryStore",
172
+ "FileConversationStore",
173
+ "FileTaskStore",
174
+ "FilePreferencesStore",
175
+ # Persistence - Manager
176
+ "PersistenceManager",
177
+ "PersistenceConfig",
178
+ "get_persistence_manager",
179
+ "configure_persistence",
128
180
  ]
@@ -0,0 +1,88 @@
1
+ """
2
+ Persistence module for agent state, memory, and conversation history.
3
+
4
+ This module provides pluggable storage backends for:
5
+ - Memory (global and project-scoped key-value storage)
6
+ - Conversation history (full conversation state including tool calls)
7
+ - Task state (task lists and progress)
8
+ - Preferences (user and agent configuration)
9
+
10
+ Example usage:
11
+ from agent_runtime.persistence import (
12
+ MemoryStore,
13
+ ConversationStore,
14
+ FileMemoryStore,
15
+ FileConversationStore,
16
+ PersistenceManager,
17
+ Scope,
18
+ )
19
+
20
+ # Use the high-level manager
21
+ manager = PersistenceManager()
22
+
23
+ # Store global memory
24
+ await manager.memory.set("user_name", "Alice", scope=Scope.GLOBAL)
25
+
26
+ # Store project-specific memory
27
+ await manager.memory.set("project_type", "python", scope=Scope.PROJECT)
28
+
29
+ # Save a conversation
30
+ await manager.conversations.save(conversation)
31
+ """
32
+
33
+ from agent_runtime.persistence.base import (
34
+ MemoryStore,
35
+ ConversationStore,
36
+ TaskStore,
37
+ PreferencesStore,
38
+ Scope,
39
+ Conversation,
40
+ ConversationMessage,
41
+ ToolCall,
42
+ ToolResult,
43
+ TaskList,
44
+ Task,
45
+ TaskState,
46
+ )
47
+
48
+ from agent_runtime.persistence.file import (
49
+ FileMemoryStore,
50
+ FileConversationStore,
51
+ FileTaskStore,
52
+ FilePreferencesStore,
53
+ )
54
+
55
+ from agent_runtime.persistence.manager import (
56
+ PersistenceManager,
57
+ PersistenceConfig,
58
+ get_persistence_manager,
59
+ configure_persistence,
60
+ )
61
+
62
+ __all__ = [
63
+ # Abstract interfaces
64
+ "MemoryStore",
65
+ "ConversationStore",
66
+ "TaskStore",
67
+ "PreferencesStore",
68
+ "Scope",
69
+ # Data classes
70
+ "Conversation",
71
+ "ConversationMessage",
72
+ "ToolCall",
73
+ "ToolResult",
74
+ "TaskList",
75
+ "Task",
76
+ "TaskState",
77
+ # File implementations
78
+ "FileMemoryStore",
79
+ "FileConversationStore",
80
+ "FileTaskStore",
81
+ "FilePreferencesStore",
82
+ # Manager
83
+ "PersistenceManager",
84
+ "PersistenceConfig",
85
+ "get_persistence_manager",
86
+ "configure_persistence",
87
+ ]
88
+
@@ -0,0 +1,332 @@
1
+ """
2
+ Abstract base classes for persistence backends.
3
+
4
+ These interfaces define the contract that all storage backends must implement.
5
+ Projects depending on agent-runtime-core can provide their own implementations
6
+ (e.g., database-backed, cloud storage, etc.).
7
+
8
+ For Django/database implementations:
9
+ - The `scope` parameter can be ignored if you use user/tenant context instead
10
+ - Store implementations receive context through their constructor (e.g., user, org)
11
+ - The abstract methods still accept scope for interface compatibility, but
12
+ implementations can choose to ignore it
13
+
14
+ Example Django implementation:
15
+ class DjangoMemoryStore(MemoryStore):
16
+ def __init__(self, user):
17
+ self.user = user
18
+
19
+ async def get(self, key: str, scope: Scope = Scope.PROJECT) -> Optional[Any]:
20
+ # Ignore scope, use self.user instead
21
+ try:
22
+ entry = await Memory.objects.aget(user=self.user, key=key)
23
+ return entry.value
24
+ except Memory.DoesNotExist:
25
+ return None
26
+ """
27
+
28
+ from abc import ABC, abstractmethod
29
+ from dataclasses import dataclass, field
30
+ from datetime import datetime
31
+ from enum import Enum
32
+ from typing import Any, Optional, AsyncIterator
33
+ from uuid import UUID
34
+
35
+
36
+ class Scope(str, Enum):
37
+ """
38
+ Storage scope for memory and other persistent data.
39
+
40
+ For file-based storage:
41
+ - GLOBAL: User's home directory (~/.agent_runtime/)
42
+ - PROJECT: Current working directory (./.agent_runtime/)
43
+ - SESSION: In-memory only, not persisted
44
+
45
+ For database-backed storage, implementations may ignore this
46
+ and use user/tenant context from the store constructor instead.
47
+ """
48
+
49
+ GLOBAL = "global"
50
+ PROJECT = "project"
51
+ SESSION = "session"
52
+
53
+
54
+ class TaskState(str, Enum):
55
+ """State of a task."""
56
+
57
+ NOT_STARTED = "not_started"
58
+ IN_PROGRESS = "in_progress"
59
+ COMPLETE = "complete"
60
+ CANCELLED = "cancelled"
61
+
62
+
63
+ @dataclass
64
+ class ToolCall:
65
+ """A tool call made during a conversation."""
66
+
67
+ id: str
68
+ name: str
69
+ arguments: dict
70
+ timestamp: datetime = field(default_factory=datetime.utcnow)
71
+
72
+
73
+ @dataclass
74
+ class ToolResult:
75
+ """Result of a tool call."""
76
+
77
+ tool_call_id: str
78
+ result: Any
79
+ error: Optional[str] = None
80
+ timestamp: datetime = field(default_factory=datetime.utcnow)
81
+
82
+
83
+ @dataclass
84
+ class ConversationMessage:
85
+ """A message in a conversation with full state."""
86
+
87
+ id: UUID
88
+ role: str # system, user, assistant, tool
89
+ content: str | dict | list
90
+ timestamp: datetime = field(default_factory=datetime.utcnow)
91
+
92
+ # For assistant messages with tool calls
93
+ tool_calls: list[ToolCall] = field(default_factory=list)
94
+
95
+ # For tool result messages
96
+ tool_call_id: Optional[str] = None
97
+
98
+ # Metadata
99
+ model: Optional[str] = None
100
+ usage: dict = field(default_factory=dict) # token counts
101
+ metadata: dict = field(default_factory=dict)
102
+
103
+
104
+ @dataclass
105
+ class Conversation:
106
+ """A complete conversation with all state."""
107
+
108
+ id: UUID
109
+ title: Optional[str] = None
110
+ messages: list[ConversationMessage] = field(default_factory=list)
111
+
112
+ # Metadata
113
+ created_at: datetime = field(default_factory=datetime.utcnow)
114
+ updated_at: datetime = field(default_factory=datetime.utcnow)
115
+ metadata: dict = field(default_factory=dict)
116
+
117
+ # Associated agent
118
+ agent_key: Optional[str] = None
119
+
120
+ # Summary for long conversations
121
+ summary: Optional[str] = None
122
+
123
+
124
+ @dataclass
125
+ class Task:
126
+ """A task in a task list."""
127
+
128
+ id: UUID
129
+ name: str
130
+ description: str = ""
131
+ state: TaskState = TaskState.NOT_STARTED
132
+ parent_id: Optional[UUID] = None
133
+ created_at: datetime = field(default_factory=datetime.utcnow)
134
+ updated_at: datetime = field(default_factory=datetime.utcnow)
135
+ metadata: dict = field(default_factory=dict)
136
+
137
+
138
+ @dataclass
139
+ class TaskList:
140
+ """A list of tasks."""
141
+
142
+ id: UUID
143
+ name: str
144
+ tasks: list[Task] = field(default_factory=list)
145
+ created_at: datetime = field(default_factory=datetime.utcnow)
146
+ updated_at: datetime = field(default_factory=datetime.utcnow)
147
+
148
+ # Associated conversation/run
149
+ conversation_id: Optional[UUID] = None
150
+ run_id: Optional[UUID] = None
151
+
152
+
153
+ class MemoryStore(ABC):
154
+ """
155
+ Abstract interface for key-value memory storage.
156
+
157
+ Memory stores handle persistent key-value data that agents can
158
+ use to remember information across sessions.
159
+ """
160
+
161
+ @abstractmethod
162
+ async def get(self, key: str, scope: Scope = Scope.PROJECT) -> Optional[Any]:
163
+ """Get a value by key."""
164
+ ...
165
+
166
+ @abstractmethod
167
+ async def set(self, key: str, value: Any, scope: Scope = Scope.PROJECT) -> None:
168
+ """Set a value by key."""
169
+ ...
170
+
171
+ @abstractmethod
172
+ async def delete(self, key: str, scope: Scope = Scope.PROJECT) -> bool:
173
+ """Delete a key. Returns True if key existed."""
174
+ ...
175
+
176
+ @abstractmethod
177
+ async def list_keys(self, scope: Scope = Scope.PROJECT, prefix: Optional[str] = None) -> list[str]:
178
+ """List all keys, optionally filtered by prefix."""
179
+ ...
180
+
181
+ @abstractmethod
182
+ async def clear(self, scope: Scope = Scope.PROJECT) -> None:
183
+ """Clear all keys in the given scope."""
184
+ ...
185
+
186
+ async def close(self) -> None:
187
+ """Close any connections. Override if needed."""
188
+ pass
189
+
190
+
191
+ class ConversationStore(ABC):
192
+ """
193
+ Abstract interface for conversation history storage.
194
+
195
+ Conversation stores handle full conversation state including
196
+ messages, tool calls, and metadata.
197
+ """
198
+
199
+ @abstractmethod
200
+ async def save(self, conversation: Conversation, scope: Scope = Scope.PROJECT) -> None:
201
+ """Save or update a conversation."""
202
+ ...
203
+
204
+ @abstractmethod
205
+ async def get(self, conversation_id: UUID, scope: Scope = Scope.PROJECT) -> Optional[Conversation]:
206
+ """Get a conversation by ID."""
207
+ ...
208
+
209
+ @abstractmethod
210
+ async def delete(self, conversation_id: UUID, scope: Scope = Scope.PROJECT) -> bool:
211
+ """Delete a conversation. Returns True if it existed."""
212
+ ...
213
+
214
+ @abstractmethod
215
+ async def list_conversations(
216
+ self,
217
+ scope: Scope = Scope.PROJECT,
218
+ limit: int = 100,
219
+ offset: int = 0,
220
+ agent_key: Optional[str] = None,
221
+ ) -> list[Conversation]:
222
+ """List conversations, optionally filtered by agent."""
223
+ ...
224
+
225
+ @abstractmethod
226
+ async def add_message(
227
+ self,
228
+ conversation_id: UUID,
229
+ message: ConversationMessage,
230
+ scope: Scope = Scope.PROJECT,
231
+ ) -> None:
232
+ """Add a message to an existing conversation."""
233
+ ...
234
+
235
+ @abstractmethod
236
+ async def get_messages(
237
+ self,
238
+ conversation_id: UUID,
239
+ scope: Scope = Scope.PROJECT,
240
+ limit: Optional[int] = None,
241
+ before: Optional[datetime] = None,
242
+ ) -> list[ConversationMessage]:
243
+ """Get messages from a conversation."""
244
+ ...
245
+
246
+ async def close(self) -> None:
247
+ """Close any connections. Override if needed."""
248
+ pass
249
+
250
+
251
+ class TaskStore(ABC):
252
+ """
253
+ Abstract interface for task list storage.
254
+
255
+ Task stores handle task lists and their state for tracking
256
+ agent progress on complex work.
257
+ """
258
+
259
+ @abstractmethod
260
+ async def save(self, task_list: TaskList, scope: Scope = Scope.PROJECT) -> None:
261
+ """Save or update a task list."""
262
+ ...
263
+
264
+ @abstractmethod
265
+ async def get(self, task_list_id: UUID, scope: Scope = Scope.PROJECT) -> Optional[TaskList]:
266
+ """Get a task list by ID."""
267
+ ...
268
+
269
+ @abstractmethod
270
+ async def delete(self, task_list_id: UUID, scope: Scope = Scope.PROJECT) -> bool:
271
+ """Delete a task list. Returns True if it existed."""
272
+ ...
273
+
274
+ @abstractmethod
275
+ async def get_by_conversation(
276
+ self,
277
+ conversation_id: UUID,
278
+ scope: Scope = Scope.PROJECT,
279
+ ) -> Optional[TaskList]:
280
+ """Get the task list associated with a conversation."""
281
+ ...
282
+
283
+ @abstractmethod
284
+ async def update_task(
285
+ self,
286
+ task_list_id: UUID,
287
+ task_id: UUID,
288
+ state: Optional[TaskState] = None,
289
+ name: Optional[str] = None,
290
+ description: Optional[str] = None,
291
+ scope: Scope = Scope.PROJECT,
292
+ ) -> None:
293
+ """Update a specific task in a task list."""
294
+ ...
295
+
296
+ async def close(self) -> None:
297
+ """Close any connections. Override if needed."""
298
+ pass
299
+
300
+
301
+ class PreferencesStore(ABC):
302
+ """
303
+ Abstract interface for preferences storage.
304
+
305
+ Preferences stores handle user and agent configuration
306
+ that persists across sessions.
307
+ """
308
+
309
+ @abstractmethod
310
+ async def get(self, key: str, scope: Scope = Scope.GLOBAL) -> Optional[Any]:
311
+ """Get a preference value."""
312
+ ...
313
+
314
+ @abstractmethod
315
+ async def set(self, key: str, value: Any, scope: Scope = Scope.GLOBAL) -> None:
316
+ """Set a preference value."""
317
+ ...
318
+
319
+ @abstractmethod
320
+ async def delete(self, key: str, scope: Scope = Scope.GLOBAL) -> bool:
321
+ """Delete a preference. Returns True if it existed."""
322
+ ...
323
+
324
+ @abstractmethod
325
+ async def get_all(self, scope: Scope = Scope.GLOBAL) -> dict[str, Any]:
326
+ """Get all preferences in the given scope."""
327
+ ...
328
+
329
+ async def close(self) -> None:
330
+ """Close any connections. Override if needed."""
331
+ pass
332
+