@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
@@ -4,6 +4,7 @@ from typing import Any
4
4
  from uuid import uuid4
5
5
 
6
6
  import pytest
7
+ from google.protobuf.json_format import MessageToDict
7
8
 
8
9
  from elizaos.advanced_planning.planning_service import ActionPlan, ActionStep
9
10
  from elizaos.runtime import AgentRuntime
@@ -15,7 +16,6 @@ from elizaos.types.model import ModelType
15
16
  from elizaos.types.primitives import Content, as_uuid
16
17
 
17
18
 
18
- @pytest.mark.skip(reason="State.values.extra access issue with protobuf")
19
19
  @pytest.mark.asyncio
20
20
  async def test_advanced_planning_provider_parses_model_output() -> None:
21
21
  character = Character(name="AdvPlanningProvider", bio=["Test"], advanced_planning=True)
@@ -49,7 +49,13 @@ async def test_advanced_planning_provider_parses_model_output() -> None:
49
49
  state = await runtime.compose_state(msg)
50
50
  result = await provider.get(runtime, msg, state)
51
51
  assert result.data is not None
52
- assert result.data.get("planningRequired") is True
52
+ parsed_data = (
53
+ MessageToDict(result.data, preserving_proto_field_name=True)
54
+ if hasattr(result.data, "DESCRIPTOR")
55
+ else result.data
56
+ )
57
+ assert isinstance(parsed_data, dict)
58
+ assert parsed_data.get("planningRequired") is True
53
59
 
54
60
 
55
61
  @pytest.mark.asyncio
@@ -72,7 +78,6 @@ async def test_advanced_planning_service_creates_simple_plan() -> None:
72
78
  assert any(step.action_name == "SEND_EMAIL" for step in plan.steps)
73
79
 
74
80
 
75
- @pytest.mark.skip(reason="State.values.extra access issue with protobuf")
76
81
  @pytest.mark.asyncio
77
82
  async def test_advanced_planning_service_creates_comprehensive_plan_and_executes() -> None:
78
83
  character = Character(name="AdvPlanningSvcExec", bio=["Test"], advanced_planning=True)
@@ -124,7 +129,6 @@ async def test_advanced_planning_service_creates_comprehensive_plan_and_executes
124
129
  assert result.total_steps >= 1
125
130
 
126
131
 
127
- @pytest.mark.skip(reason="validate lambda returns bool, not coroutine")
128
132
  @pytest.mark.asyncio
129
133
  async def test_advanced_planning_dag_executes_in_dependency_order() -> None:
130
134
  character = Character(name="AdvPlanningDag", bio=["Test"], advanced_planning=True)
@@ -220,6 +224,8 @@ async def test_advanced_planning_dag_executes_in_dependency_order() -> None:
220
224
  content=Content(text="run"),
221
225
  )
222
226
  state = await runtime.compose_state(msg)
223
- await planning_service.execute_plan(plan, msg, state=state, callback=None)
227
+ execution_result = await planning_service.execute_plan(plan, msg, state=state, callback=None)
224
228
 
229
+ assert execution_result.success is True
230
+ assert execution_result.completed_steps == 3
225
231
  assert execution_order == ["STEP_A", "STEP_B", "STEP_C"]
@@ -0,0 +1,124 @@
1
+ import asyncio
2
+ import unittest
3
+ import uuid
4
+ from unittest.mock import AsyncMock, MagicMock
5
+
6
+ from elizaos.bootstrap.services.embedding import EmbeddingService
7
+ from elizaos.types import ModelType
8
+ from elizaos.types.events import EventType
9
+ from elizaos.types.memory import Memory
10
+ from elizaos.types.primitives import Content
11
+
12
+
13
+ class TestAsyncEmbedding(unittest.IsolatedAsyncioTestCase):
14
+ async def test_async_embedding_generation(self):
15
+ # Mock runtime
16
+ runtime = (
17
+ MagicMock()
18
+ ) # Don't use spec=IAgentRuntime to avoid abstract methods issues for now
19
+ runtime.agent_id = uuid.uuid4()
20
+ runtime.logger = MagicMock()
21
+
22
+ # Event handling
23
+ events = {}
24
+
25
+ def register_event(event, handler):
26
+ if event not in events:
27
+ events[event] = []
28
+ events[event].append(handler)
29
+
30
+ async def emit_event(event, payload):
31
+ handlers = events.get(event, [])
32
+ for handler in handlers:
33
+ if asyncio.iscoroutinefunction(handler):
34
+ await handler(payload)
35
+ else:
36
+ handler(payload)
37
+
38
+ async def use_model(model_type, **kwargs):
39
+ if model_type == ModelType.TEXT_SMALL:
40
+ return "intent"
41
+ if model_type == ModelType.TEXT_EMBEDDING:
42
+ return [0.1] * 384
43
+ return None
44
+
45
+ runtime.register_event = register_event
46
+ runtime.emit_event = AsyncMock(side_effect=emit_event)
47
+ runtime.use_model = AsyncMock(side_effect=use_model)
48
+
49
+ # Mock adapter
50
+ adapter = AsyncMock()
51
+ runtime.db = adapter
52
+ runtime._adapter = adapter
53
+ service = await EmbeddingService.start(runtime)
54
+
55
+ # Setup completion listener
56
+ completed_future = asyncio.Future()
57
+
58
+ async def on_completed(payload):
59
+ completed_future.set_result(payload)
60
+
61
+ runtime.register_event(
62
+ EventType.Name(EventType.EVENT_TYPE_EMBEDDING_GENERATION_COMPLETED), on_completed
63
+ )
64
+
65
+ # Create memory
66
+ memory_id = str(uuid.uuid4())
67
+ memory = Memory(
68
+ id=memory_id,
69
+ content=Content(
70
+ text="A very long message that should trigger intent generation and then embedding."
71
+ ),
72
+ room_id=str(uuid.uuid4()),
73
+ entity_id=str(uuid.uuid4()),
74
+ agent_id=str(runtime.agent_id),
75
+ )
76
+
77
+ # Emit request
78
+ # Construct proper EventPayload
79
+ # EventPayload (protobuf) expects 'extra' to be a Struct or map<string, Value>
80
+ # But for python test convenience, we can use a mock that has attributes
81
+
82
+ # Actually EventPayload 'extra' field is google.protobuf.Struct usually
83
+ # But here we just need an object with .extra attribute for our code to work
84
+ # Or we can use the actual proto class
85
+
86
+ # Let's use a simple Namespace
87
+ from types import SimpleNamespace
88
+
89
+ payload = SimpleNamespace(extra={"memory": memory})
90
+
91
+ # Manually trigger handler because emit_event in MockRuntime doesn't wait for queue processing
92
+ # In real runtime, emit_event calls handlers. embedding service handler writes to queue.
93
+ # Worker reads from queue.
94
+
95
+ event_name = EventType.Name(EventType.EVENT_TYPE_EMBEDDING_GENERATION_REQUESTED)
96
+ await runtime.emit_event(event_name, payload)
97
+
98
+ # Wait for completion
99
+ try:
100
+ result = await asyncio.wait_for(completed_future, timeout=5.0)
101
+ self.assertEqual(result["memory_id"], str(memory_id))
102
+ except TimeoutError:
103
+ print("TIMEOUT! Logging errors:")
104
+ for call in runtime.logger.error.call_args_list:
105
+ print(call)
106
+ for call in runtime.logger.warning.call_args_list:
107
+ print(call)
108
+ self.fail("Embedding generation timed out")
109
+
110
+ # Verify db update
111
+ runtime._adapter.update_memory.assert_called_once()
112
+ call_args = runtime._adapter.update_memory.call_args
113
+ updated_memory = call_args[0][0]
114
+ self.assertIsNotNone(updated_memory.embedding)
115
+ self.assertEqual(len(updated_memory.embedding), 384)
116
+ # Check intent (metadata update)
117
+ # Verify in custom metadata
118
+ self.assertEqual(updated_memory.metadata.custom.custom_data["intent"], "intent")
119
+
120
+ await service.stop()
121
+
122
+
123
+ if __name__ == "__main__":
124
+ unittest.main()
@@ -1,5 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
+ import time
3
4
  from unittest.mock import AsyncMock, MagicMock
4
5
 
5
6
  import pytest
@@ -11,9 +12,10 @@ from elizaos.bootstrap.autonomy import (
11
12
  autonomy_status_provider,
12
13
  send_to_admin_action,
13
14
  )
15
+ from elizaos.bootstrap.autonomy.service import AUTONOMY_TASK_TAGS
14
16
  from elizaos.bootstrap.autonomy.types import AutonomyStatus
15
17
  from elizaos.types.memory import Memory
16
- from elizaos.types.primitives import Content, as_uuid
18
+ from elizaos.types.primitives import Content, as_uuid, string_to_uuid
17
19
 
18
20
  TEST_AGENT_ID = "00000000-0000-0000-0000-000000000001"
19
21
  TEST_ROOM_ID = "00000000-0000-0000-0000-000000000002"
@@ -75,6 +77,9 @@ class TestAutonomyService:
75
77
  assert AutonomyService.service_type == AUTONOMY_SERVICE_TYPE
76
78
  assert AutonomyService.service_type == "AUTONOMY"
77
79
 
80
+ def test_task_tags_match_typescript(self):
81
+ assert AUTONOMY_TASK_TAGS == ["queue", "repeat", "autonomy"]
82
+
78
83
  @pytest.mark.asyncio
79
84
  async def test_start_creates_service(self, test_runtime):
80
85
  service = await AutonomyService.start(test_runtime)
@@ -85,6 +90,12 @@ class TestAutonomyService:
85
90
  assert service.get_loop_interval() == 30000
86
91
  assert service.get_autonomous_room_id() is not None
87
92
 
93
+ @pytest.mark.asyncio
94
+ async def test_autonomous_room_id_is_deterministic(self, test_runtime):
95
+ service = await AutonomyService.start(test_runtime)
96
+ expected_room_id = as_uuid(string_to_uuid(f"autonomy-room-{test_runtime.agent_id}"))
97
+ assert service.get_autonomous_room_id() == expected_room_id
98
+
88
99
  @pytest.mark.asyncio
89
100
  async def test_auto_start_when_enabled(self, test_runtime):
90
101
  test_runtime.enable_autonomy = True
@@ -172,13 +183,14 @@ class TestAutonomyService:
172
183
  test_runtime.get_setting = MagicMock(return_value=str(target_room_id))
173
184
 
174
185
  dup_id = as_uuid(TEST_MESSAGE_ID)
186
+ now_ms = int(time.time() * 1000)
175
187
  older = Memory(
176
188
  id=dup_id,
177
189
  room_id=target_room_id,
178
190
  entity_id=as_uuid(TEST_ENTITY_ID),
179
191
  agent_id=as_uuid(TEST_AGENT_ID),
180
192
  content=Content(text="old"),
181
- created_at=10,
193
+ created_at=now_ms - 60_000, # 1 minute ago (within 1-hour window)
182
194
  )
183
195
  newer = Memory(
184
196
  id=dup_id,
@@ -186,7 +198,7 @@ class TestAutonomyService:
186
198
  entity_id=as_uuid(TEST_ENTITY_ID),
187
199
  agent_id=as_uuid(TEST_AGENT_ID),
188
200
  content=Content(text="new"),
189
- created_at=20,
201
+ created_at=now_ms - 30_000, # 30 seconds ago (within 1-hour window)
190
202
  )
191
203
 
192
204
  async def get_memories(params):
@@ -435,11 +447,20 @@ class TestAutonomyIntegration:
435
447
  AutonomyService,
436
448
  admin_chat_provider,
437
449
  autonomy_status_provider,
450
+ disable_autonomy_action,
451
+ enable_autonomy_action,
452
+ post_action_evaluator,
438
453
  send_to_admin_action,
439
454
  )
440
455
 
441
456
  assert AutonomyService is not None
442
457
  assert AUTONOMY_SERVICE_TYPE == "AUTONOMY"
443
458
  assert send_to_admin_action is not None
459
+ assert enable_autonomy_action is not None
460
+ assert enable_autonomy_action.name == "ENABLE_AUTONOMY"
461
+ assert disable_autonomy_action is not None
462
+ assert disable_autonomy_action.name == "DISABLE_AUTONOMY"
463
+ assert post_action_evaluator is not None
464
+ assert post_action_evaluator.name == "POST_ACTION_EVALUATOR"
444
465
  assert admin_chat_provider is not None
445
466
  assert autonomy_status_provider is not None
@@ -0,0 +1,104 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+ from types import SimpleNamespace
5
+ from unittest.mock import AsyncMock
6
+
7
+ import pytest
8
+
9
+ from elizaos.advanced_memory.actions.reset_session import (
10
+ reset_session_action as advanced_reset_session_action,
11
+ )
12
+ from elizaos.basic_capabilities.providers.recent_messages import (
13
+ recent_messages_provider as basic_recent_messages_provider,
14
+ )
15
+ from elizaos.bootstrap.actions.reset_session import (
16
+ reset_session_action as bootstrap_reset_session_action,
17
+ )
18
+ from elizaos.bootstrap.providers.recent_messages import (
19
+ recent_messages_provider as bootstrap_recent_messages_provider,
20
+ )
21
+ from elizaos.types import Content, Memory, as_uuid
22
+
23
+
24
+ @dataclass
25
+ class FakeRoom:
26
+ id: object
27
+ world_id: object | None
28
+ metadata: dict[str, object]
29
+
30
+
31
+ class FakeRuntime:
32
+ def __init__(self, room: FakeRoom, *, role: str = "OWNER") -> None:
33
+ self.room = room
34
+ self.updated_room: FakeRoom | None = None
35
+ self.get_room = AsyncMock(return_value=room)
36
+ self.update_room = AsyncMock(side_effect=self._update_room)
37
+ self.get_memories = AsyncMock(return_value=[])
38
+ self.get_entity = AsyncMock(return_value=SimpleNamespace(name="User"))
39
+ self.agent_id = as_uuid("91000000-0000-0000-0000-000000000001")
40
+ self.character = SimpleNamespace(name="CompactionAgent")
41
+ self.get_world = AsyncMock(
42
+ return_value=SimpleNamespace(
43
+ metadata={"roles": {"91000000-0000-0000-0000-000000000002": role}}
44
+ )
45
+ )
46
+
47
+ async def _update_room(self, room: FakeRoom) -> None:
48
+ self.updated_room = room
49
+ self.room = room
50
+
51
+
52
+ def _message() -> Memory:
53
+ return Memory(
54
+ id=as_uuid("91000000-0000-0000-0000-000000000010"),
55
+ entity_id=as_uuid("91000000-0000-0000-0000-000000000002"),
56
+ room_id=as_uuid("91000000-0000-0000-0000-000000000003"),
57
+ content=Content(text="start over", source="test"),
58
+ )
59
+
60
+
61
+ @pytest.mark.asyncio
62
+ @pytest.mark.parametrize(
63
+ "action_under_test",
64
+ [bootstrap_reset_session_action, advanced_reset_session_action],
65
+ )
66
+ async def test_reset_session_action_updates_room_metadata(action_under_test: object) -> None:
67
+ room = FakeRoom(
68
+ id=as_uuid("91000000-0000-0000-0000-000000000003"),
69
+ world_id=as_uuid("91000000-0000-0000-0000-000000000004"),
70
+ metadata={
71
+ "lastCompactionAt": 1000,
72
+ "compactionHistory": [{"timestamp": 1000, "triggeredBy": "old", "reason": "manual"}],
73
+ },
74
+ )
75
+ runtime = FakeRuntime(room)
76
+
77
+ result = await action_under_test.handler(runtime, _message(), None, None, None, None)
78
+
79
+ assert result.success is True
80
+ assert runtime.updated_room is not None
81
+ assert runtime.updated_room.metadata["lastCompactionAt"] >= 1000
82
+ assert len(runtime.updated_room.metadata["compactionHistory"]) == 2
83
+ assert result.values["previousCompactionAt"] == 1000
84
+
85
+
86
+ @pytest.mark.asyncio
87
+ @pytest.mark.parametrize(
88
+ "provider_under_test",
89
+ [bootstrap_recent_messages_provider, basic_recent_messages_provider],
90
+ )
91
+ async def test_recent_messages_provider_uses_last_compaction_boundary(
92
+ provider_under_test: object,
93
+ ) -> None:
94
+ room = FakeRoom(
95
+ id=as_uuid("91000000-0000-0000-0000-000000000003"),
96
+ world_id=None,
97
+ metadata={"lastCompactionAt": 4242},
98
+ )
99
+ runtime = FakeRuntime(room)
100
+
101
+ result = await provider_under_test.get(runtime, _message(), None)
102
+
103
+ assert result.values["roomId"] == str(_message().room_id)
104
+ assert runtime.get_memories.await_args.kwargs["start"] == 4242
@@ -0,0 +1,115 @@
1
+ from __future__ import annotations
2
+
3
+ import uuid
4
+ from types import SimpleNamespace
5
+ from unittest.mock import AsyncMock, MagicMock
6
+
7
+ import pytest
8
+
9
+ from elizaos.advanced_memory.memory_service import MemoryService
10
+ from elizaos.advanced_memory.types import LongTermMemoryCategory
11
+ from elizaos.basic_capabilities.services.embedding import (
12
+ EmbeddingService as BasicEmbeddingService,
13
+ )
14
+ from elizaos.bootstrap.services.embedding import (
15
+ EmbeddingService as BootstrapEmbeddingService,
16
+ )
17
+ from elizaos.types import ModelType
18
+
19
+
20
+ def _mock_runtime() -> MagicMock:
21
+ runtime = MagicMock()
22
+ runtime.agent_id = uuid.uuid4()
23
+ runtime.logger = MagicMock()
24
+
25
+ async def use_model(model_type, **_kwargs):
26
+ if model_type == ModelType.TEXT_EMBEDDING:
27
+ return [0.1, 0.2, 0.3]
28
+ return "ok"
29
+
30
+ runtime.use_model = AsyncMock(side_effect=use_model)
31
+ return runtime
32
+
33
+
34
+ @pytest.mark.asyncio
35
+ async def test_embedding_services_use_lru_eviction() -> None:
36
+ for service_cls in (BasicEmbeddingService, BootstrapEmbeddingService):
37
+ runtime = _mock_runtime()
38
+ service = service_cls()
39
+ service._runtime = runtime
40
+ service.set_max_cache_size(2)
41
+
42
+ await service.embed("a")
43
+ await service.embed("b")
44
+ await service.embed("a")
45
+ await service.embed("c")
46
+
47
+ assert list(service._cache.keys()) == ["a", "c"]
48
+
49
+
50
+ @pytest.mark.asyncio
51
+ async def test_bootstrap_embedding_queue_deduplicates_and_clears_on_stop() -> None:
52
+ service = BootstrapEmbeddingService()
53
+ service._runtime = _mock_runtime()
54
+
55
+ payload = SimpleNamespace(extra={"memory": {"id": "memory-1"}})
56
+
57
+ await service._handle_embedding_request(payload)
58
+ await service._handle_embedding_request(payload)
59
+
60
+ assert service._queue.qsize() == 1
61
+ assert service._pending_payload_keys == {"memory-1"}
62
+
63
+ await service.stop()
64
+
65
+ assert service._queue.qsize() == 0
66
+ assert service._pending_payload_keys == set()
67
+
68
+
69
+ @pytest.mark.asyncio
70
+ async def test_advanced_memory_fallback_storage_is_bounded() -> None:
71
+ service = MemoryService(runtime=None)
72
+ agent_id = uuid.uuid4()
73
+
74
+ for index in range(service._MAX_LOCAL_SESSION_SUMMARIES + 25):
75
+ await service.store_session_summary(
76
+ agent_id=agent_id,
77
+ room_id=uuid.uuid4(),
78
+ summary=f"summary-{index}",
79
+ message_count=index,
80
+ last_message_offset=index,
81
+ )
82
+
83
+ assert len(service._session_summaries) == service._MAX_LOCAL_SESSION_SUMMARIES
84
+
85
+ for index in range(service._MAX_LOCAL_EXTRACTION_CHECKPOINTS + 25):
86
+ await service.set_last_extraction_checkpoint(uuid.uuid4(), uuid.uuid4(), index)
87
+
88
+ assert len(service._extraction_checkpoints) == service._MAX_LOCAL_EXTRACTION_CHECKPOINTS
89
+
90
+ for _ in range(service._MAX_LOCAL_LONG_TERM_ENTITIES + 25):
91
+ entity_id = uuid.uuid4()
92
+ await service.store_long_term_memory(
93
+ agent_id=agent_id,
94
+ entity_id=entity_id,
95
+ category=LongTermMemoryCategory.SEMANTIC,
96
+ content=f"entity-{entity_id}",
97
+ confidence=0.9,
98
+ )
99
+
100
+ assert len(service._long_term) == service._MAX_LOCAL_LONG_TERM_ENTITIES
101
+
102
+ hot_entity_id = uuid.uuid4()
103
+ for index in range(service._MAX_LOCAL_LONG_TERM_PER_ENTITY + 25):
104
+ await service.store_long_term_memory(
105
+ agent_id=agent_id,
106
+ entity_id=hot_entity_id,
107
+ category=LongTermMemoryCategory.SEMANTIC,
108
+ content=f"fact-{index}",
109
+ confidence=float(index),
110
+ )
111
+
112
+ retained = service._long_term[str(hot_entity_id)]
113
+ assert len(retained) == service._MAX_LOCAL_LONG_TERM_PER_ENTITY
114
+ assert retained[0].content == "fact-25"
115
+ assert retained[-1].content == f"fact-{service._MAX_LOCAL_LONG_TERM_PER_ENTITY + 24}"
@@ -59,17 +59,15 @@ class TestAgentRuntimeSettings:
59
59
  runtime.set_setting("test_key", "test_value")
60
60
  assert runtime.get_setting("test_key") == "test_value"
61
61
 
62
- @pytest.mark.skip(reason="CharacterSettings proto doesn't support arbitrary fields")
63
62
  def test_get_setting_from_character(self) -> None:
64
63
  character = Character(
65
64
  name="Test",
66
65
  bio=["Test"],
67
- settings={"char_setting": "char_value"},
66
+ settings={"extra": {"char_setting": "char_value"}},
68
67
  )
69
68
  runtime = AgentRuntime(character=character)
70
69
  assert runtime.get_setting("char_setting") == "char_value"
71
70
 
72
- @pytest.mark.skip(reason="Runtime get_setting from secrets not yet implemented")
73
71
  def test_get_setting_from_secrets(self) -> None:
74
72
  character = Character(
75
73
  name="Test",
@@ -305,42 +303,38 @@ class TestAgentRuntimeLLMMode:
305
303
  runtime = AgentRuntime(character=character, llm_mode=LLMMode.LARGE)
306
304
  assert runtime.get_llm_mode() == LLMMode.LARGE
307
305
 
308
- @pytest.mark.skip(reason="CharacterSettings proto doesn't have LLM_MODE field")
309
306
  def test_character_setting_small(self) -> None:
310
307
  character = Character(
311
308
  name="Test",
312
309
  bio=["Test"],
313
- settings={"LLM_MODE": "SMALL"},
310
+ settings={"extra": {"LLM_MODE": "SMALL"}},
314
311
  )
315
312
  runtime = AgentRuntime(character=character)
316
313
  assert runtime.get_llm_mode() == LLMMode.SMALL
317
314
 
318
- @pytest.mark.skip(reason="CharacterSettings proto doesn't have LLM_MODE field")
319
315
  def test_constructor_option_takes_precedence(self) -> None:
320
316
  character = Character(
321
317
  name="Test",
322
318
  bio=["Test"],
323
- settings={"LLM_MODE": "SMALL"},
319
+ settings={"extra": {"LLM_MODE": "SMALL"}},
324
320
  )
325
321
  runtime = AgentRuntime(character=character, llm_mode=LLMMode.LARGE)
326
322
  assert runtime.get_llm_mode() == LLMMode.LARGE
327
323
 
328
- @pytest.mark.skip(reason="CharacterSettings proto doesn't have LLM_MODE field")
329
324
  def test_case_insensitive_character_setting(self) -> None:
330
325
  character = Character(
331
326
  name="Test",
332
327
  bio=["Test"],
333
- settings={"LLM_MODE": "small"},
328
+ settings={"extra": {"LLM_MODE": "small"}},
334
329
  )
335
330
  runtime = AgentRuntime(character=character)
336
331
  assert runtime.get_llm_mode() == LLMMode.SMALL
337
332
 
338
- @pytest.mark.skip(reason="CharacterSettings proto doesn't have LLM_MODE field")
339
333
  def test_invalid_setting_defaults_to_default(self) -> None:
340
334
  character = Character(
341
335
  name="Test",
342
336
  bio=["Test"],
343
- settings={"LLM_MODE": "invalid"},
337
+ settings={"extra": {"LLM_MODE": "invalid"}},
344
338
  )
345
339
  runtime = AgentRuntime(character=character)
346
340
  assert runtime.get_llm_mode() == LLMMode.DEFAULT
@@ -391,32 +385,29 @@ class TestAgentRuntimeCheckShouldRespond:
391
385
  runtime = AgentRuntime(character=character, check_should_respond=True)
392
386
  assert runtime.is_check_should_respond_enabled() is True
393
387
 
394
- @pytest.mark.skip(reason="CharacterSettings proto doesn't have CHECK_SHOULD_RESPOND field")
395
388
  def test_character_setting_false(self) -> None:
396
389
  character = Character(
397
390
  name="Test",
398
391
  bio=["Test"],
399
- settings={"CHECK_SHOULD_RESPOND": "false"},
392
+ settings={"extra": {"CHECK_SHOULD_RESPOND": "false"}},
400
393
  )
401
394
  runtime = AgentRuntime(character=character)
402
395
  assert runtime.is_check_should_respond_enabled() is False
403
396
 
404
- @pytest.mark.skip(reason="CharacterSettings proto doesn't have CHECK_SHOULD_RESPOND field")
405
397
  def test_constructor_option_takes_precedence(self) -> None:
406
398
  character = Character(
407
399
  name="Test",
408
400
  bio=["Test"],
409
- settings={"CHECK_SHOULD_RESPOND": "false"},
401
+ settings={"extra": {"CHECK_SHOULD_RESPOND": "false"}},
410
402
  )
411
403
  runtime = AgentRuntime(character=character, check_should_respond=True)
412
404
  assert runtime.is_check_should_respond_enabled() is True
413
405
 
414
- @pytest.mark.skip(reason="CharacterSettings proto doesn't have CHECK_SHOULD_RESPOND field")
415
406
  def test_non_false_string_defaults_to_true(self) -> None:
416
407
  character = Character(
417
408
  name="Test",
418
409
  bio=["Test"],
419
- settings={"CHECK_SHOULD_RESPOND": "yes"},
410
+ settings={"extra": {"CHECK_SHOULD_RESPOND": "yes"}},
420
411
  )
421
412
  runtime = AgentRuntime(character=character)
422
413
  assert runtime.is_check_should_respond_enabled() is True