agent-runtime-core 0.2.1__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.
- {agent_runtime → agent_runtime_core}/__init__.py +8 -8
- {agent_runtime → agent_runtime_core}/config.py +1 -1
- {agent_runtime → agent_runtime_core}/events/__init__.py +5 -5
- {agent_runtime → agent_runtime_core}/events/memory.py +1 -1
- {agent_runtime → agent_runtime_core}/events/redis.py +1 -1
- {agent_runtime → agent_runtime_core}/events/sqlite.py +1 -1
- {agent_runtime → agent_runtime_core}/llm/__init__.py +6 -6
- {agent_runtime → agent_runtime_core}/llm/anthropic.py +4 -4
- {agent_runtime → agent_runtime_core}/llm/litellm_client.py +2 -2
- {agent_runtime → agent_runtime_core}/llm/openai.py +4 -4
- {agent_runtime → agent_runtime_core}/persistence/__init__.py +48 -12
- agent_runtime_core/persistence/base.py +737 -0
- {agent_runtime → agent_runtime_core}/persistence/file.py +1 -1
- {agent_runtime → agent_runtime_core}/persistence/manager.py +122 -14
- {agent_runtime → agent_runtime_core}/queue/__init__.py +5 -5
- {agent_runtime → agent_runtime_core}/queue/memory.py +1 -1
- {agent_runtime → agent_runtime_core}/queue/redis.py +1 -1
- {agent_runtime → agent_runtime_core}/queue/sqlite.py +1 -1
- {agent_runtime → agent_runtime_core}/registry.py +1 -1
- {agent_runtime → agent_runtime_core}/runner.py +6 -6
- {agent_runtime → agent_runtime_core}/state/__init__.py +5 -5
- {agent_runtime → agent_runtime_core}/state/memory.py +1 -1
- {agent_runtime → agent_runtime_core}/state/redis.py +1 -1
- {agent_runtime → agent_runtime_core}/state/sqlite.py +1 -1
- {agent_runtime → agent_runtime_core}/testing.py +1 -1
- {agent_runtime → agent_runtime_core}/tracing/__init__.py +4 -4
- {agent_runtime → agent_runtime_core}/tracing/langfuse.py +1 -1
- {agent_runtime → agent_runtime_core}/tracing/noop.py +1 -1
- {agent_runtime_core-0.2.1.dist-info → agent_runtime_core-0.4.0.dist-info}/METADATA +352 -42
- agent_runtime_core-0.4.0.dist-info/RECORD +36 -0
- agent_runtime/persistence/base.py +0 -332
- agent_runtime_core-0.2.1.dist-info/RECORD +0 -36
- {agent_runtime → agent_runtime_core}/events/base.py +0 -0
- {agent_runtime → agent_runtime_core}/interfaces.py +0 -0
- {agent_runtime → agent_runtime_core}/queue/base.py +0 -0
- {agent_runtime → agent_runtime_core}/state/base.py +0 -0
- {agent_runtime_core-0.2.1.dist-info → agent_runtime_core-0.4.0.dist-info}/WHEEL +0 -0
- {agent_runtime_core-0.2.1.dist-info → agent_runtime_core-0.4.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,737 @@
|
|
|
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: {prompt_tokens, completion_tokens, total_tokens}
|
|
101
|
+
metadata: dict = field(default_factory=dict)
|
|
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
|
+
|
|
107
|
+
|
|
108
|
+
@dataclass
|
|
109
|
+
class Conversation:
|
|
110
|
+
"""A complete conversation with all state."""
|
|
111
|
+
|
|
112
|
+
id: UUID
|
|
113
|
+
title: Optional[str] = None
|
|
114
|
+
messages: list[ConversationMessage] = field(default_factory=list)
|
|
115
|
+
|
|
116
|
+
# Metadata
|
|
117
|
+
created_at: datetime = field(default_factory=datetime.utcnow)
|
|
118
|
+
updated_at: datetime = field(default_factory=datetime.utcnow)
|
|
119
|
+
metadata: dict = field(default_factory=dict)
|
|
120
|
+
|
|
121
|
+
# Associated agent
|
|
122
|
+
agent_key: Optional[str] = None
|
|
123
|
+
|
|
124
|
+
# Summary for long conversations
|
|
125
|
+
summary: Optional[str] = None
|
|
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
|
+
|
|
131
|
+
|
|
132
|
+
@dataclass
|
|
133
|
+
class Task:
|
|
134
|
+
"""A task in a task list."""
|
|
135
|
+
|
|
136
|
+
id: UUID
|
|
137
|
+
name: str
|
|
138
|
+
description: str = ""
|
|
139
|
+
state: TaskState = TaskState.NOT_STARTED
|
|
140
|
+
parent_id: Optional[UUID] = None
|
|
141
|
+
created_at: datetime = field(default_factory=datetime.utcnow)
|
|
142
|
+
updated_at: datetime = field(default_factory=datetime.utcnow)
|
|
143
|
+
metadata: dict = field(default_factory=dict)
|
|
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
|
+
|
|
159
|
+
|
|
160
|
+
@dataclass
|
|
161
|
+
class TaskList:
|
|
162
|
+
"""A list of tasks."""
|
|
163
|
+
|
|
164
|
+
id: UUID
|
|
165
|
+
name: str
|
|
166
|
+
tasks: list[Task] = field(default_factory=list)
|
|
167
|
+
created_at: datetime = field(default_factory=datetime.utcnow)
|
|
168
|
+
updated_at: datetime = field(default_factory=datetime.utcnow)
|
|
169
|
+
|
|
170
|
+
# Associated conversation/run
|
|
171
|
+
conversation_id: Optional[UUID] = None
|
|
172
|
+
run_id: Optional[UUID] = None
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
class MemoryStore(ABC):
|
|
176
|
+
"""
|
|
177
|
+
Abstract interface for key-value memory storage.
|
|
178
|
+
|
|
179
|
+
Memory stores handle persistent key-value data that agents can
|
|
180
|
+
use to remember information across sessions.
|
|
181
|
+
"""
|
|
182
|
+
|
|
183
|
+
@abstractmethod
|
|
184
|
+
async def get(self, key: str, scope: Scope = Scope.PROJECT) -> Optional[Any]:
|
|
185
|
+
"""Get a value by key."""
|
|
186
|
+
...
|
|
187
|
+
|
|
188
|
+
@abstractmethod
|
|
189
|
+
async def set(self, key: str, value: Any, scope: Scope = Scope.PROJECT) -> None:
|
|
190
|
+
"""Set a value by key."""
|
|
191
|
+
...
|
|
192
|
+
|
|
193
|
+
@abstractmethod
|
|
194
|
+
async def delete(self, key: str, scope: Scope = Scope.PROJECT) -> bool:
|
|
195
|
+
"""Delete a key. Returns True if key existed."""
|
|
196
|
+
...
|
|
197
|
+
|
|
198
|
+
@abstractmethod
|
|
199
|
+
async def list_keys(self, scope: Scope = Scope.PROJECT, prefix: Optional[str] = None) -> list[str]:
|
|
200
|
+
"""List all keys, optionally filtered by prefix."""
|
|
201
|
+
...
|
|
202
|
+
|
|
203
|
+
@abstractmethod
|
|
204
|
+
async def clear(self, scope: Scope = Scope.PROJECT) -> None:
|
|
205
|
+
"""Clear all keys in the given scope."""
|
|
206
|
+
...
|
|
207
|
+
|
|
208
|
+
async def close(self) -> None:
|
|
209
|
+
"""Close any connections. Override if needed."""
|
|
210
|
+
pass
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
class ConversationStore(ABC):
|
|
214
|
+
"""
|
|
215
|
+
Abstract interface for conversation history storage.
|
|
216
|
+
|
|
217
|
+
Conversation stores handle full conversation state including
|
|
218
|
+
messages, tool calls, and metadata.
|
|
219
|
+
"""
|
|
220
|
+
|
|
221
|
+
@abstractmethod
|
|
222
|
+
async def save(self, conversation: Conversation, scope: Scope = Scope.PROJECT) -> None:
|
|
223
|
+
"""Save or update a conversation."""
|
|
224
|
+
...
|
|
225
|
+
|
|
226
|
+
@abstractmethod
|
|
227
|
+
async def get(self, conversation_id: UUID, scope: Scope = Scope.PROJECT) -> Optional[Conversation]:
|
|
228
|
+
"""Get a conversation by ID."""
|
|
229
|
+
...
|
|
230
|
+
|
|
231
|
+
@abstractmethod
|
|
232
|
+
async def delete(self, conversation_id: UUID, scope: Scope = Scope.PROJECT) -> bool:
|
|
233
|
+
"""Delete a conversation. Returns True if it existed."""
|
|
234
|
+
...
|
|
235
|
+
|
|
236
|
+
@abstractmethod
|
|
237
|
+
async def list_conversations(
|
|
238
|
+
self,
|
|
239
|
+
scope: Scope = Scope.PROJECT,
|
|
240
|
+
limit: int = 100,
|
|
241
|
+
offset: int = 0,
|
|
242
|
+
agent_key: Optional[str] = None,
|
|
243
|
+
) -> list[Conversation]:
|
|
244
|
+
"""List conversations, optionally filtered by agent."""
|
|
245
|
+
...
|
|
246
|
+
|
|
247
|
+
@abstractmethod
|
|
248
|
+
async def add_message(
|
|
249
|
+
self,
|
|
250
|
+
conversation_id: UUID,
|
|
251
|
+
message: ConversationMessage,
|
|
252
|
+
scope: Scope = Scope.PROJECT,
|
|
253
|
+
) -> None:
|
|
254
|
+
"""Add a message to an existing conversation."""
|
|
255
|
+
...
|
|
256
|
+
|
|
257
|
+
@abstractmethod
|
|
258
|
+
async def get_messages(
|
|
259
|
+
self,
|
|
260
|
+
conversation_id: UUID,
|
|
261
|
+
scope: Scope = Scope.PROJECT,
|
|
262
|
+
limit: Optional[int] = None,
|
|
263
|
+
before: Optional[datetime] = None,
|
|
264
|
+
) -> list[ConversationMessage]:
|
|
265
|
+
"""Get messages from a conversation."""
|
|
266
|
+
...
|
|
267
|
+
|
|
268
|
+
async def close(self) -> None:
|
|
269
|
+
"""Close any connections. Override if needed."""
|
|
270
|
+
pass
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
class TaskStore(ABC):
|
|
274
|
+
"""
|
|
275
|
+
Abstract interface for task list storage.
|
|
276
|
+
|
|
277
|
+
Task stores handle task lists and their state for tracking
|
|
278
|
+
agent progress on complex work.
|
|
279
|
+
"""
|
|
280
|
+
|
|
281
|
+
@abstractmethod
|
|
282
|
+
async def save(self, task_list: TaskList, scope: Scope = Scope.PROJECT) -> None:
|
|
283
|
+
"""Save or update a task list."""
|
|
284
|
+
...
|
|
285
|
+
|
|
286
|
+
@abstractmethod
|
|
287
|
+
async def get(self, task_list_id: UUID, scope: Scope = Scope.PROJECT) -> Optional[TaskList]:
|
|
288
|
+
"""Get a task list by ID."""
|
|
289
|
+
...
|
|
290
|
+
|
|
291
|
+
@abstractmethod
|
|
292
|
+
async def delete(self, task_list_id: UUID, scope: Scope = Scope.PROJECT) -> bool:
|
|
293
|
+
"""Delete a task list. Returns True if it existed."""
|
|
294
|
+
...
|
|
295
|
+
|
|
296
|
+
@abstractmethod
|
|
297
|
+
async def get_by_conversation(
|
|
298
|
+
self,
|
|
299
|
+
conversation_id: UUID,
|
|
300
|
+
scope: Scope = Scope.PROJECT,
|
|
301
|
+
) -> Optional[TaskList]:
|
|
302
|
+
"""Get the task list associated with a conversation."""
|
|
303
|
+
...
|
|
304
|
+
|
|
305
|
+
@abstractmethod
|
|
306
|
+
async def update_task(
|
|
307
|
+
self,
|
|
308
|
+
task_list_id: UUID,
|
|
309
|
+
task_id: UUID,
|
|
310
|
+
state: Optional[TaskState] = None,
|
|
311
|
+
name: Optional[str] = None,
|
|
312
|
+
description: Optional[str] = None,
|
|
313
|
+
scope: Scope = Scope.PROJECT,
|
|
314
|
+
) -> None:
|
|
315
|
+
"""Update a specific task in a task list."""
|
|
316
|
+
...
|
|
317
|
+
|
|
318
|
+
async def close(self) -> None:
|
|
319
|
+
"""Close any connections. Override if needed."""
|
|
320
|
+
pass
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
class PreferencesStore(ABC):
|
|
324
|
+
"""
|
|
325
|
+
Abstract interface for preferences storage.
|
|
326
|
+
|
|
327
|
+
Preferences stores handle user and agent configuration
|
|
328
|
+
that persists across sessions.
|
|
329
|
+
"""
|
|
330
|
+
|
|
331
|
+
@abstractmethod
|
|
332
|
+
async def get(self, key: str, scope: Scope = Scope.GLOBAL) -> Optional[Any]:
|
|
333
|
+
"""Get a preference value."""
|
|
334
|
+
...
|
|
335
|
+
|
|
336
|
+
@abstractmethod
|
|
337
|
+
async def set(self, key: str, value: Any, scope: Scope = Scope.GLOBAL) -> None:
|
|
338
|
+
"""Set a preference value."""
|
|
339
|
+
...
|
|
340
|
+
|
|
341
|
+
@abstractmethod
|
|
342
|
+
async def delete(self, key: str, scope: Scope = Scope.GLOBAL) -> bool:
|
|
343
|
+
"""Delete a preference. Returns True if it existed."""
|
|
344
|
+
...
|
|
345
|
+
|
|
346
|
+
@abstractmethod
|
|
347
|
+
async def get_all(self, scope: Scope = Scope.GLOBAL) -> dict[str, Any]:
|
|
348
|
+
"""Get all preferences in the given scope."""
|
|
349
|
+
...
|
|
350
|
+
|
|
351
|
+
async def close(self) -> None:
|
|
352
|
+
"""Close any connections. Override if needed."""
|
|
353
|
+
pass
|
|
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
|