agent-runtime-core 0.8.0__py3-none-any.whl → 0.9.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_core/__init__.py +65 -3
- agent_runtime_core/agentic_loop.py +285 -20
- agent_runtime_core/config.py +8 -0
- agent_runtime_core/contexts.py +72 -4
- agent_runtime_core/interfaces.py +29 -11
- agent_runtime_core/llm/anthropic.py +161 -7
- agent_runtime_core/llm/models_config.py +50 -6
- agent_runtime_core/llm/openai.py +51 -2
- agent_runtime_core/multi_agent.py +1419 -17
- agent_runtime_core/persistence/__init__.py +8 -0
- agent_runtime_core/persistence/base.py +318 -1
- agent_runtime_core/persistence/file.py +226 -2
- agent_runtime_core/privacy.py +250 -0
- agent_runtime_core/tool_calling_agent.py +3 -1
- {agent_runtime_core-0.8.0.dist-info → agent_runtime_core-0.9.1.dist-info}/METADATA +2 -1
- {agent_runtime_core-0.8.0.dist-info → agent_runtime_core-0.9.1.dist-info}/RECORD +18 -17
- agent_runtime_core-0.9.1.dist-info/licenses/LICENSE +83 -0
- agent_runtime_core-0.8.0.dist-info/licenses/LICENSE +0 -21
- {agent_runtime_core-0.8.0.dist-info → agent_runtime_core-0.9.1.dist-info}/WHEEL +0 -0
|
@@ -41,6 +41,9 @@ from agent_runtime_core.persistence.base import (
|
|
|
41
41
|
# Optional stores
|
|
42
42
|
KnowledgeStore,
|
|
43
43
|
AuditStore,
|
|
44
|
+
# Shared memory
|
|
45
|
+
SharedMemoryStore,
|
|
46
|
+
MemoryItem,
|
|
44
47
|
# Enums
|
|
45
48
|
Scope,
|
|
46
49
|
TaskState,
|
|
@@ -71,6 +74,7 @@ from agent_runtime_core.persistence.file import (
|
|
|
71
74
|
FileTaskStore,
|
|
72
75
|
FilePreferencesStore,
|
|
73
76
|
FileKnowledgeStore,
|
|
77
|
+
InMemorySharedMemoryStore,
|
|
74
78
|
)
|
|
75
79
|
|
|
76
80
|
from agent_runtime_core.persistence.manager import (
|
|
@@ -89,6 +93,9 @@ __all__ = [
|
|
|
89
93
|
# Abstract interfaces - optional
|
|
90
94
|
"KnowledgeStore",
|
|
91
95
|
"AuditStore",
|
|
96
|
+
# Shared memory
|
|
97
|
+
"SharedMemoryStore",
|
|
98
|
+
"MemoryItem",
|
|
92
99
|
# Enums
|
|
93
100
|
"Scope",
|
|
94
101
|
"TaskState",
|
|
@@ -117,6 +124,7 @@ __all__ = [
|
|
|
117
124
|
"FileTaskStore",
|
|
118
125
|
"FilePreferencesStore",
|
|
119
126
|
"FileKnowledgeStore",
|
|
127
|
+
"InMemorySharedMemoryStore",
|
|
120
128
|
# Manager
|
|
121
129
|
"PersistenceManager",
|
|
122
130
|
"PersistenceConfig",
|
|
@@ -29,7 +29,7 @@ from abc import ABC, abstractmethod
|
|
|
29
29
|
from dataclasses import dataclass, field
|
|
30
30
|
from datetime import datetime
|
|
31
31
|
from enum import Enum
|
|
32
|
-
from typing import Any, Optional, AsyncIterator
|
|
32
|
+
from typing import Any, Optional, AsyncIterator, List, Dict
|
|
33
33
|
from uuid import UUID
|
|
34
34
|
|
|
35
35
|
|
|
@@ -735,3 +735,320 @@ class AuditStore(ABC):
|
|
|
735
735
|
async def close(self) -> None:
|
|
736
736
|
"""Close any connections. Override if needed."""
|
|
737
737
|
pass
|
|
738
|
+
|
|
739
|
+
|
|
740
|
+
# =============================================================================
|
|
741
|
+
# Shared Memory Models and Store
|
|
742
|
+
# =============================================================================
|
|
743
|
+
|
|
744
|
+
|
|
745
|
+
@dataclass
|
|
746
|
+
class MemoryItem:
|
|
747
|
+
"""
|
|
748
|
+
A single memory item with semantic key.
|
|
749
|
+
|
|
750
|
+
Memory items use dot-notation keys for hierarchical organization:
|
|
751
|
+
- user.name → "Chris"
|
|
752
|
+
- user.preferences.theme → "dark"
|
|
753
|
+
- user.preferences.language → "en"
|
|
754
|
+
- project.name → "Agent Libraries"
|
|
755
|
+
- conversation.summary → "Discussed privacy features..."
|
|
756
|
+
|
|
757
|
+
Attributes:
|
|
758
|
+
id: Unique identifier for this memory
|
|
759
|
+
key: Semantic key using dot-notation (e.g., "user.preferences.theme")
|
|
760
|
+
value: The actual value (any JSON-serializable type)
|
|
761
|
+
scope: Memory scope (CONVERSATION, USER, SYSTEM)
|
|
762
|
+
created_at: When this memory was created
|
|
763
|
+
updated_at: When this memory was last updated
|
|
764
|
+
source: What created this memory (e.g., "agent:triage", "user:explicit")
|
|
765
|
+
confidence: How confident the agent is (0.0-1.0)
|
|
766
|
+
metadata: Additional context about this memory
|
|
767
|
+
expires_at: Optional expiration time
|
|
768
|
+
conversation_id: For CONVERSATION scope, the conversation this belongs to
|
|
769
|
+
system_id: For SYSTEM scope, the system this belongs to
|
|
770
|
+
"""
|
|
771
|
+
|
|
772
|
+
id: UUID
|
|
773
|
+
key: str
|
|
774
|
+
value: Any
|
|
775
|
+
scope: str = "conversation" # MemoryScope value as string
|
|
776
|
+
created_at: datetime = field(default_factory=datetime.utcnow)
|
|
777
|
+
updated_at: datetime = field(default_factory=datetime.utcnow)
|
|
778
|
+
source: str = "agent"
|
|
779
|
+
confidence: float = 1.0
|
|
780
|
+
metadata: dict = field(default_factory=dict)
|
|
781
|
+
expires_at: Optional[datetime] = None
|
|
782
|
+
conversation_id: Optional[UUID] = None
|
|
783
|
+
system_id: Optional[str] = None
|
|
784
|
+
|
|
785
|
+
def to_dict(self) -> dict:
|
|
786
|
+
"""Convert to dictionary for serialization."""
|
|
787
|
+
return {
|
|
788
|
+
"id": str(self.id),
|
|
789
|
+
"key": self.key,
|
|
790
|
+
"value": self.value,
|
|
791
|
+
"scope": self.scope,
|
|
792
|
+
"created_at": self.created_at.isoformat() if self.created_at else None,
|
|
793
|
+
"updated_at": self.updated_at.isoformat() if self.updated_at else None,
|
|
794
|
+
"source": self.source,
|
|
795
|
+
"confidence": self.confidence,
|
|
796
|
+
"metadata": self.metadata,
|
|
797
|
+
"expires_at": self.expires_at.isoformat() if self.expires_at else None,
|
|
798
|
+
"conversation_id": str(self.conversation_id) if self.conversation_id else None,
|
|
799
|
+
"system_id": self.system_id,
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
@classmethod
|
|
803
|
+
def from_dict(cls, data: dict) -> "MemoryItem":
|
|
804
|
+
"""Create from dictionary."""
|
|
805
|
+
return cls(
|
|
806
|
+
id=UUID(data["id"]) if isinstance(data.get("id"), str) else data.get("id"),
|
|
807
|
+
key=data["key"],
|
|
808
|
+
value=data["value"],
|
|
809
|
+
scope=data.get("scope", "conversation"),
|
|
810
|
+
created_at=datetime.fromisoformat(data["created_at"]) if data.get("created_at") else datetime.utcnow(),
|
|
811
|
+
updated_at=datetime.fromisoformat(data["updated_at"]) if data.get("updated_at") else datetime.utcnow(),
|
|
812
|
+
source=data.get("source", "agent"),
|
|
813
|
+
confidence=data.get("confidence", 1.0),
|
|
814
|
+
metadata=data.get("metadata", {}),
|
|
815
|
+
expires_at=datetime.fromisoformat(data["expires_at"]) if data.get("expires_at") else None,
|
|
816
|
+
conversation_id=UUID(data["conversation_id"]) if data.get("conversation_id") else None,
|
|
817
|
+
system_id=data.get("system_id"),
|
|
818
|
+
)
|
|
819
|
+
|
|
820
|
+
|
|
821
|
+
class SharedMemoryStore(ABC):
|
|
822
|
+
"""
|
|
823
|
+
Abstract interface for shared memory storage.
|
|
824
|
+
|
|
825
|
+
Shared memory stores handle user-scoped memories that can be shared
|
|
826
|
+
across agents in a system. This is different from the basic MemoryStore
|
|
827
|
+
which is just key-value - SharedMemoryStore has:
|
|
828
|
+
|
|
829
|
+
- Semantic keys with dot-notation hierarchy
|
|
830
|
+
- Scope awareness (conversation, user, system)
|
|
831
|
+
- Confidence scores
|
|
832
|
+
- Source tracking
|
|
833
|
+
- Expiration support
|
|
834
|
+
- Batch operations
|
|
835
|
+
- Filtering and listing
|
|
836
|
+
|
|
837
|
+
Privacy enforcement should happen at the framework level (e.g., Django)
|
|
838
|
+
before calling these methods.
|
|
839
|
+
|
|
840
|
+
Example:
|
|
841
|
+
store = DjangoSharedMemoryStore(user=request.user)
|
|
842
|
+
|
|
843
|
+
# Set a memory
|
|
844
|
+
await store.set("user.preferences.theme", "dark", scope=MemoryScope.USER)
|
|
845
|
+
|
|
846
|
+
# Get a memory
|
|
847
|
+
theme = await store.get("user.preferences.theme")
|
|
848
|
+
|
|
849
|
+
# List all user preferences
|
|
850
|
+
prefs = await store.list(prefix="user.preferences", scope=MemoryScope.USER)
|
|
851
|
+
|
|
852
|
+
# Delete a memory
|
|
853
|
+
await store.delete("user.preferences.theme")
|
|
854
|
+
"""
|
|
855
|
+
|
|
856
|
+
@abstractmethod
|
|
857
|
+
async def get(
|
|
858
|
+
self,
|
|
859
|
+
key: str,
|
|
860
|
+
scope: Optional[str] = None,
|
|
861
|
+
conversation_id: Optional[UUID] = None,
|
|
862
|
+
system_id: Optional[str] = None,
|
|
863
|
+
) -> Optional[MemoryItem]:
|
|
864
|
+
"""
|
|
865
|
+
Get a memory item by key.
|
|
866
|
+
|
|
867
|
+
Args:
|
|
868
|
+
key: The semantic key (e.g., "user.preferences.theme")
|
|
869
|
+
scope: Optional scope filter (conversation, user, system)
|
|
870
|
+
conversation_id: For conversation-scoped memories
|
|
871
|
+
system_id: For system-scoped memories
|
|
872
|
+
|
|
873
|
+
Returns:
|
|
874
|
+
The MemoryItem if found, None otherwise
|
|
875
|
+
"""
|
|
876
|
+
...
|
|
877
|
+
|
|
878
|
+
@abstractmethod
|
|
879
|
+
async def set(
|
|
880
|
+
self,
|
|
881
|
+
key: str,
|
|
882
|
+
value: Any,
|
|
883
|
+
scope: str = "user",
|
|
884
|
+
source: str = "agent",
|
|
885
|
+
confidence: float = 1.0,
|
|
886
|
+
metadata: Optional[dict] = None,
|
|
887
|
+
expires_at: Optional[datetime] = None,
|
|
888
|
+
conversation_id: Optional[UUID] = None,
|
|
889
|
+
system_id: Optional[str] = None,
|
|
890
|
+
) -> MemoryItem:
|
|
891
|
+
"""
|
|
892
|
+
Set a memory item. Creates or updates.
|
|
893
|
+
|
|
894
|
+
Args:
|
|
895
|
+
key: The semantic key (e.g., "user.preferences.theme")
|
|
896
|
+
value: The value to store (any JSON-serializable type)
|
|
897
|
+
scope: Memory scope (conversation, user, system)
|
|
898
|
+
source: What created this (e.g., "agent:triage")
|
|
899
|
+
confidence: Confidence score (0.0-1.0)
|
|
900
|
+
metadata: Additional context
|
|
901
|
+
expires_at: Optional expiration time
|
|
902
|
+
conversation_id: For conversation-scoped memories
|
|
903
|
+
system_id: For system-scoped memories
|
|
904
|
+
|
|
905
|
+
Returns:
|
|
906
|
+
The created/updated MemoryItem
|
|
907
|
+
"""
|
|
908
|
+
...
|
|
909
|
+
|
|
910
|
+
@abstractmethod
|
|
911
|
+
async def delete(
|
|
912
|
+
self,
|
|
913
|
+
key: str,
|
|
914
|
+
scope: Optional[str] = None,
|
|
915
|
+
conversation_id: Optional[UUID] = None,
|
|
916
|
+
system_id: Optional[str] = None,
|
|
917
|
+
) -> bool:
|
|
918
|
+
"""
|
|
919
|
+
Delete a memory item.
|
|
920
|
+
|
|
921
|
+
Args:
|
|
922
|
+
key: The semantic key
|
|
923
|
+
scope: Optional scope filter
|
|
924
|
+
conversation_id: For conversation-scoped memories
|
|
925
|
+
system_id: For system-scoped memories
|
|
926
|
+
|
|
927
|
+
Returns:
|
|
928
|
+
True if the memory existed and was deleted
|
|
929
|
+
"""
|
|
930
|
+
...
|
|
931
|
+
|
|
932
|
+
@abstractmethod
|
|
933
|
+
async def list(
|
|
934
|
+
self,
|
|
935
|
+
prefix: Optional[str] = None,
|
|
936
|
+
scope: Optional[str] = None,
|
|
937
|
+
conversation_id: Optional[UUID] = None,
|
|
938
|
+
system_id: Optional[str] = None,
|
|
939
|
+
source: Optional[str] = None,
|
|
940
|
+
min_confidence: Optional[float] = None,
|
|
941
|
+
limit: int = 100,
|
|
942
|
+
) -> List[MemoryItem]:
|
|
943
|
+
"""
|
|
944
|
+
List memory items with optional filters.
|
|
945
|
+
|
|
946
|
+
Args:
|
|
947
|
+
prefix: Filter by key prefix (e.g., "user.preferences")
|
|
948
|
+
scope: Filter by scope
|
|
949
|
+
conversation_id: For conversation-scoped memories
|
|
950
|
+
system_id: For system-scoped memories
|
|
951
|
+
source: Filter by source
|
|
952
|
+
min_confidence: Minimum confidence score
|
|
953
|
+
limit: Maximum number of items to return
|
|
954
|
+
|
|
955
|
+
Returns:
|
|
956
|
+
List of matching MemoryItems
|
|
957
|
+
"""
|
|
958
|
+
...
|
|
959
|
+
|
|
960
|
+
@abstractmethod
|
|
961
|
+
async def get_many(
|
|
962
|
+
self,
|
|
963
|
+
keys: List[str],
|
|
964
|
+
scope: Optional[str] = None,
|
|
965
|
+
conversation_id: Optional[UUID] = None,
|
|
966
|
+
system_id: Optional[str] = None,
|
|
967
|
+
) -> Dict[str, MemoryItem]:
|
|
968
|
+
"""
|
|
969
|
+
Get multiple memory items by keys.
|
|
970
|
+
|
|
971
|
+
Args:
|
|
972
|
+
keys: List of semantic keys
|
|
973
|
+
scope: Optional scope filter
|
|
974
|
+
conversation_id: For conversation-scoped memories
|
|
975
|
+
system_id: For system-scoped memories
|
|
976
|
+
|
|
977
|
+
Returns:
|
|
978
|
+
Dictionary of key -> MemoryItem for found items
|
|
979
|
+
"""
|
|
980
|
+
...
|
|
981
|
+
|
|
982
|
+
@abstractmethod
|
|
983
|
+
async def set_many(
|
|
984
|
+
self,
|
|
985
|
+
items: List[tuple],
|
|
986
|
+
scope: str = "user",
|
|
987
|
+
source: str = "agent",
|
|
988
|
+
conversation_id: Optional[UUID] = None,
|
|
989
|
+
system_id: Optional[str] = None,
|
|
990
|
+
) -> List[MemoryItem]:
|
|
991
|
+
"""
|
|
992
|
+
Set multiple memory items atomically.
|
|
993
|
+
|
|
994
|
+
Args:
|
|
995
|
+
items: List of (key, value) tuples
|
|
996
|
+
scope: Memory scope for all items
|
|
997
|
+
source: Source for all items
|
|
998
|
+
conversation_id: For conversation-scoped memories
|
|
999
|
+
system_id: For system-scoped memories
|
|
1000
|
+
|
|
1001
|
+
Returns:
|
|
1002
|
+
List of created/updated MemoryItems
|
|
1003
|
+
"""
|
|
1004
|
+
...
|
|
1005
|
+
|
|
1006
|
+
@abstractmethod
|
|
1007
|
+
async def clear(
|
|
1008
|
+
self,
|
|
1009
|
+
scope: Optional[str] = None,
|
|
1010
|
+
conversation_id: Optional[UUID] = None,
|
|
1011
|
+
system_id: Optional[str] = None,
|
|
1012
|
+
prefix: Optional[str] = None,
|
|
1013
|
+
) -> int:
|
|
1014
|
+
"""
|
|
1015
|
+
Clear memory items.
|
|
1016
|
+
|
|
1017
|
+
Args:
|
|
1018
|
+
scope: Optional scope filter
|
|
1019
|
+
conversation_id: For conversation-scoped memories
|
|
1020
|
+
system_id: For system-scoped memories
|
|
1021
|
+
prefix: Optional key prefix filter
|
|
1022
|
+
|
|
1023
|
+
Returns:
|
|
1024
|
+
Number of items deleted
|
|
1025
|
+
"""
|
|
1026
|
+
...
|
|
1027
|
+
|
|
1028
|
+
async def get_value(
|
|
1029
|
+
self,
|
|
1030
|
+
key: str,
|
|
1031
|
+
default: Any = None,
|
|
1032
|
+
scope: Optional[str] = None,
|
|
1033
|
+
conversation_id: Optional[UUID] = None,
|
|
1034
|
+
system_id: Optional[str] = None,
|
|
1035
|
+
) -> Any:
|
|
1036
|
+
"""
|
|
1037
|
+
Convenience method to get just the value.
|
|
1038
|
+
|
|
1039
|
+
Args:
|
|
1040
|
+
key: The semantic key
|
|
1041
|
+
default: Default value if not found
|
|
1042
|
+
scope: Optional scope filter
|
|
1043
|
+
conversation_id: For conversation-scoped memories
|
|
1044
|
+
system_id: For system-scoped memories
|
|
1045
|
+
|
|
1046
|
+
Returns:
|
|
1047
|
+
The value if found, default otherwise
|
|
1048
|
+
"""
|
|
1049
|
+
item = await self.get(key, scope, conversation_id, system_id)
|
|
1050
|
+
return item.value if item else default
|
|
1051
|
+
|
|
1052
|
+
async def close(self) -> None:
|
|
1053
|
+
"""Close any connections. Override if needed."""
|
|
1054
|
+
pass
|
|
@@ -12,7 +12,7 @@ import json
|
|
|
12
12
|
import os
|
|
13
13
|
from datetime import datetime
|
|
14
14
|
from pathlib import Path
|
|
15
|
-
from typing import Any, Optional
|
|
15
|
+
from typing import Any, Optional, List, Dict
|
|
16
16
|
from uuid import UUID
|
|
17
17
|
|
|
18
18
|
from agent_runtime_core.persistence.base import (
|
|
@@ -781,4 +781,228 @@ try:
|
|
|
781
781
|
from agent_runtime_core.vectorstore.embeddings import EmbeddingClient
|
|
782
782
|
except ImportError:
|
|
783
783
|
VectorStore = None # type: ignore
|
|
784
|
-
EmbeddingClient = None # type: ignore
|
|
784
|
+
EmbeddingClient = None # type: ignore
|
|
785
|
+
|
|
786
|
+
|
|
787
|
+
# =============================================================================
|
|
788
|
+
# In-Memory Shared Memory Store (for testing)
|
|
789
|
+
# =============================================================================
|
|
790
|
+
|
|
791
|
+
|
|
792
|
+
from agent_runtime_core.persistence.base import SharedMemoryStore, MemoryItem
|
|
793
|
+
|
|
794
|
+
|
|
795
|
+
class InMemorySharedMemoryStore(SharedMemoryStore):
|
|
796
|
+
"""
|
|
797
|
+
In-memory implementation of SharedMemoryStore for testing.
|
|
798
|
+
|
|
799
|
+
This store keeps all data in memory and is not persistent.
|
|
800
|
+
Useful for unit tests and development.
|
|
801
|
+
|
|
802
|
+
Example:
|
|
803
|
+
store = InMemorySharedMemoryStore()
|
|
804
|
+
|
|
805
|
+
# Set a memory
|
|
806
|
+
await store.set("user.name", "Chris", scope="user")
|
|
807
|
+
|
|
808
|
+
# Get a memory
|
|
809
|
+
item = await store.get("user.name")
|
|
810
|
+
print(item.value) # "Chris"
|
|
811
|
+
"""
|
|
812
|
+
|
|
813
|
+
def __init__(self):
|
|
814
|
+
# Storage: dict[composite_key, MemoryItem]
|
|
815
|
+
# composite_key = f"{scope}:{conversation_id or ''}:{system_id or ''}:{key}"
|
|
816
|
+
self._storage: dict[str, MemoryItem] = {}
|
|
817
|
+
|
|
818
|
+
def _make_composite_key(
|
|
819
|
+
self,
|
|
820
|
+
key: str,
|
|
821
|
+
scope: Optional[str] = None,
|
|
822
|
+
conversation_id: Optional[UUID] = None,
|
|
823
|
+
system_id: Optional[str] = None,
|
|
824
|
+
) -> str:
|
|
825
|
+
"""Create a composite key for storage lookup."""
|
|
826
|
+
scope_part = scope or "user"
|
|
827
|
+
conv_part = str(conversation_id) if conversation_id else ""
|
|
828
|
+
sys_part = system_id or ""
|
|
829
|
+
return f"{scope_part}:{conv_part}:{sys_part}:{key}"
|
|
830
|
+
|
|
831
|
+
def _matches_filter(
|
|
832
|
+
self,
|
|
833
|
+
item: MemoryItem,
|
|
834
|
+
prefix: Optional[str] = None,
|
|
835
|
+
scope: Optional[str] = None,
|
|
836
|
+
conversation_id: Optional[UUID] = None,
|
|
837
|
+
system_id: Optional[str] = None,
|
|
838
|
+
source: Optional[str] = None,
|
|
839
|
+
min_confidence: Optional[float] = None,
|
|
840
|
+
) -> bool:
|
|
841
|
+
"""Check if an item matches the given filters."""
|
|
842
|
+
if prefix and not item.key.startswith(prefix):
|
|
843
|
+
return False
|
|
844
|
+
if scope and item.scope != scope:
|
|
845
|
+
return False
|
|
846
|
+
if conversation_id and item.conversation_id != conversation_id:
|
|
847
|
+
return False
|
|
848
|
+
if system_id and item.system_id != system_id:
|
|
849
|
+
return False
|
|
850
|
+
if source and item.source != source:
|
|
851
|
+
return False
|
|
852
|
+
if min_confidence is not None and item.confidence < min_confidence:
|
|
853
|
+
return False
|
|
854
|
+
return True
|
|
855
|
+
|
|
856
|
+
async def get(
|
|
857
|
+
self,
|
|
858
|
+
key: str,
|
|
859
|
+
scope: Optional[str] = None,
|
|
860
|
+
conversation_id: Optional[UUID] = None,
|
|
861
|
+
system_id: Optional[str] = None,
|
|
862
|
+
) -> Optional[MemoryItem]:
|
|
863
|
+
"""Get a memory item by key."""
|
|
864
|
+
composite_key = self._make_composite_key(key, scope, conversation_id, system_id)
|
|
865
|
+
return self._storage.get(composite_key)
|
|
866
|
+
|
|
867
|
+
async def set(
|
|
868
|
+
self,
|
|
869
|
+
key: str,
|
|
870
|
+
value: Any,
|
|
871
|
+
scope: str = "user",
|
|
872
|
+
source: str = "agent",
|
|
873
|
+
confidence: float = 1.0,
|
|
874
|
+
metadata: Optional[dict] = None,
|
|
875
|
+
expires_at: Optional[datetime] = None,
|
|
876
|
+
conversation_id: Optional[UUID] = None,
|
|
877
|
+
system_id: Optional[str] = None,
|
|
878
|
+
) -> MemoryItem:
|
|
879
|
+
"""Set a memory item. Creates or updates."""
|
|
880
|
+
from uuid import uuid4
|
|
881
|
+
|
|
882
|
+
composite_key = self._make_composite_key(key, scope, conversation_id, system_id)
|
|
883
|
+
existing = self._storage.get(composite_key)
|
|
884
|
+
|
|
885
|
+
now = datetime.utcnow()
|
|
886
|
+
if existing:
|
|
887
|
+
# Update existing
|
|
888
|
+
item = MemoryItem(
|
|
889
|
+
id=existing.id,
|
|
890
|
+
key=key,
|
|
891
|
+
value=value,
|
|
892
|
+
scope=scope,
|
|
893
|
+
created_at=existing.created_at,
|
|
894
|
+
updated_at=now,
|
|
895
|
+
source=source,
|
|
896
|
+
confidence=confidence,
|
|
897
|
+
metadata=metadata or {},
|
|
898
|
+
expires_at=expires_at,
|
|
899
|
+
conversation_id=conversation_id,
|
|
900
|
+
system_id=system_id,
|
|
901
|
+
)
|
|
902
|
+
else:
|
|
903
|
+
# Create new
|
|
904
|
+
item = MemoryItem(
|
|
905
|
+
id=uuid4(),
|
|
906
|
+
key=key,
|
|
907
|
+
value=value,
|
|
908
|
+
scope=scope,
|
|
909
|
+
created_at=now,
|
|
910
|
+
updated_at=now,
|
|
911
|
+
source=source,
|
|
912
|
+
confidence=confidence,
|
|
913
|
+
metadata=metadata or {},
|
|
914
|
+
expires_at=expires_at,
|
|
915
|
+
conversation_id=conversation_id,
|
|
916
|
+
system_id=system_id,
|
|
917
|
+
)
|
|
918
|
+
|
|
919
|
+
self._storage[composite_key] = item
|
|
920
|
+
return item
|
|
921
|
+
|
|
922
|
+
async def delete(
|
|
923
|
+
self,
|
|
924
|
+
key: str,
|
|
925
|
+
scope: Optional[str] = None,
|
|
926
|
+
conversation_id: Optional[UUID] = None,
|
|
927
|
+
system_id: Optional[str] = None,
|
|
928
|
+
) -> bool:
|
|
929
|
+
"""Delete a memory item."""
|
|
930
|
+
composite_key = self._make_composite_key(key, scope, conversation_id, system_id)
|
|
931
|
+
if composite_key in self._storage:
|
|
932
|
+
del self._storage[composite_key]
|
|
933
|
+
return True
|
|
934
|
+
return False
|
|
935
|
+
|
|
936
|
+
async def list(
|
|
937
|
+
self,
|
|
938
|
+
prefix: Optional[str] = None,
|
|
939
|
+
scope: Optional[str] = None,
|
|
940
|
+
conversation_id: Optional[UUID] = None,
|
|
941
|
+
system_id: Optional[str] = None,
|
|
942
|
+
source: Optional[str] = None,
|
|
943
|
+
min_confidence: Optional[float] = None,
|
|
944
|
+
limit: int = 100,
|
|
945
|
+
) -> List[MemoryItem]:
|
|
946
|
+
"""List memory items with optional filters."""
|
|
947
|
+
results = []
|
|
948
|
+
for item in self._storage.values():
|
|
949
|
+
if self._matches_filter(
|
|
950
|
+
item, prefix, scope, conversation_id, system_id, source, min_confidence
|
|
951
|
+
):
|
|
952
|
+
results.append(item)
|
|
953
|
+
|
|
954
|
+
# Sort by updated_at descending
|
|
955
|
+
results.sort(key=lambda x: x.updated_at, reverse=True)
|
|
956
|
+
return results[:limit]
|
|
957
|
+
|
|
958
|
+
async def get_many(
|
|
959
|
+
self,
|
|
960
|
+
keys: List[str],
|
|
961
|
+
scope: Optional[str] = None,
|
|
962
|
+
conversation_id: Optional[UUID] = None,
|
|
963
|
+
system_id: Optional[str] = None,
|
|
964
|
+
) -> Dict[str, MemoryItem]:
|
|
965
|
+
"""Get multiple memory items by keys."""
|
|
966
|
+
results = {}
|
|
967
|
+
for key in keys:
|
|
968
|
+
item = await self.get(key, scope, conversation_id, system_id)
|
|
969
|
+
if item:
|
|
970
|
+
results[key] = item
|
|
971
|
+
return results
|
|
972
|
+
|
|
973
|
+
async def set_many(
|
|
974
|
+
self,
|
|
975
|
+
items: List[tuple],
|
|
976
|
+
scope: str = "user",
|
|
977
|
+
source: str = "agent",
|
|
978
|
+
conversation_id: Optional[UUID] = None,
|
|
979
|
+
system_id: Optional[str] = None,
|
|
980
|
+
) -> List[MemoryItem]:
|
|
981
|
+
"""Set multiple memory items atomically."""
|
|
982
|
+
results = []
|
|
983
|
+
for key, value in items:
|
|
984
|
+
item = await self.set(
|
|
985
|
+
key, value, scope, source,
|
|
986
|
+
conversation_id=conversation_id,
|
|
987
|
+
system_id=system_id,
|
|
988
|
+
)
|
|
989
|
+
results.append(item)
|
|
990
|
+
return results
|
|
991
|
+
|
|
992
|
+
async def clear(
|
|
993
|
+
self,
|
|
994
|
+
scope: Optional[str] = None,
|
|
995
|
+
conversation_id: Optional[UUID] = None,
|
|
996
|
+
system_id: Optional[str] = None,
|
|
997
|
+
prefix: Optional[str] = None,
|
|
998
|
+
) -> int:
|
|
999
|
+
"""Clear memory items."""
|
|
1000
|
+
to_delete = []
|
|
1001
|
+
for composite_key, item in self._storage.items():
|
|
1002
|
+
if self._matches_filter(item, prefix, scope, conversation_id, system_id):
|
|
1003
|
+
to_delete.append(composite_key)
|
|
1004
|
+
|
|
1005
|
+
for key in to_delete:
|
|
1006
|
+
del self._storage[key]
|
|
1007
|
+
|
|
1008
|
+
return len(to_delete)
|