agent-runtime-core 0.1.4__py3-none-any.whl → 0.2.1__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.
agent_runtime/__init__.py CHANGED
@@ -34,7 +34,7 @@ Example usage:
34
34
  return RunResult(final_output={"message": "Hello!"})
35
35
  """
36
36
 
37
- __version__ = "0.1.4"
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
  ]
@@ -40,7 +40,7 @@ class AnthropicClient(LLMClient):
40
40
  if AsyncAnthropic is None:
41
41
  raise ImportError(
42
42
  "anthropic package is required for AnthropicClient. "
43
- "Install it with: pip install agent_runtime[anthropic]"
43
+ "Install it with: pip install agent-runtime-core[anthropic]"
44
44
  )
45
45
 
46
46
  from agent_runtime.config import get_config
@@ -38,7 +38,7 @@ class LiteLLMClient(LLMClient):
38
38
  if litellm is None:
39
39
  raise ImportError(
40
40
  "litellm package is required for LiteLLMClient. "
41
- "Install it with: pip install agent_runtime[litellm]"
41
+ "Install it with: pip install agent-runtime-core[litellm]"
42
42
  )
43
43
 
44
44
  from agent_runtime.config import get_config
@@ -42,7 +42,7 @@ class OpenAIClient(LLMClient):
42
42
  if AsyncOpenAI is None:
43
43
  raise ImportError(
44
44
  "openai package is required for OpenAIClient. "
45
- "Install it with: pip install agent_runtime[openai]"
45
+ "Install it with: pip install agent-runtime-core[openai]"
46
46
  )
47
47
 
48
48
  from agent_runtime.config import get_config
@@ -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
+
@@ -0,0 +1,507 @@
1
+ """
2
+ File-based implementations of persistence stores.
3
+
4
+ These implementations store data in hidden directories:
5
+ - Global: ~/.agent_runtime/
6
+ - Project: ./.agent_runtime/
7
+
8
+ Data is stored as JSON files for easy inspection and debugging.
9
+ """
10
+
11
+ import json
12
+ import os
13
+ from datetime import datetime
14
+ from pathlib import Path
15
+ from typing import Any, Optional
16
+ from uuid import UUID
17
+
18
+ from agent_runtime.persistence.base import (
19
+ MemoryStore,
20
+ ConversationStore,
21
+ TaskStore,
22
+ PreferencesStore,
23
+ Scope,
24
+ Conversation,
25
+ ConversationMessage,
26
+ ToolCall,
27
+ ToolResult,
28
+ TaskList,
29
+ Task,
30
+ TaskState,
31
+ )
32
+
33
+
34
+ def _get_base_path(scope: Scope, project_dir: Optional[Path] = None) -> Path:
35
+ """Get the base path for a given scope."""
36
+ if scope == Scope.GLOBAL:
37
+ return Path.home() / ".agent_runtime"
38
+ elif scope == Scope.PROJECT:
39
+ base = project_dir or Path.cwd()
40
+ return base / ".agent_runtime"
41
+ else:
42
+ raise ValueError(f"Cannot get path for scope: {scope}")
43
+
44
+
45
+ def _ensure_dir(path: Path) -> None:
46
+ """Ensure a directory exists."""
47
+ path.mkdir(parents=True, exist_ok=True)
48
+
49
+
50
+ class _JSONEncoder(json.JSONEncoder):
51
+ """Custom JSON encoder for our data types."""
52
+
53
+ def default(self, obj):
54
+ if isinstance(obj, UUID):
55
+ return str(obj)
56
+ if isinstance(obj, datetime):
57
+ return obj.isoformat()
58
+ if isinstance(obj, TaskState):
59
+ return obj.value
60
+ if hasattr(obj, '__dataclass_fields__'):
61
+ return {k: getattr(obj, k) for k in obj.__dataclass_fields__}
62
+ return super().default(obj)
63
+
64
+
65
+ def _json_dumps(obj: Any) -> str:
66
+ """Serialize object to JSON string."""
67
+ return json.dumps(obj, cls=_JSONEncoder, indent=2)
68
+
69
+
70
+ def _parse_datetime(value: Any) -> datetime:
71
+ """Parse a datetime from string or return as-is."""
72
+ if isinstance(value, datetime):
73
+ return value
74
+ if isinstance(value, str):
75
+ return datetime.fromisoformat(value)
76
+ return value
77
+
78
+
79
+ def _parse_uuid(value: Any) -> UUID:
80
+ """Parse a UUID from string or return as-is."""
81
+ if isinstance(value, UUID):
82
+ return value
83
+ if isinstance(value, str):
84
+ return UUID(value)
85
+ return value
86
+
87
+
88
+ class FileMemoryStore(MemoryStore):
89
+ """
90
+ File-based memory store.
91
+
92
+ Stores key-value pairs in JSON files:
93
+ - {base_path}/memory/{key}.json
94
+ """
95
+
96
+ def __init__(self, project_dir: Optional[Path] = None):
97
+ self._project_dir = project_dir
98
+
99
+ def _get_memory_path(self, scope: Scope) -> Path:
100
+ return _get_base_path(scope, self._project_dir) / "memory"
101
+
102
+ def _get_key_path(self, key: str, scope: Scope) -> Path:
103
+ # Sanitize key for filesystem
104
+ safe_key = key.replace("/", "_").replace("\\", "_")
105
+ return self._get_memory_path(scope) / f"{safe_key}.json"
106
+
107
+ async def get(self, key: str, scope: Scope = Scope.PROJECT) -> Optional[Any]:
108
+ path = self._get_key_path(key, scope)
109
+ if not path.exists():
110
+ return None
111
+ try:
112
+ with open(path, "r") as f:
113
+ data = json.load(f)
114
+ return data.get("value")
115
+ except (json.JSONDecodeError, IOError):
116
+ return None
117
+
118
+ async def set(self, key: str, value: Any, scope: Scope = Scope.PROJECT) -> None:
119
+ path = self._get_key_path(key, scope)
120
+ _ensure_dir(path.parent)
121
+ with open(path, "w") as f:
122
+ f.write(_json_dumps({
123
+ "key": key,
124
+ "value": value,
125
+ "updated_at": datetime.utcnow(),
126
+ }))
127
+
128
+ async def delete(self, key: str, scope: Scope = Scope.PROJECT) -> bool:
129
+ path = self._get_key_path(key, scope)
130
+ if path.exists():
131
+ path.unlink()
132
+ return True
133
+ return False
134
+
135
+ async def list_keys(self, scope: Scope = Scope.PROJECT, prefix: Optional[str] = None) -> list[str]:
136
+ memory_path = self._get_memory_path(scope)
137
+ if not memory_path.exists():
138
+ return []
139
+ keys = []
140
+ for file in memory_path.glob("*.json"):
141
+ key = file.stem
142
+ if prefix is None or key.startswith(prefix):
143
+ keys.append(key)
144
+ return sorted(keys)
145
+
146
+ async def clear(self, scope: Scope = Scope.PROJECT) -> None:
147
+ memory_path = self._get_memory_path(scope)
148
+ if memory_path.exists():
149
+ for file in memory_path.glob("*.json"):
150
+ file.unlink()
151
+
152
+
153
+ class FileConversationStore(ConversationStore):
154
+ """
155
+ File-based conversation store.
156
+
157
+ Stores conversations in JSON files:
158
+ - {base_path}/conversations/{conversation_id}.json
159
+ """
160
+
161
+ def __init__(self, project_dir: Optional[Path] = None):
162
+ self._project_dir = project_dir
163
+
164
+ def _get_conversations_path(self, scope: Scope) -> Path:
165
+ return _get_base_path(scope, self._project_dir) / "conversations"
166
+
167
+ def _get_conversation_path(self, conversation_id: UUID, scope: Scope) -> Path:
168
+ return self._get_conversations_path(scope) / f"{conversation_id}.json"
169
+
170
+ def _serialize_conversation(self, conversation: Conversation) -> dict:
171
+ """Serialize a conversation to a dict."""
172
+ return {
173
+ "id": str(conversation.id),
174
+ "title": conversation.title,
175
+ "messages": [self._serialize_message(m) for m in conversation.messages],
176
+ "created_at": conversation.created_at.isoformat(),
177
+ "updated_at": conversation.updated_at.isoformat(),
178
+ "metadata": conversation.metadata,
179
+ "agent_key": conversation.agent_key,
180
+ "summary": conversation.summary,
181
+ }
182
+
183
+ def _serialize_message(self, message: ConversationMessage) -> dict:
184
+ """Serialize a message to a dict."""
185
+ return {
186
+ "id": str(message.id),
187
+ "role": message.role,
188
+ "content": message.content,
189
+ "timestamp": message.timestamp.isoformat(),
190
+ "tool_calls": [
191
+ {
192
+ "id": tc.id,
193
+ "name": tc.name,
194
+ "arguments": tc.arguments,
195
+ "timestamp": tc.timestamp.isoformat(),
196
+ }
197
+ for tc in message.tool_calls
198
+ ],
199
+ "tool_call_id": message.tool_call_id,
200
+ "model": message.model,
201
+ "usage": message.usage,
202
+ "metadata": message.metadata,
203
+ }
204
+
205
+ def _deserialize_conversation(self, data: dict) -> Conversation:
206
+ """Deserialize a conversation from a dict."""
207
+ return Conversation(
208
+ id=_parse_uuid(data["id"]),
209
+ title=data.get("title"),
210
+ messages=[self._deserialize_message(m) for m in data.get("messages", [])],
211
+ created_at=_parse_datetime(data["created_at"]),
212
+ updated_at=_parse_datetime(data["updated_at"]),
213
+ metadata=data.get("metadata", {}),
214
+ agent_key=data.get("agent_key"),
215
+ summary=data.get("summary"),
216
+ )
217
+
218
+ def _deserialize_message(self, data: dict) -> ConversationMessage:
219
+ """Deserialize a message from a dict."""
220
+ return ConversationMessage(
221
+ id=_parse_uuid(data["id"]),
222
+ role=data["role"],
223
+ content=data["content"],
224
+ timestamp=_parse_datetime(data["timestamp"]),
225
+ tool_calls=[
226
+ ToolCall(
227
+ id=tc["id"],
228
+ name=tc["name"],
229
+ arguments=tc["arguments"],
230
+ timestamp=_parse_datetime(tc["timestamp"]),
231
+ )
232
+ for tc in data.get("tool_calls", [])
233
+ ],
234
+ tool_call_id=data.get("tool_call_id"),
235
+ model=data.get("model"),
236
+ usage=data.get("usage", {}),
237
+ metadata=data.get("metadata", {}),
238
+ )
239
+
240
+ async def save(self, conversation: Conversation, scope: Scope = Scope.PROJECT) -> None:
241
+ path = self._get_conversation_path(conversation.id, scope)
242
+ _ensure_dir(path.parent)
243
+ conversation.updated_at = datetime.utcnow()
244
+ with open(path, "w") as f:
245
+ f.write(_json_dumps(self._serialize_conversation(conversation)))
246
+
247
+ async def get(self, conversation_id: UUID, scope: Scope = Scope.PROJECT) -> Optional[Conversation]:
248
+ path = self._get_conversation_path(conversation_id, scope)
249
+ if not path.exists():
250
+ return None
251
+ try:
252
+ with open(path, "r") as f:
253
+ data = json.load(f)
254
+ return self._deserialize_conversation(data)
255
+ except (json.JSONDecodeError, IOError):
256
+ return None
257
+
258
+ async def delete(self, conversation_id: UUID, scope: Scope = Scope.PROJECT) -> bool:
259
+ path = self._get_conversation_path(conversation_id, scope)
260
+ if path.exists():
261
+ path.unlink()
262
+ return True
263
+ return False
264
+
265
+ async def list_conversations(
266
+ self,
267
+ scope: Scope = Scope.PROJECT,
268
+ limit: int = 100,
269
+ offset: int = 0,
270
+ agent_key: Optional[str] = None,
271
+ ) -> list[Conversation]:
272
+ conversations_path = self._get_conversations_path(scope)
273
+ if not conversations_path.exists():
274
+ return []
275
+
276
+ conversations = []
277
+ for file in conversations_path.glob("*.json"):
278
+ try:
279
+ with open(file, "r") as f:
280
+ data = json.load(f)
281
+ conv = self._deserialize_conversation(data)
282
+ if agent_key is None or conv.agent_key == agent_key:
283
+ conversations.append(conv)
284
+ except (json.JSONDecodeError, IOError):
285
+ continue
286
+
287
+ # Sort by updated_at descending
288
+ conversations.sort(key=lambda c: c.updated_at, reverse=True)
289
+ return conversations[offset:offset + limit]
290
+
291
+ async def add_message(
292
+ self,
293
+ conversation_id: UUID,
294
+ message: ConversationMessage,
295
+ scope: Scope = Scope.PROJECT,
296
+ ) -> None:
297
+ conversation = await self.get(conversation_id, scope)
298
+ if conversation is None:
299
+ raise ValueError(f"Conversation not found: {conversation_id}")
300
+ conversation.messages.append(message)
301
+ await self.save(conversation, scope)
302
+
303
+ async def get_messages(
304
+ self,
305
+ conversation_id: UUID,
306
+ scope: Scope = Scope.PROJECT,
307
+ limit: Optional[int] = None,
308
+ before: Optional[datetime] = None,
309
+ ) -> list[ConversationMessage]:
310
+ conversation = await self.get(conversation_id, scope)
311
+ if conversation is None:
312
+ return []
313
+
314
+ messages = conversation.messages
315
+ if before:
316
+ messages = [m for m in messages if m.timestamp < before]
317
+ if limit:
318
+ messages = messages[-limit:]
319
+ return messages
320
+
321
+
322
+ class FileTaskStore(TaskStore):
323
+ """
324
+ File-based task store.
325
+
326
+ Stores task lists in JSON files:
327
+ - {base_path}/tasks/{task_list_id}.json
328
+ """
329
+
330
+ def __init__(self, project_dir: Optional[Path] = None):
331
+ self._project_dir = project_dir
332
+
333
+ def _get_tasks_path(self, scope: Scope) -> Path:
334
+ return _get_base_path(scope, self._project_dir) / "tasks"
335
+
336
+ def _get_task_list_path(self, task_list_id: UUID, scope: Scope) -> Path:
337
+ return self._get_tasks_path(scope) / f"{task_list_id}.json"
338
+
339
+ def _serialize_task_list(self, task_list: TaskList) -> dict:
340
+ return {
341
+ "id": str(task_list.id),
342
+ "name": task_list.name,
343
+ "tasks": [
344
+ {
345
+ "id": str(t.id),
346
+ "name": t.name,
347
+ "description": t.description,
348
+ "state": t.state.value,
349
+ "parent_id": str(t.parent_id) if t.parent_id else None,
350
+ "created_at": t.created_at.isoformat(),
351
+ "updated_at": t.updated_at.isoformat(),
352
+ "metadata": t.metadata,
353
+ }
354
+ for t in task_list.tasks
355
+ ],
356
+ "created_at": task_list.created_at.isoformat(),
357
+ "updated_at": task_list.updated_at.isoformat(),
358
+ "conversation_id": str(task_list.conversation_id) if task_list.conversation_id else None,
359
+ "run_id": str(task_list.run_id) if task_list.run_id else None,
360
+ }
361
+
362
+ def _deserialize_task_list(self, data: dict) -> TaskList:
363
+ return TaskList(
364
+ id=_parse_uuid(data["id"]),
365
+ name=data["name"],
366
+ tasks=[
367
+ Task(
368
+ id=_parse_uuid(t["id"]),
369
+ name=t["name"],
370
+ description=t.get("description", ""),
371
+ state=TaskState(t["state"]),
372
+ parent_id=_parse_uuid(t["parent_id"]) if t.get("parent_id") else None,
373
+ created_at=_parse_datetime(t["created_at"]),
374
+ updated_at=_parse_datetime(t["updated_at"]),
375
+ metadata=t.get("metadata", {}),
376
+ )
377
+ for t in data.get("tasks", [])
378
+ ],
379
+ created_at=_parse_datetime(data["created_at"]),
380
+ updated_at=_parse_datetime(data["updated_at"]),
381
+ conversation_id=_parse_uuid(data["conversation_id"]) if data.get("conversation_id") else None,
382
+ run_id=_parse_uuid(data["run_id"]) if data.get("run_id") else None,
383
+ )
384
+
385
+ async def save(self, task_list: TaskList, scope: Scope = Scope.PROJECT) -> None:
386
+ path = self._get_task_list_path(task_list.id, scope)
387
+ _ensure_dir(path.parent)
388
+ task_list.updated_at = datetime.utcnow()
389
+ with open(path, "w") as f:
390
+ f.write(_json_dumps(self._serialize_task_list(task_list)))
391
+
392
+ async def get(self, task_list_id: UUID, scope: Scope = Scope.PROJECT) -> Optional[TaskList]:
393
+ path = self._get_task_list_path(task_list_id, scope)
394
+ if not path.exists():
395
+ return None
396
+ try:
397
+ with open(path, "r") as f:
398
+ data = json.load(f)
399
+ return self._deserialize_task_list(data)
400
+ except (json.JSONDecodeError, IOError):
401
+ return None
402
+
403
+ async def delete(self, task_list_id: UUID, scope: Scope = Scope.PROJECT) -> bool:
404
+ path = self._get_task_list_path(task_list_id, scope)
405
+ if path.exists():
406
+ path.unlink()
407
+ return True
408
+ return False
409
+
410
+ async def get_by_conversation(
411
+ self,
412
+ conversation_id: UUID,
413
+ scope: Scope = Scope.PROJECT,
414
+ ) -> Optional[TaskList]:
415
+ tasks_path = self._get_tasks_path(scope)
416
+ if not tasks_path.exists():
417
+ return None
418
+
419
+ for file in tasks_path.glob("*.json"):
420
+ try:
421
+ with open(file, "r") as f:
422
+ data = json.load(f)
423
+ if data.get("conversation_id") == str(conversation_id):
424
+ return self._deserialize_task_list(data)
425
+ except (json.JSONDecodeError, IOError):
426
+ continue
427
+ return None
428
+
429
+ async def update_task(
430
+ self,
431
+ task_list_id: UUID,
432
+ task_id: UUID,
433
+ state: Optional[TaskState] = None,
434
+ name: Optional[str] = None,
435
+ description: Optional[str] = None,
436
+ scope: Scope = Scope.PROJECT,
437
+ ) -> None:
438
+ task_list = await self.get(task_list_id, scope)
439
+ if task_list is None:
440
+ raise ValueError(f"Task list not found: {task_list_id}")
441
+
442
+ for task in task_list.tasks:
443
+ if task.id == task_id:
444
+ if state is not None:
445
+ task.state = state
446
+ if name is not None:
447
+ task.name = name
448
+ if description is not None:
449
+ task.description = description
450
+ task.updated_at = datetime.utcnow()
451
+ break
452
+ else:
453
+ raise ValueError(f"Task not found: {task_id}")
454
+
455
+ await self.save(task_list, scope)
456
+
457
+
458
+
459
+ class FilePreferencesStore(PreferencesStore):
460
+ """
461
+ File-based preferences store.
462
+
463
+ Stores preferences in a single JSON file:
464
+ - {base_path}/preferences.json
465
+ """
466
+
467
+ def __init__(self, project_dir: Optional[Path] = None):
468
+ self._project_dir = project_dir
469
+
470
+ def _get_preferences_path(self, scope: Scope) -> Path:
471
+ return _get_base_path(scope, self._project_dir) / "preferences.json"
472
+
473
+ async def _load_preferences(self, scope: Scope) -> dict:
474
+ path = self._get_preferences_path(scope)
475
+ if not path.exists():
476
+ return {}
477
+ try:
478
+ with open(path, "r") as f:
479
+ return json.load(f)
480
+ except (json.JSONDecodeError, IOError):
481
+ return {}
482
+
483
+ async def _save_preferences(self, preferences: dict, scope: Scope) -> None:
484
+ path = self._get_preferences_path(scope)
485
+ _ensure_dir(path.parent)
486
+ with open(path, "w") as f:
487
+ f.write(_json_dumps(preferences))
488
+
489
+ async def get(self, key: str, scope: Scope = Scope.GLOBAL) -> Optional[Any]:
490
+ preferences = await self._load_preferences(scope)
491
+ return preferences.get(key)
492
+
493
+ async def set(self, key: str, value: Any, scope: Scope = Scope.GLOBAL) -> None:
494
+ preferences = await self._load_preferences(scope)
495
+ preferences[key] = value
496
+ await self._save_preferences(preferences, scope)
497
+
498
+ async def delete(self, key: str, scope: Scope = Scope.GLOBAL) -> bool:
499
+ preferences = await self._load_preferences(scope)
500
+ if key in preferences:
501
+ del preferences[key]
502
+ await self._save_preferences(preferences, scope)
503
+ return True
504
+ return False
505
+
506
+ async def get_all(self, scope: Scope = Scope.GLOBAL) -> dict[str, Any]:
507
+ return await self._load_preferences(scope)
@@ -0,0 +1,266 @@
1
+ """
2
+ Persistence manager for coordinating storage backends.
3
+
4
+ The PersistenceManager provides a unified interface for accessing
5
+ all persistence stores with configurable backends.
6
+
7
+ For Django integration, you can either:
8
+ 1. Pass pre-instantiated store instances
9
+ 2. Pass store classes with appropriate kwargs
10
+ 3. Use factory functions for request-scoped stores
11
+ """
12
+
13
+ from dataclasses import dataclass, field
14
+ from pathlib import Path
15
+ from typing import Any, Callable, Optional, Type, Union
16
+
17
+ from agent_runtime.persistence.base import (
18
+ MemoryStore,
19
+ ConversationStore,
20
+ TaskStore,
21
+ PreferencesStore,
22
+ Scope,
23
+ )
24
+ from agent_runtime.persistence.file import (
25
+ FileMemoryStore,
26
+ FileConversationStore,
27
+ FileTaskStore,
28
+ FilePreferencesStore,
29
+ )
30
+
31
+
32
+ # Type aliases for store factories
33
+ MemoryStoreFactory = Callable[[], MemoryStore]
34
+ ConversationStoreFactory = Callable[[], ConversationStore]
35
+ TaskStoreFactory = Callable[[], TaskStore]
36
+ PreferencesStoreFactory = Callable[[], PreferencesStore]
37
+
38
+
39
+ @dataclass
40
+ class PersistenceConfig:
41
+ """
42
+ Configuration for persistence backends.
43
+
44
+ Each store can be configured as:
45
+ - A class (will be instantiated with store_kwargs)
46
+ - A pre-instantiated store instance
47
+ - A factory function that returns a store instance
48
+
49
+ Example for Django:
50
+ from myapp.stores import DjangoMemoryStore, DjangoConversationStore
51
+
52
+ # Option 1: Pass classes with kwargs
53
+ config = PersistenceConfig(
54
+ memory_store_class=DjangoMemoryStore,
55
+ memory_store_kwargs={"user": request.user},
56
+ )
57
+
58
+ # Option 2: Pass pre-instantiated stores
59
+ config = PersistenceConfig(
60
+ memory_store=DjangoMemoryStore(user=request.user),
61
+ conversation_store=DjangoConversationStore(user=request.user),
62
+ )
63
+
64
+ # Option 3: Pass factory functions
65
+ config = PersistenceConfig(
66
+ memory_store_factory=lambda: DjangoMemoryStore(user=get_current_user()),
67
+ )
68
+ """
69
+
70
+ # Backend classes (can be swapped for custom implementations)
71
+ memory_store_class: Type[MemoryStore] = FileMemoryStore
72
+ conversation_store_class: Type[ConversationStore] = FileConversationStore
73
+ task_store_class: Type[TaskStore] = FileTaskStore
74
+ preferences_store_class: Type[PreferencesStore] = FilePreferencesStore
75
+
76
+ # Pre-instantiated store instances (takes precedence over classes)
77
+ memory_store: Optional[MemoryStore] = None
78
+ conversation_store: Optional[ConversationStore] = None
79
+ task_store: Optional[TaskStore] = None
80
+ preferences_store: Optional[PreferencesStore] = None
81
+
82
+ # Factory functions (takes precedence over classes, but not instances)
83
+ memory_store_factory: Optional[MemoryStoreFactory] = None
84
+ conversation_store_factory: Optional[ConversationStoreFactory] = None
85
+ task_store_factory: Optional[TaskStoreFactory] = None
86
+ preferences_store_factory: Optional[PreferencesStoreFactory] = None
87
+
88
+ # Kwargs passed to store class constructors (only used with classes)
89
+ memory_store_kwargs: dict = field(default_factory=dict)
90
+ conversation_store_kwargs: dict = field(default_factory=dict)
91
+ task_store_kwargs: dict = field(default_factory=dict)
92
+ preferences_store_kwargs: dict = field(default_factory=dict)
93
+
94
+ # Project directory (convenience for file-based stores)
95
+ # Only used if store_kwargs doesn't already have project_dir
96
+ project_dir: Optional[Path] = None
97
+
98
+
99
+ class PersistenceManager:
100
+ """
101
+ Unified manager for all persistence stores.
102
+
103
+ Provides access to memory, conversations, tasks, and preferences
104
+ with pluggable backends.
105
+
106
+ Example:
107
+ # Use default file-based storage
108
+ manager = PersistenceManager()
109
+
110
+ # Store global memory
111
+ await manager.memory.set("user_name", "Alice", scope=Scope.GLOBAL)
112
+
113
+ # Store project-specific memory
114
+ await manager.memory.set("project_type", "python", scope=Scope.PROJECT)
115
+
116
+ # Save a conversation
117
+ await manager.conversations.save(conversation)
118
+
119
+ # Use custom backends (Django example)
120
+ config = PersistenceConfig(
121
+ memory_store=DjangoMemoryStore(user=request.user),
122
+ conversation_store=DjangoConversationStore(user=request.user),
123
+ )
124
+ manager = PersistenceManager(config)
125
+ """
126
+
127
+ def __init__(self, config: Optional[PersistenceConfig] = None):
128
+ self._config = config or PersistenceConfig()
129
+ self._memory: Optional[MemoryStore] = None
130
+ self._conversations: Optional[ConversationStore] = None
131
+ self._tasks: Optional[TaskStore] = None
132
+ self._preferences: Optional[PreferencesStore] = None
133
+
134
+ def _build_kwargs(self, store_kwargs: dict) -> dict:
135
+ """Build kwargs for store instantiation."""
136
+ kwargs = {}
137
+ # Only add project_dir if not already in store_kwargs
138
+ if self._config.project_dir and "project_dir" not in store_kwargs:
139
+ kwargs["project_dir"] = self._config.project_dir
140
+ kwargs.update(store_kwargs)
141
+ return kwargs
142
+
143
+ @property
144
+ def memory(self) -> MemoryStore:
145
+ """Get the memory store."""
146
+ if self._memory is None:
147
+ # Priority: instance > factory > class
148
+ if self._config.memory_store is not None:
149
+ self._memory = self._config.memory_store
150
+ elif self._config.memory_store_factory is not None:
151
+ self._memory = self._config.memory_store_factory()
152
+ else:
153
+ kwargs = self._build_kwargs(self._config.memory_store_kwargs)
154
+ self._memory = self._config.memory_store_class(**kwargs)
155
+ return self._memory
156
+
157
+ @property
158
+ def conversations(self) -> ConversationStore:
159
+ """Get the conversation store."""
160
+ if self._conversations is None:
161
+ if self._config.conversation_store is not None:
162
+ self._conversations = self._config.conversation_store
163
+ elif self._config.conversation_store_factory is not None:
164
+ self._conversations = self._config.conversation_store_factory()
165
+ else:
166
+ kwargs = self._build_kwargs(self._config.conversation_store_kwargs)
167
+ self._conversations = self._config.conversation_store_class(**kwargs)
168
+ return self._conversations
169
+
170
+ @property
171
+ def tasks(self) -> TaskStore:
172
+ """Get the task store."""
173
+ if self._tasks is None:
174
+ if self._config.task_store is not None:
175
+ self._tasks = self._config.task_store
176
+ elif self._config.task_store_factory is not None:
177
+ self._tasks = self._config.task_store_factory()
178
+ else:
179
+ kwargs = self._build_kwargs(self._config.task_store_kwargs)
180
+ self._tasks = self._config.task_store_class(**kwargs)
181
+ return self._tasks
182
+
183
+ @property
184
+ def preferences(self) -> PreferencesStore:
185
+ """Get the preferences store."""
186
+ if self._preferences is None:
187
+ if self._config.preferences_store is not None:
188
+ self._preferences = self._config.preferences_store
189
+ elif self._config.preferences_store_factory is not None:
190
+ self._preferences = self._config.preferences_store_factory()
191
+ else:
192
+ kwargs = self._build_kwargs(self._config.preferences_store_kwargs)
193
+ self._preferences = self._config.preferences_store_class(**kwargs)
194
+ return self._preferences
195
+
196
+ async def close(self) -> None:
197
+ """Close all stores."""
198
+ if self._memory:
199
+ await self._memory.close()
200
+ if self._conversations:
201
+ await self._conversations.close()
202
+ if self._tasks:
203
+ await self._tasks.close()
204
+ if self._preferences:
205
+ await self._preferences.close()
206
+
207
+
208
+ # Global manager instance
209
+ _manager: Optional[PersistenceManager] = None
210
+ _config: Optional[PersistenceConfig] = None
211
+
212
+
213
+ def configure_persistence(
214
+ memory_store_class: Optional[Type[MemoryStore]] = None,
215
+ conversation_store_class: Optional[Type[ConversationStore]] = None,
216
+ task_store_class: Optional[Type[TaskStore]] = None,
217
+ preferences_store_class: Optional[Type[PreferencesStore]] = None,
218
+ project_dir: Optional[Path] = None,
219
+ **kwargs,
220
+ ) -> PersistenceConfig:
221
+ """
222
+ Configure the global persistence manager.
223
+
224
+ Args:
225
+ memory_store_class: Custom memory store implementation
226
+ conversation_store_class: Custom conversation store implementation
227
+ task_store_class: Custom task store implementation
228
+ preferences_store_class: Custom preferences store implementation
229
+ project_dir: Project directory for PROJECT scope
230
+ **kwargs: Additional store-specific configuration
231
+
232
+ Returns:
233
+ The configured PersistenceConfig
234
+ """
235
+ global _config, _manager
236
+
237
+ config = PersistenceConfig(project_dir=project_dir)
238
+
239
+ if memory_store_class:
240
+ config.memory_store_class = memory_store_class
241
+ if conversation_store_class:
242
+ config.conversation_store_class = conversation_store_class
243
+ if task_store_class:
244
+ config.task_store_class = task_store_class
245
+ if preferences_store_class:
246
+ config.preferences_store_class = preferences_store_class
247
+
248
+ _config = config
249
+ _manager = None # Reset manager to use new config
250
+
251
+ return config
252
+
253
+
254
+ def get_persistence_manager() -> PersistenceManager:
255
+ """
256
+ Get the global persistence manager.
257
+
258
+ Creates a new manager with default config if not configured.
259
+ """
260
+ global _manager
261
+
262
+ if _manager is None:
263
+ _manager = PersistenceManager(_config)
264
+
265
+ return _manager
266
+
@@ -37,7 +37,7 @@ class LangfuseTraceSink(TraceSink):
37
37
  if Langfuse is None:
38
38
  raise ImportError(
39
39
  "langfuse package is required for LangfuseTraceSink. "
40
- "Install with: pip install agent_runtime[langfuse]"
40
+ "Install with: pip install agent-runtime-core[langfuse]"
41
41
  )
42
42
 
43
43
  self._client = Langfuse(
@@ -1,9 +1,9 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: agent-runtime-core
3
- Version: 0.1.4
3
+ Version: 0.2.1
4
4
  Summary: Framework-agnostic Python library for executing AI agents with consistent patterns
5
- Project-URL: Homepage, https://github.com/colstrom/agent_runtime
6
- Project-URL: Repository, https://github.com/colstrom/agent_runtime
5
+ Project-URL: Homepage, https://github.com/colstrom/agent_runtime_core
6
+ Project-URL: Repository, https://github.com/colstrom/agent_runtime_core
7
7
  Author: Chris Olstrom
8
8
  License-Expression: MIT
9
9
  License-File: LICENSE
@@ -1,4 +1,4 @@
1
- agent_runtime/__init__.py,sha256=zE6kt74R7dmm7Oe9o7B4CsSwoOm6D2H4LHX_EGj_pHA,2430
1
+ agent_runtime/__init__.py,sha256=d-9hrmPS2JmaaDdZRfO5_B1HiJxLU_hMrXY-COXOxxs,3591
2
2
  agent_runtime/config.py,sha256=ZRjpILjsjeh_kl7873DtV2g_zaTrfdkb3NgdQ6ndb5Y,4981
3
3
  agent_runtime/interfaces.py,sha256=-VGZJHUkyF8kdO-BDkURyc-sLbObIHErIFw1Hzn3n14,10434
4
4
  agent_runtime/registry.py,sha256=sa0speDFxFCZlXoCge8cPNqWYUeWHyazs6tBer5Jg1w,1471
@@ -10,9 +10,13 @@ agent_runtime/events/memory.py,sha256=7qseR6RtdaP833FxEHwyPw5TC7l4brJHr8uEx0mLc1
10
10
  agent_runtime/events/redis.py,sha256=Z6WEvp_6jcIPi4ZgkGk5J61qxgGqllwk7jqJM4jcTXk,5869
11
11
  agent_runtime/events/sqlite.py,sha256=EiX1BMOqeS7BelmD8A6cvhz3fE4w7vJ2Wg4pFu1V2u0,5092
12
12
  agent_runtime/llm/__init__.py,sha256=JEk1Q2H6U9_Uid48YVm1wYR1W7po0vtjfjf2TTmQe_A,2431
13
- agent_runtime/llm/anthropic.py,sha256=R5KIcHAvHwoibkI0bXxg0RXlwd1qRGcdlGEEEoApVCM,7491
14
- agent_runtime/llm/litellm_client.py,sha256=M_-dAJVMRfWeNJlB0HXpFykbiIDRGwq1uMnamO3XUr8,5178
15
- agent_runtime/llm/openai.py,sha256=eL6_lhsUvwlE24TfDnzmlF285M0tExjb00rJV43oz28,6837
13
+ agent_runtime/llm/anthropic.py,sha256=ho3psMYAARpXyzqejgA1dx6Nk8io0TwCKb0mp_wsGyM,7496
14
+ agent_runtime/llm/litellm_client.py,sha256=Pic3N4CHVoqzdHUbKlizBcuPP0xCKoeY6U2ZjsZIgWg,5183
15
+ agent_runtime/llm/openai.py,sha256=cRk4WBpiVknqsy_cgPAGdC8Zj250nasqo1dNQN0uxlw,6842
16
+ agent_runtime/persistence/__init__.py,sha256=YynbxYtCtaSurQS9Ikenj9n_4xSEh0T9K0kOpg3IW04,2009
17
+ agent_runtime/persistence/base.py,sha256=7p9HYWYY4pjmDvQgLh11Fie_XmMnkimkCG7tUQt0zUQ,9444
18
+ agent_runtime/persistence/file.py,sha256=uMNSNfXvHPMJzl3QTrU-CPt1cLCH8J9rkaThbq7kLf0,17780
19
+ agent_runtime/persistence/manager.py,sha256=PP-imx6ygIlUSID8rMwR9CTeD02HjZ3mdqczgZvF1es,9682
16
20
  agent_runtime/queue/__init__.py,sha256=78k29iEl8brp71LrOnmTHhQzPMHkzGre-Xqdl1NlNr0,1502
17
21
  agent_runtime/queue/base.py,sha256=QW1eWbwBX_tmVD8yJobFJtlxLd_RtUWHTuXGessuxy8,3959
18
22
  agent_runtime/queue/memory.py,sha256=n7kiE0Fw_BFUdzMyoO1QPO0ATzz8zBYaMQex7GdceZw,5411
@@ -24,9 +28,9 @@ agent_runtime/state/memory.py,sha256=xOnlqM3ArXDKAdPx3PxdS9IGgJDSM-EKp_S1Hsit180
24
28
  agent_runtime/state/redis.py,sha256=-lPi_2xKm7Bc4DVMJfSEAF7wJHctLV3ZMM9AYBeQKZU,3425
25
29
  agent_runtime/state/sqlite.py,sha256=NwuiTBXELb2tyOoH91MZqRJaCk9h8PskyY2VUc5EMr0,4868
26
30
  agent_runtime/tracing/__init__.py,sha256=m4WzfgJpnV5XCCoMpBYZdJU_JTkAdhEhl7M7tpf62RY,1246
27
- agent_runtime/tracing/langfuse.py,sha256=uThF0P6f1VJ1l1b7UuiFQ-oHZ-tCa9MbbHvTqkSuQ2A,3650
31
+ agent_runtime/tracing/langfuse.py,sha256=Z-2eEUHlxCC82JtXOAaoi-1zI6tQwEOWdpJgfCXcZH0,3655
28
32
  agent_runtime/tracing/noop.py,sha256=MOm5eTrnf3d4WhiWrwVU5Kd3GmJ1903V0U7U3Qwho7U,746
29
- agent_runtime_core-0.1.4.dist-info/METADATA,sha256=uUCm7NRcZiHN7PLOMKhBZMEa-Sf-SJi87ON2HBDxIH4,12478
30
- agent_runtime_core-0.1.4.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
31
- agent_runtime_core-0.1.4.dist-info/licenses/LICENSE,sha256=PcOO8aiOZ4H2MWYeKIis3o6xTCT1hNkDyCxHZhh1NeM,1070
32
- agent_runtime_core-0.1.4.dist-info/RECORD,,
33
+ agent_runtime_core-0.2.1.dist-info/METADATA,sha256=_YZWAHr9zovJKJ0d0Xhk04lPfU9PeaN9gmUFFvaQM58,12488
34
+ agent_runtime_core-0.2.1.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
35
+ agent_runtime_core-0.2.1.dist-info/licenses/LICENSE,sha256=PcOO8aiOZ4H2MWYeKIis3o6xTCT1hNkDyCxHZhh1NeM,1070
36
+ agent_runtime_core-0.2.1.dist-info/RECORD,,