@elizaos/python 2.0.0-alpha.3 → 2.0.0-alpha.30

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (167) hide show
  1. package/elizaos/__init__.py +0 -1
  2. package/elizaos/advanced_capabilities/__init__.py +6 -41
  3. package/elizaos/advanced_capabilities/actions/__init__.py +1 -21
  4. package/elizaos/advanced_capabilities/actions/add_contact.py +24 -13
  5. package/elizaos/advanced_capabilities/actions/follow_room.py +29 -29
  6. package/elizaos/advanced_capabilities/actions/image_generation.py +15 -28
  7. package/elizaos/advanced_capabilities/actions/mute_room.py +15 -28
  8. package/elizaos/advanced_capabilities/actions/remove_contact.py +17 -3
  9. package/elizaos/advanced_capabilities/actions/roles.py +17 -30
  10. package/elizaos/advanced_capabilities/actions/schedule_follow_up.py +70 -15
  11. package/elizaos/advanced_capabilities/actions/search_contacts.py +17 -3
  12. package/elizaos/advanced_capabilities/actions/send_message.py +184 -51
  13. package/elizaos/advanced_capabilities/actions/settings.py +17 -3
  14. package/elizaos/advanced_capabilities/actions/unfollow_room.py +15 -28
  15. package/elizaos/advanced_capabilities/actions/unmute_room.py +15 -28
  16. package/elizaos/advanced_capabilities/actions/update_contact.py +17 -3
  17. package/elizaos/advanced_capabilities/actions/update_entity.py +17 -3
  18. package/elizaos/advanced_capabilities/evaluators/__init__.py +2 -9
  19. package/elizaos/advanced_capabilities/evaluators/reflection.py +3 -132
  20. package/elizaos/advanced_capabilities/evaluators/relationship_extraction.py +5 -201
  21. package/elizaos/advanced_capabilities/providers/__init__.py +1 -12
  22. package/elizaos/advanced_capabilities/providers/knowledge.py +23 -3
  23. package/elizaos/advanced_capabilities/services/__init__.py +2 -9
  24. package/elizaos/advanced_capabilities/services/rolodex.py +2 -2
  25. package/elizaos/advanced_memory/actions/reset_session.py +143 -0
  26. package/elizaos/advanced_memory/evaluators/reflection.py +134 -0
  27. package/elizaos/advanced_memory/evaluators/relationship_extraction.py +203 -0
  28. package/elizaos/advanced_memory/memory_service.py +69 -27
  29. package/elizaos/advanced_memory/plugin.py +2 -1
  30. package/elizaos/advanced_memory/test_advanced_memory.py +357 -0
  31. package/elizaos/advanced_memory/types.py +2 -2
  32. package/elizaos/advanced_planning/actions/schedule_follow_up.py +222 -0
  33. package/elizaos/advanced_planning/planning_service.py +26 -14
  34. package/elizaos/basic_capabilities/__init__.py +0 -2
  35. package/elizaos/basic_capabilities/providers/__init__.py +0 -3
  36. package/elizaos/basic_capabilities/providers/actions.py +118 -29
  37. package/elizaos/basic_capabilities/providers/agent_settings.py +64 -0
  38. package/elizaos/basic_capabilities/providers/character.py +19 -21
  39. package/elizaos/basic_capabilities/providers/contacts.py +79 -0
  40. package/elizaos/basic_capabilities/providers/current_time.py +7 -4
  41. package/elizaos/basic_capabilities/providers/facts.py +87 -0
  42. package/elizaos/basic_capabilities/providers/follow_ups.py +117 -0
  43. package/elizaos/basic_capabilities/providers/knowledge.py +96 -0
  44. package/elizaos/basic_capabilities/providers/recent_messages.py +5 -0
  45. package/elizaos/basic_capabilities/providers/relationships.py +113 -0
  46. package/elizaos/basic_capabilities/providers/roles.py +96 -0
  47. package/elizaos/basic_capabilities/providers/settings.py +56 -0
  48. package/elizaos/basic_capabilities/providers/time.py +7 -4
  49. package/elizaos/basic_capabilities/services/embedding.py +10 -7
  50. package/elizaos/basic_capabilities/services/task.py +3 -3
  51. package/elizaos/bootstrap/__init__.py +21 -2
  52. package/elizaos/bootstrap/actions/__init__.py +3 -0
  53. package/elizaos/bootstrap/actions/reset_session.py +3 -0
  54. package/elizaos/bootstrap/actions/roles.py +5 -4
  55. package/elizaos/bootstrap/actions/schedule_follow_up.py +65 -7
  56. package/elizaos/bootstrap/actions/send_message.py +162 -15
  57. package/elizaos/bootstrap/autonomy/__init__.py +5 -1
  58. package/elizaos/bootstrap/autonomy/action.py +161 -0
  59. package/elizaos/bootstrap/autonomy/evaluators.py +217 -0
  60. package/elizaos/bootstrap/autonomy/service.py +238 -28
  61. package/elizaos/bootstrap/plugin.py +7 -0
  62. package/elizaos/bootstrap/providers/actions.py +118 -27
  63. package/elizaos/bootstrap/providers/agent_settings.py +1 -0
  64. package/elizaos/bootstrap/providers/attachments.py +1 -0
  65. package/elizaos/bootstrap/providers/capabilities.py +1 -0
  66. package/elizaos/bootstrap/providers/character.py +1 -0
  67. package/elizaos/bootstrap/providers/choice.py +1 -0
  68. package/elizaos/bootstrap/providers/contacts.py +1 -0
  69. package/elizaos/bootstrap/providers/current_time.py +8 -2
  70. package/elizaos/bootstrap/providers/entities.py +1 -0
  71. package/elizaos/bootstrap/providers/evaluators.py +1 -0
  72. package/elizaos/bootstrap/providers/facts.py +1 -0
  73. package/elizaos/bootstrap/providers/follow_ups.py +1 -0
  74. package/elizaos/bootstrap/providers/knowledge.py +26 -3
  75. package/elizaos/bootstrap/providers/providers_list.py +1 -0
  76. package/elizaos/bootstrap/providers/recent_messages.py +5 -0
  77. package/elizaos/bootstrap/providers/relationships.py +20 -13
  78. package/elizaos/bootstrap/providers/roles.py +1 -0
  79. package/elizaos/bootstrap/providers/settings.py +1 -0
  80. package/elizaos/bootstrap/providers/time.py +8 -4
  81. package/elizaos/bootstrap/providers/world.py +1 -0
  82. package/elizaos/bootstrap/services/embedding.py +206 -8
  83. package/elizaos/bootstrap/services/rolodex.py +2 -2
  84. package/elizaos/bootstrap/services/task.py +3 -3
  85. package/elizaos/deterministic.py +193 -0
  86. package/elizaos/generated/__init__.py +1 -0
  87. package/elizaos/generated/action_docs.py +3181 -0
  88. package/elizaos/generated/spec_helpers.py +175 -0
  89. package/elizaos/media/mime.py +4 -4
  90. package/elizaos/media/search.py +23 -23
  91. package/elizaos/runtime.py +223 -64
  92. package/elizaos/services/hook_service.py +3 -3
  93. package/elizaos/services/message_service.py +175 -29
  94. package/elizaos/types/components.py +2 -2
  95. package/elizaos/types/generated/__init__.py +12 -0
  96. package/elizaos/types/generated/eliza/v1/agent_pb2.py +63 -0
  97. package/elizaos/types/generated/eliza/v1/agent_pb2.pyi +159 -0
  98. package/elizaos/types/generated/eliza/v1/components_pb2.py +65 -0
  99. package/elizaos/types/generated/eliza/v1/components_pb2.pyi +160 -0
  100. package/elizaos/types/generated/eliza/v1/database_pb2.py +78 -0
  101. package/elizaos/types/generated/eliza/v1/database_pb2.pyi +305 -0
  102. package/elizaos/types/generated/eliza/v1/environment_pb2.py +58 -0
  103. package/elizaos/types/generated/eliza/v1/environment_pb2.pyi +135 -0
  104. package/elizaos/types/generated/eliza/v1/events_pb2.py +82 -0
  105. package/elizaos/types/generated/eliza/v1/events_pb2.pyi +322 -0
  106. package/elizaos/types/generated/eliza/v1/ipc_pb2.py +113 -0
  107. package/elizaos/types/generated/eliza/v1/ipc_pb2.pyi +367 -0
  108. package/elizaos/types/generated/eliza/v1/knowledge_pb2.py +41 -0
  109. package/elizaos/types/generated/eliza/v1/knowledge_pb2.pyi +26 -0
  110. package/elizaos/types/generated/eliza/v1/memory_pb2.py +55 -0
  111. package/elizaos/types/generated/eliza/v1/memory_pb2.pyi +111 -0
  112. package/elizaos/types/generated/eliza/v1/message_service_pb2.py +48 -0
  113. package/elizaos/types/generated/eliza/v1/message_service_pb2.pyi +69 -0
  114. package/elizaos/types/generated/eliza/v1/messaging_pb2.py +51 -0
  115. package/elizaos/types/generated/eliza/v1/messaging_pb2.pyi +97 -0
  116. package/elizaos/types/generated/eliza/v1/model_pb2.py +84 -0
  117. package/elizaos/types/generated/eliza/v1/model_pb2.pyi +280 -0
  118. package/elizaos/types/generated/eliza/v1/payment_pb2.py +44 -0
  119. package/elizaos/types/generated/eliza/v1/payment_pb2.pyi +70 -0
  120. package/elizaos/types/generated/eliza/v1/plugin_pb2.py +68 -0
  121. package/elizaos/types/generated/eliza/v1/plugin_pb2.pyi +145 -0
  122. package/elizaos/types/generated/eliza/v1/primitives_pb2.py +48 -0
  123. package/elizaos/types/generated/eliza/v1/primitives_pb2.pyi +92 -0
  124. package/elizaos/types/generated/eliza/v1/prompts_pb2.py +52 -0
  125. package/elizaos/types/generated/eliza/v1/prompts_pb2.pyi +74 -0
  126. package/elizaos/types/generated/eliza/v1/service_interfaces_pb2.py +211 -0
  127. package/elizaos/types/generated/eliza/v1/service_interfaces_pb2.pyi +1296 -0
  128. package/elizaos/types/generated/eliza/v1/service_pb2.py +42 -0
  129. package/elizaos/types/generated/eliza/v1/service_pb2.pyi +69 -0
  130. package/elizaos/types/generated/eliza/v1/settings_pb2.py +58 -0
  131. package/elizaos/types/generated/eliza/v1/settings_pb2.pyi +85 -0
  132. package/elizaos/types/generated/eliza/v1/state_pb2.py +60 -0
  133. package/elizaos/types/generated/eliza/v1/state_pb2.pyi +114 -0
  134. package/elizaos/types/generated/eliza/v1/task_pb2.py +42 -0
  135. package/elizaos/types/generated/eliza/v1/task_pb2.pyi +58 -0
  136. package/elizaos/types/generated/eliza/v1/tee_pb2.py +52 -0
  137. package/elizaos/types/generated/eliza/v1/tee_pb2.pyi +90 -0
  138. package/elizaos/types/generated/eliza/v1/testing_pb2.py +39 -0
  139. package/elizaos/types/generated/eliza/v1/testing_pb2.pyi +23 -0
  140. package/elizaos/types/model.py +33 -3
  141. package/elizaos/types/primitives.py +3 -3
  142. package/elizaos/types/runtime.py +17 -3
  143. package/elizaos/types/state.py +2 -2
  144. package/elizaos/utils/streaming.py +3 -3
  145. package/elizaos/utils/validation.py +76 -0
  146. package/package.json +4 -3
  147. package/pyproject.toml +1 -2
  148. package/requirements-dev.lock +2 -2
  149. package/requirements.in +1 -2
  150. package/requirements.lock +2 -2
  151. package/tests/test_action_parameters.py +2 -3
  152. package/tests/test_actions_provider_examples.py +58 -1
  153. package/tests/test_advanced_memory_behavior.py +0 -2
  154. package/tests/test_advanced_memory_flag.py +0 -2
  155. package/tests/test_advanced_planning_behavior.py +11 -5
  156. package/tests/test_async_embedding.py +124 -0
  157. package/tests/test_autonomy.py +24 -3
  158. package/tests/test_history_compaction.py +104 -0
  159. package/tests/test_memory_bounds.py +115 -0
  160. package/tests/test_runtime.py +8 -17
  161. package/tests/test_schedule_follow_up_action.py +260 -0
  162. package/tests/test_send_message_action_targets.py +114 -0
  163. package/tests/test_settings_crypto.py +0 -2
  164. package/tests/test_validation.py +141 -0
  165. package/tests/verify_memory_architecture.py +192 -0
  166. package/uv.lock +1565 -0
  167. package/elizaos/basic_capabilities/providers/capabilities.py +0 -62
@@ -0,0 +1,134 @@
1
+ from __future__ import annotations
2
+
3
+ from collections.abc import Awaitable, Callable
4
+ from typing import TYPE_CHECKING
5
+
6
+ from elizaos.bootstrap.types import EvaluatorResult
7
+ from elizaos.bootstrap.utils.xml import parse_key_value_xml
8
+ from elizaos.generated.spec_helpers import require_evaluator_spec
9
+ from elizaos.prompts import REFLECTION_TEMPLATE
10
+ from elizaos.types import ActionResult, Evaluator, HandlerOptions, ModelType
11
+
12
+ if TYPE_CHECKING:
13
+ from elizaos.types import Content, IAgentRuntime, Memory, State
14
+
15
+ # Get text content from centralized specs
16
+ _spec = require_evaluator_spec("REFLECTION")
17
+
18
+
19
+ async def evaluate_reflection(
20
+ runtime: IAgentRuntime,
21
+ message: Memory,
22
+ state: State | None = None,
23
+ ) -> EvaluatorResult:
24
+ if state is None:
25
+ return EvaluatorResult(
26
+ score=50,
27
+ passed=True,
28
+ reason="No state for reflection",
29
+ details={},
30
+ )
31
+
32
+ recent_interactions: list[str] = []
33
+ room_id = message.room_id
34
+
35
+ if room_id:
36
+ recent_messages = await runtime.get_memories(
37
+ room_id=room_id,
38
+ limit=10,
39
+ order_by="created_at",
40
+ order_direction="desc",
41
+ )
42
+
43
+ for msg in recent_messages:
44
+ if msg.content and msg.content.text:
45
+ sender = "Unknown"
46
+ if msg.entity_id:
47
+ if str(msg.entity_id) == str(runtime.agent_id):
48
+ sender = runtime.character.name
49
+ else:
50
+ entity = await runtime.get_entity(msg.entity_id)
51
+ if entity and entity.name:
52
+ sender = entity.name
53
+ recent_interactions.append(f"{sender}: {msg.content.text}")
54
+
55
+ if not recent_interactions:
56
+ return EvaluatorResult(
57
+ score=50,
58
+ passed=True,
59
+ reason="No recent interactions to reflect on",
60
+ details={"noInteractions": True},
61
+ )
62
+
63
+ interactions_text = "\n".join(recent_interactions)
64
+
65
+ template = (
66
+ runtime.character.templates.get("reflectionTemplate")
67
+ if runtime.character.templates and "reflectionTemplate" in runtime.character.templates
68
+ else REFLECTION_TEMPLATE
69
+ )
70
+ prompt = runtime.compose_prompt(state=state, template=template)
71
+ prompt = prompt.replace("{{recentInteractions}}", interactions_text)
72
+
73
+ response_text = await runtime.use_model(ModelType.TEXT_LARGE, prompt=prompt)
74
+ parsed_xml = parse_key_value_xml(response_text)
75
+
76
+ if parsed_xml is None:
77
+ raise ValueError("Failed to parse reflection response")
78
+
79
+ quality_str = str(parsed_xml.get("quality_score", "50"))
80
+ quality_score = max(0, min(100, int(quality_str)))
81
+
82
+ thought = str(parsed_xml.get("thought", ""))
83
+ strengths = str(parsed_xml.get("strengths", ""))
84
+ improvements = str(parsed_xml.get("improvements", ""))
85
+ learnings = str(parsed_xml.get("learnings", ""))
86
+
87
+ passed = quality_score >= 50
88
+
89
+ return EvaluatorResult(
90
+ score=quality_score,
91
+ passed=passed,
92
+ reason=f"Strengths: {strengths}\nImprovements: {improvements}",
93
+ details={
94
+ "thought": thought,
95
+ "strengths": strengths,
96
+ "improvements": improvements,
97
+ "learnings": learnings,
98
+ "interactionCount": len(recent_interactions),
99
+ },
100
+ )
101
+
102
+
103
+ async def validate_reflection(
104
+ runtime: IAgentRuntime,
105
+ message: Memory,
106
+ _state: State | None = None,
107
+ ) -> bool:
108
+ return True
109
+
110
+
111
+ async def _reflection_handler(
112
+ runtime: IAgentRuntime,
113
+ message: Memory,
114
+ state: State | None = None,
115
+ options: HandlerOptions | None = None,
116
+ callback: Callable[[Content], Awaitable[list[Memory]]] | None = None,
117
+ responses: list[Memory] | None = None,
118
+ ) -> ActionResult | None:
119
+ """Wrapper handler that matches the expected signature."""
120
+ _ = options, callback, responses # Unused parameters
121
+ result = await evaluate_reflection(runtime, message, state)
122
+ # Return ActionResult - evaluators don't typically return action results
123
+ return ActionResult(text=result.reason, success=result.passed, values={}, data={})
124
+
125
+
126
+ reflection_evaluator = Evaluator(
127
+ name=str(_spec["name"]),
128
+ description=str(_spec["description"]),
129
+ similes=list(_spec.get("similes", [])) if _spec.get("similes") else [],
130
+ validate=validate_reflection,
131
+ handler=_reflection_handler,
132
+ always_run=bool(_spec.get("alwaysRun", False)),
133
+ examples=[],
134
+ )
@@ -0,0 +1,203 @@
1
+ from __future__ import annotations
2
+
3
+ import re
4
+ from collections.abc import Awaitable, Callable
5
+ from typing import TYPE_CHECKING
6
+
7
+ from elizaos.bootstrap.types import EvaluatorResult
8
+ from elizaos.generated.spec_helpers import require_evaluator_spec
9
+ from elizaos.types import ActionResult, Evaluator, HandlerOptions
10
+
11
+ if TYPE_CHECKING:
12
+ from elizaos.types import Content, IAgentRuntime, Memory, State
13
+
14
+ # Get text content from centralized specs
15
+ _spec = require_evaluator_spec("RELATIONSHIP_EXTRACTION")
16
+
17
+ X_HANDLE_PATTERN = re.compile(r"@[\w]+")
18
+ EMAIL_PATTERN = re.compile(r"[\w.+-]+@[\w.-]+\.\w+")
19
+ PHONE_PATTERN = re.compile(r"\+?[\d\s\-()]{10,}")
20
+ DISCORD_PATTERN = re.compile(r"[\w]+#\d{4}")
21
+
22
+
23
+ def extract_platform_identities(text: str) -> list[dict[str, str | bool | float]]:
24
+ identities: list[dict[str, str | bool | float]] = []
25
+
26
+ for match in X_HANDLE_PATTERN.finditer(text):
27
+ handle = match.group()
28
+ if handle.lower() not in ("@here", "@everyone", "@channel"):
29
+ identities.append(
30
+ {
31
+ "platform": "x",
32
+ "handle": handle,
33
+ "verified": False,
34
+ "confidence": 0.7,
35
+ }
36
+ )
37
+
38
+ for match in EMAIL_PATTERN.finditer(text):
39
+ identities.append(
40
+ {
41
+ "platform": "email",
42
+ "handle": match.group(),
43
+ "verified": False,
44
+ "confidence": 0.9,
45
+ }
46
+ )
47
+
48
+ for match in DISCORD_PATTERN.finditer(text):
49
+ identities.append(
50
+ {
51
+ "platform": "discord",
52
+ "handle": match.group(),
53
+ "verified": False,
54
+ "confidence": 0.8,
55
+ }
56
+ )
57
+
58
+ return identities
59
+
60
+
61
+ def detect_relationship_indicators(text: str) -> list[dict[str, str | float]]:
62
+ indicators: list[dict[str, str | float]] = []
63
+
64
+ friend_patterns = [
65
+ r"my friend",
66
+ r"good friend",
67
+ r"best friend",
68
+ r"close friend",
69
+ r"we're friends",
70
+ ]
71
+ for pattern in friend_patterns:
72
+ if re.search(pattern, text, re.IGNORECASE):
73
+ indicators.append(
74
+ {
75
+ "type": "friend",
76
+ "sentiment": "positive",
77
+ "confidence": 0.8,
78
+ }
79
+ )
80
+ break
81
+
82
+ colleague_patterns = [
83
+ r"my colleague",
84
+ r"coworker",
85
+ r"co-worker",
86
+ r"work together",
87
+ r"at work",
88
+ ]
89
+ for pattern in colleague_patterns:
90
+ if re.search(pattern, text, re.IGNORECASE):
91
+ indicators.append(
92
+ {
93
+ "type": "colleague",
94
+ "sentiment": "neutral",
95
+ "confidence": 0.8,
96
+ }
97
+ )
98
+ break
99
+
100
+ family_patterns = [
101
+ r"my (brother|sister|mom|dad|mother|father|parent|son|daughter|child)",
102
+ r"my family",
103
+ r"family member",
104
+ ]
105
+ for pattern in family_patterns:
106
+ if re.search(pattern, text, re.IGNORECASE):
107
+ indicators.append(
108
+ {
109
+ "type": "family",
110
+ "sentiment": "positive",
111
+ "confidence": 0.9,
112
+ }
113
+ )
114
+ break
115
+
116
+ return indicators
117
+
118
+
119
+ async def evaluate_relationship_extraction(
120
+ runtime: IAgentRuntime,
121
+ message: Memory,
122
+ state: State | None = None,
123
+ ) -> EvaluatorResult:
124
+ text = message.content.text if message.content else ""
125
+
126
+ if not text:
127
+ return EvaluatorResult(
128
+ score=50,
129
+ passed=True,
130
+ reason="No text to analyze",
131
+ details={"noText": True},
132
+ )
133
+
134
+ identities = extract_platform_identities(text)
135
+
136
+ indicators = detect_relationship_indicators(text)
137
+
138
+ if identities and message.entity_id:
139
+ entity = await runtime.get_entity(str(message.entity_id))
140
+ if entity:
141
+ metadata = entity.metadata or {}
142
+ existing_identities = metadata.get("platformIdentities", [])
143
+ if isinstance(existing_identities, list):
144
+ for identity in identities:
145
+ exists = any(
146
+ i.get("platform") == identity["platform"]
147
+ and i.get("handle") == identity["handle"]
148
+ for i in existing_identities
149
+ if isinstance(i, dict)
150
+ )
151
+ if not exists:
152
+ existing_identities.append(identity)
153
+ metadata["platformIdentities"] = existing_identities
154
+ entity.metadata = metadata
155
+ await runtime.update_entity(entity)
156
+
157
+ runtime.logger.info(
158
+ f"Completed extraction: src=evaluator:relationship_extraction agentId={runtime.agent_id} identitiesFound={len(identities)} indicatorsFound={len(indicators)}"
159
+ )
160
+
161
+ return EvaluatorResult(
162
+ score=70,
163
+ passed=True,
164
+ reason=f"Found {len(identities)} identities and {len(indicators)} relationship indicators",
165
+ details={
166
+ "identitiesCount": len(identities),
167
+ "indicatorsCount": len(indicators),
168
+ },
169
+ )
170
+
171
+
172
+ async def validate_relationship_extraction(
173
+ runtime: IAgentRuntime,
174
+ message: Memory,
175
+ _state: State | None = None,
176
+ ) -> bool:
177
+ return message.content is not None and bool(message.content.text)
178
+
179
+
180
+ async def _relationship_extraction_handler(
181
+ runtime: IAgentRuntime,
182
+ message: Memory,
183
+ state: State | None = None,
184
+ options: HandlerOptions | None = None,
185
+ callback: Callable[[Content], Awaitable[list[Memory]]] | None = None,
186
+ responses: list[Memory] | None = None,
187
+ ) -> ActionResult | None:
188
+ """Wrapper handler that matches the expected signature."""
189
+ _ = options, callback, responses # Unused parameters
190
+ result = await evaluate_relationship_extraction(runtime, message, state)
191
+ # Return None as ActionResult - evaluators don't typically return action results
192
+ return ActionResult(text=result.reason, success=result.passed, values={}, data={})
193
+
194
+
195
+ relationship_extraction_evaluator = Evaluator(
196
+ name=str(_spec["name"]),
197
+ description=str(_spec["description"]),
198
+ similes=list(_spec.get("similes", [])) if _spec.get("similes") else [],
199
+ validate=validate_relationship_extraction,
200
+ handler=_relationship_extraction_handler,
201
+ always_run=bool(_spec.get("alwaysRun", False)),
202
+ examples=[],
203
+ )
@@ -3,7 +3,8 @@ from __future__ import annotations
3
3
  import heapq
4
4
  import re
5
5
  import time
6
- from typing import cast
6
+ from collections import OrderedDict
7
+ from typing import TypeVar, cast
7
8
  from uuid import UUID, uuid4
8
9
 
9
10
  from elizaos.types.model import ModelType
@@ -27,6 +28,7 @@ from .types import (
27
28
  _TABLE_SESSION_SUMMARY = "session_summary"
28
29
  _TABLE_LONG_TERM_MEMORY = "long_term_memory"
29
30
  _GLOBAL_LONG_TERM_ROOM_ID = string_to_uuid("advanced-memory:long-term")
31
+ _OrderedValueT = TypeVar("_OrderedValueT")
30
32
 
31
33
 
32
34
  def _parse_summary_xml(xml: str) -> SummaryResult:
@@ -79,14 +81,28 @@ def _top_k_by_confidence(items: list[LongTermMemory], limit: int) -> list[LongTe
79
81
 
80
82
  class MemoryService(Service):
81
83
  service_type = "memory"
84
+ _MAX_LOCAL_SESSION_SUMMARIES = 500
85
+ _MAX_LOCAL_EXTRACTION_CHECKPOINTS = 500
86
+ _MAX_LOCAL_LONG_TERM_ENTITIES = 500
87
+ _MAX_LOCAL_LONG_TERM_PER_ENTITY = 200
82
88
 
83
89
  def __init__(self, runtime=None) -> None:
84
90
  super().__init__(runtime=runtime)
85
91
  self._config: MemoryConfig = MemoryConfig()
86
92
  # Fallback storage for runtimes without a DB adapter (tests/benchmarks).
87
- self._session_summaries: dict[str, SessionSummary] = {}
88
- self._long_term: dict[str, list[LongTermMemory]] = {}
89
- self._extraction_checkpoints: dict[str, int] = {}
93
+ self._session_summaries: OrderedDict[str, SessionSummary] = OrderedDict()
94
+ self._long_term: OrderedDict[str, list[LongTermMemory]] = OrderedDict()
95
+ self._extraction_checkpoints: OrderedDict[str, int] = OrderedDict()
96
+
97
+ @staticmethod
98
+ def _touch_ordered(mapping: OrderedDict[str, _OrderedValueT], key: str) -> None:
99
+ if key in mapping:
100
+ mapping.move_to_end(key)
101
+
102
+ @staticmethod
103
+ def _prune_ordered(mapping: OrderedDict[str, _OrderedValueT], max_entries: int) -> None:
104
+ while len(mapping) > max_entries:
105
+ mapping.popitem(last=False)
90
106
 
91
107
  @property
92
108
  def capability_description(self) -> str:
@@ -95,38 +111,36 @@ class MemoryService(Service):
95
111
  @classmethod
96
112
  async def start(cls, runtime):
97
113
  svc = cls(runtime=runtime)
98
- # read settings
99
- settings = runtime.character.settings or {}
100
- if (v := settings.get("MEMORY_SUMMARIZATION_THRESHOLD")) is not None and isinstance(
114
+ if (v := runtime.get_setting("MEMORY_SUMMARIZATION_THRESHOLD")) is not None and isinstance(
101
115
  v, (int, float, str)
102
116
  ):
103
117
  svc._config.short_term_summarization_threshold = int(v)
104
- if (v := settings.get("MEMORY_RETAIN_RECENT")) is not None and isinstance(
118
+ if (v := runtime.get_setting("MEMORY_RETAIN_RECENT")) is not None and isinstance(
105
119
  v, (int, float, str)
106
120
  ):
107
121
  svc._config.short_term_retain_recent = int(v)
108
- if (v := settings.get("MEMORY_SUMMARIZATION_INTERVAL")) is not None and isinstance(
122
+ if (v := runtime.get_setting("MEMORY_SUMMARIZATION_INTERVAL")) is not None and isinstance(
109
123
  v, (int, float, str)
110
124
  ):
111
125
  svc._config.short_term_summarization_interval = int(v)
112
- if (v := settings.get("MEMORY_MAX_NEW_MESSAGES")) is not None and isinstance(
126
+ if (v := runtime.get_setting("MEMORY_MAX_NEW_MESSAGES")) is not None and isinstance(
113
127
  v, (int, float, str)
114
128
  ):
115
129
  svc._config.summary_max_new_messages = int(v)
116
- if (v := settings.get("MEMORY_LONG_TERM_ENABLED")) is not None:
130
+ if (v := runtime.get_setting("MEMORY_LONG_TERM_ENABLED")) is not None:
117
131
  if str(v).lower() == "false":
118
132
  svc._config.long_term_extraction_enabled = False
119
133
  elif str(v).lower() == "true":
120
134
  svc._config.long_term_extraction_enabled = True
121
- if (v := settings.get("MEMORY_CONFIDENCE_THRESHOLD")) is not None and isinstance(
135
+ if (v := runtime.get_setting("MEMORY_CONFIDENCE_THRESHOLD")) is not None and isinstance(
122
136
  v, (int, float, str)
123
137
  ):
124
138
  svc._config.long_term_confidence_threshold = float(v)
125
- if (v := settings.get("MEMORY_EXTRACTION_THRESHOLD")) is not None and isinstance(
139
+ if (v := runtime.get_setting("MEMORY_EXTRACTION_THRESHOLD")) is not None and isinstance(
126
140
  v, (int, float, str)
127
141
  ):
128
142
  svc._config.long_term_extraction_threshold = int(v)
129
- if (v := settings.get("MEMORY_EXTRACTION_INTERVAL")) is not None and isinstance(
143
+ if (v := runtime.get_setting("MEMORY_EXTRACTION_INTERVAL")) is not None and isinstance(
130
144
  v, (int, float, str)
131
145
  ):
132
146
  svc._config.long_term_extraction_interval = int(v)
@@ -146,7 +160,7 @@ class MemoryService(Service):
146
160
  return f"memory:extraction:{entity_id}:{room_id}"
147
161
 
148
162
  async def get_last_extraction_checkpoint(self, entity_id: UUID, room_id: UUID) -> int:
149
- runtime = self.runtime
163
+ runtime = self._runtime
150
164
  key = self._checkpoint_key(entity_id, room_id)
151
165
  if runtime is not None and getattr(runtime, "_adapter", None) is not None:
152
166
  cached = await runtime.get_cache(key)
@@ -158,17 +172,23 @@ class MemoryService(Service):
158
172
  return 0
159
173
  except Exception:
160
174
  return 0
161
- return int(self._extraction_checkpoints.get(key, 0))
175
+ checkpoint = self._extraction_checkpoints.get(key)
176
+ if checkpoint is None:
177
+ return 0
178
+ self._touch_ordered(self._extraction_checkpoints, key)
179
+ return int(checkpoint)
162
180
 
163
181
  async def set_last_extraction_checkpoint(
164
182
  self, entity_id: UUID, room_id: UUID, message_count: int
165
183
  ) -> None:
166
- runtime = self.runtime
184
+ runtime = self._runtime
167
185
  key = self._checkpoint_key(entity_id, room_id)
168
186
  if runtime is not None and getattr(runtime, "_adapter", None) is not None:
169
187
  _ = await runtime.set_cache(key, int(message_count))
170
188
  return
171
189
  self._extraction_checkpoints[key] = int(message_count)
190
+ self._touch_ordered(self._extraction_checkpoints, key)
191
+ self._prune_ordered(self._extraction_checkpoints, self._MAX_LOCAL_EXTRACTION_CHECKPOINTS)
172
192
 
173
193
  async def should_run_extraction(
174
194
  self, entity_id: UUID, room_id: UUID, current_message_count: int
@@ -182,7 +202,7 @@ class MemoryService(Service):
182
202
  return current_cp > last_cp
183
203
 
184
204
  async def get_current_session_summary(self, room_id: UUID) -> SessionSummary | None:
185
- runtime = self.runtime
205
+ runtime = self._runtime
186
206
  if runtime is None:
187
207
  return None
188
208
 
@@ -221,7 +241,11 @@ class MemoryService(Service):
221
241
  # Best-effort; ignore corrupt rows.
222
242
  continue
223
243
 
224
- return self._session_summaries.get(str(room_id))
244
+ summary = self._session_summaries.get(str(room_id))
245
+ if summary is None:
246
+ return None
247
+ self._touch_ordered(self._session_summaries, str(room_id))
248
+ return summary
225
249
 
226
250
  async def store_session_summary(
227
251
  self,
@@ -234,7 +258,7 @@ class MemoryService(Service):
234
258
  topics: list[str] | None = None,
235
259
  metadata: dict[str, object] | None = None,
236
260
  ) -> SessionSummary:
237
- runtime = self.runtime
261
+ runtime = self._runtime
238
262
  s = SessionSummary(
239
263
  id=uuid4(),
240
264
  agent_id=agent_id,
@@ -273,13 +297,16 @@ class MemoryService(Service):
273
297
  )
274
298
  return s
275
299
 
276
- self._session_summaries[str(room_id)] = s
300
+ room_key = str(room_id)
301
+ self._session_summaries[room_key] = s
302
+ self._touch_ordered(self._session_summaries, room_key)
303
+ self._prune_ordered(self._session_summaries, self._MAX_LOCAL_SESSION_SUMMARIES)
277
304
  return s
278
305
 
279
306
  async def update_session_summary(
280
307
  self, summary_id: UUID, room_id: UUID, **updates: object
281
308
  ) -> None:
282
- runtime = self.runtime
309
+ runtime = self._runtime
283
310
  if runtime is not None and getattr(runtime, "_adapter", None) is not None:
284
311
  existing = await self.get_current_session_summary(room_id)
285
312
  if not existing or existing.id != summary_id:
@@ -324,9 +351,11 @@ class MemoryService(Service):
324
351
  )
325
352
  return
326
353
 
327
- existing = self._session_summaries.get(str(room_id))
354
+ room_key = str(room_id)
355
+ existing = self._session_summaries.get(room_key)
328
356
  if not existing or existing.id != summary_id:
329
357
  return
358
+ self._touch_ordered(self._session_summaries, room_key)
330
359
  if "summary" in updates and isinstance(updates["summary"], str):
331
360
  existing.summary = updates["summary"]
332
361
  if "message_count" in updates and isinstance(updates["message_count"], (int, float, str)):
@@ -350,7 +379,7 @@ class MemoryService(Service):
350
379
  source: str | None = None,
351
380
  metadata: dict[str, object] | None = None,
352
381
  ) -> LongTermMemory:
353
- runtime = self.runtime
382
+ runtime = self._runtime
354
383
  m = LongTermMemory(
355
384
  id=uuid4(),
356
385
  agent_id=agent_id,
@@ -383,7 +412,17 @@ class MemoryService(Service):
383
412
  )
384
413
  return m
385
414
 
386
- self._long_term.setdefault(str(entity_id), []).append(m)
415
+ entity_key = str(entity_id)
416
+ bucket = self._long_term.get(entity_key)
417
+ if bucket is None:
418
+ bucket = []
419
+ self._long_term[entity_key] = bucket
420
+ else:
421
+ self._touch_ordered(self._long_term, entity_key)
422
+ bucket.append(m)
423
+ if len(bucket) > self._MAX_LOCAL_LONG_TERM_PER_ENTITY:
424
+ del bucket[: len(bucket) - self._MAX_LOCAL_LONG_TERM_PER_ENTITY]
425
+ self._prune_ordered(self._long_term, self._MAX_LOCAL_LONG_TERM_ENTITIES)
387
426
  return m
388
427
 
389
428
  async def get_long_term_memories(
@@ -394,7 +433,7 @@ class MemoryService(Service):
394
433
  ) -> list[LongTermMemory]:
395
434
  if limit <= 0:
396
435
  return []
397
- runtime = self.runtime
436
+ runtime = self._runtime
398
437
  if runtime is not None and getattr(runtime, "_adapter", None) is not None:
399
438
  db_mems = await runtime.get_memories(
400
439
  {
@@ -439,7 +478,10 @@ class MemoryService(Service):
439
478
  continue
440
479
  return _top_k_by_confidence(out, limit)
441
480
 
442
- local_mems = self._long_term.get(str(entity_id), [])
481
+ entity_key = str(entity_id)
482
+ local_mems = self._long_term.get(entity_key, [])
483
+ if entity_key in self._long_term:
484
+ self._touch_ordered(self._long_term, entity_key)
443
485
  if category is not None:
444
486
  local_mems = [m for m in local_mems if m.category == category]
445
487
  return _top_k_by_confidence(local_mems, limit)
@@ -2,6 +2,7 @@ from __future__ import annotations
2
2
 
3
3
  from elizaos.types import Plugin
4
4
 
5
+ from .actions.reset_session import reset_session_action
5
6
  from .evaluators import long_term_extraction_evaluator, summarization_evaluator
6
7
  from .memory_service import MemoryService
7
8
  from .providers import context_summary_provider, long_term_memory_provider
@@ -21,7 +22,7 @@ def create_advanced_memory_plugin() -> Plugin:
21
22
  init=init_plugin,
22
23
  config={},
23
24
  services=[MemoryService],
24
- actions=[],
25
+ actions=[reset_session_action],
25
26
  providers=[long_term_memory_provider, context_summary_provider],
26
27
  evaluators=[summarization_evaluator, long_term_extraction_evaluator],
27
28
  )