@elizaos/python 2.0.0-alpha.10 → 2.0.0-alpha.26
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.
- package/elizaos/__init__.py +0 -1
- package/elizaos/advanced_capabilities/__init__.py +6 -41
- package/elizaos/advanced_capabilities/actions/__init__.py +1 -21
- package/elizaos/advanced_capabilities/actions/add_contact.py +21 -11
- package/elizaos/advanced_capabilities/actions/follow_room.py +28 -28
- package/elizaos/advanced_capabilities/actions/image_generation.py +13 -26
- package/elizaos/advanced_capabilities/actions/mute_room.py +13 -26
- package/elizaos/advanced_capabilities/actions/remove_contact.py +16 -2
- package/elizaos/advanced_capabilities/actions/roles.py +13 -27
- package/elizaos/advanced_capabilities/actions/schedule_follow_up.py +70 -15
- package/elizaos/advanced_capabilities/actions/search_contacts.py +17 -3
- package/elizaos/advanced_capabilities/actions/send_message.py +183 -50
- package/elizaos/advanced_capabilities/actions/settings.py +16 -2
- package/elizaos/advanced_capabilities/actions/unfollow_room.py +13 -26
- package/elizaos/advanced_capabilities/actions/unmute_room.py +13 -26
- package/elizaos/advanced_capabilities/actions/update_contact.py +16 -2
- package/elizaos/advanced_capabilities/actions/update_entity.py +16 -2
- package/elizaos/advanced_capabilities/evaluators/__init__.py +2 -9
- package/elizaos/advanced_capabilities/evaluators/reflection.py +3 -132
- package/elizaos/advanced_capabilities/evaluators/relationship_extraction.py +5 -201
- package/elizaos/advanced_capabilities/providers/__init__.py +1 -12
- package/elizaos/advanced_capabilities/providers/knowledge.py +24 -3
- package/elizaos/advanced_capabilities/services/__init__.py +2 -9
- package/elizaos/advanced_memory/actions/reset_session.py +11 -0
- package/elizaos/advanced_memory/evaluators/reflection.py +134 -0
- package/elizaos/advanced_memory/evaluators/relationship_extraction.py +203 -0
- package/elizaos/advanced_memory/memory_service.py +15 -17
- package/elizaos/advanced_memory/test_advanced_memory.py +357 -0
- package/elizaos/advanced_planning/actions/schedule_follow_up.py +222 -0
- package/elizaos/advanced_planning/planning_service.py +26 -14
- package/elizaos/basic_capabilities/__init__.py +0 -2
- package/elizaos/basic_capabilities/providers/__init__.py +0 -3
- package/elizaos/basic_capabilities/providers/actions.py +118 -29
- package/elizaos/basic_capabilities/providers/agent_settings.py +64 -0
- package/elizaos/basic_capabilities/providers/character.py +19 -21
- package/elizaos/basic_capabilities/providers/contacts.py +79 -0
- package/elizaos/basic_capabilities/providers/current_time.py +7 -4
- package/elizaos/basic_capabilities/providers/facts.py +87 -0
- package/elizaos/basic_capabilities/providers/follow_ups.py +117 -0
- package/elizaos/basic_capabilities/providers/knowledge.py +97 -0
- package/elizaos/basic_capabilities/providers/relationships.py +107 -0
- package/elizaos/basic_capabilities/providers/roles.py +96 -0
- package/elizaos/basic_capabilities/providers/settings.py +56 -0
- package/elizaos/basic_capabilities/providers/time.py +7 -4
- package/elizaos/bootstrap/__init__.py +21 -2
- package/elizaos/bootstrap/actions/schedule_follow_up.py +65 -7
- package/elizaos/bootstrap/actions/send_message.py +162 -15
- package/elizaos/bootstrap/autonomy/__init__.py +5 -1
- package/elizaos/bootstrap/autonomy/action.py +161 -0
- package/elizaos/bootstrap/autonomy/evaluators.py +217 -0
- package/elizaos/bootstrap/autonomy/service.py +238 -28
- package/elizaos/bootstrap/plugin.py +7 -0
- package/elizaos/bootstrap/providers/actions.py +118 -27
- package/elizaos/bootstrap/providers/agent_settings.py +1 -0
- package/elizaos/bootstrap/providers/attachments.py +1 -0
- package/elizaos/bootstrap/providers/capabilities.py +1 -0
- package/elizaos/bootstrap/providers/character.py +1 -0
- package/elizaos/bootstrap/providers/choice.py +1 -0
- package/elizaos/bootstrap/providers/contacts.py +1 -0
- package/elizaos/bootstrap/providers/current_time.py +8 -2
- package/elizaos/bootstrap/providers/entities.py +1 -0
- package/elizaos/bootstrap/providers/evaluators.py +1 -0
- package/elizaos/bootstrap/providers/facts.py +1 -0
- package/elizaos/bootstrap/providers/follow_ups.py +1 -0
- package/elizaos/bootstrap/providers/knowledge.py +27 -3
- package/elizaos/bootstrap/providers/providers_list.py +1 -0
- package/elizaos/bootstrap/providers/relationships.py +1 -0
- package/elizaos/bootstrap/providers/roles.py +1 -0
- package/elizaos/bootstrap/providers/settings.py +1 -0
- package/elizaos/bootstrap/providers/time.py +8 -4
- package/elizaos/bootstrap/providers/world.py +1 -0
- package/elizaos/bootstrap/services/embedding.py +156 -1
- package/elizaos/deterministic.py +193 -0
- package/elizaos/generated/__init__.py +1 -0
- package/elizaos/generated/action_docs.py +3181 -0
- package/elizaos/generated/spec_helpers.py +175 -0
- package/elizaos/media/mime.py +2 -2
- package/elizaos/media/search.py +23 -23
- package/elizaos/runtime.py +215 -57
- package/elizaos/services/message_service.py +175 -29
- package/elizaos/types/components.py +2 -2
- package/elizaos/types/generated/__init__.py +12 -0
- package/elizaos/types/generated/eliza/v1/agent_pb2.py +63 -0
- package/elizaos/types/generated/eliza/v1/agent_pb2.pyi +159 -0
- package/elizaos/types/generated/eliza/v1/components_pb2.py +65 -0
- package/elizaos/types/generated/eliza/v1/components_pb2.pyi +160 -0
- package/elizaos/types/generated/eliza/v1/database_pb2.py +78 -0
- package/elizaos/types/generated/eliza/v1/database_pb2.pyi +305 -0
- package/elizaos/types/generated/eliza/v1/environment_pb2.py +58 -0
- package/elizaos/types/generated/eliza/v1/environment_pb2.pyi +135 -0
- package/elizaos/types/generated/eliza/v1/events_pb2.py +82 -0
- package/elizaos/types/generated/eliza/v1/events_pb2.pyi +322 -0
- package/elizaos/types/generated/eliza/v1/ipc_pb2.py +113 -0
- package/elizaos/types/generated/eliza/v1/ipc_pb2.pyi +367 -0
- package/elizaos/types/generated/eliza/v1/knowledge_pb2.py +41 -0
- package/elizaos/types/generated/eliza/v1/knowledge_pb2.pyi +26 -0
- package/elizaos/types/generated/eliza/v1/memory_pb2.py +55 -0
- package/elizaos/types/generated/eliza/v1/memory_pb2.pyi +111 -0
- package/elizaos/types/generated/eliza/v1/message_service_pb2.py +48 -0
- package/elizaos/types/generated/eliza/v1/message_service_pb2.pyi +69 -0
- package/elizaos/types/generated/eliza/v1/messaging_pb2.py +51 -0
- package/elizaos/types/generated/eliza/v1/messaging_pb2.pyi +97 -0
- package/elizaos/types/generated/eliza/v1/model_pb2.py +84 -0
- package/elizaos/types/generated/eliza/v1/model_pb2.pyi +280 -0
- package/elizaos/types/generated/eliza/v1/payment_pb2.py +44 -0
- package/elizaos/types/generated/eliza/v1/payment_pb2.pyi +70 -0
- package/elizaos/types/generated/eliza/v1/plugin_pb2.py +68 -0
- package/elizaos/types/generated/eliza/v1/plugin_pb2.pyi +145 -0
- package/elizaos/types/generated/eliza/v1/primitives_pb2.py +48 -0
- package/elizaos/types/generated/eliza/v1/primitives_pb2.pyi +92 -0
- package/elizaos/types/generated/eliza/v1/prompts_pb2.py +52 -0
- package/elizaos/types/generated/eliza/v1/prompts_pb2.pyi +74 -0
- package/elizaos/types/generated/eliza/v1/service_interfaces_pb2.py +211 -0
- package/elizaos/types/generated/eliza/v1/service_interfaces_pb2.pyi +1296 -0
- package/elizaos/types/generated/eliza/v1/service_pb2.py +42 -0
- package/elizaos/types/generated/eliza/v1/service_pb2.pyi +69 -0
- package/elizaos/types/generated/eliza/v1/settings_pb2.py +58 -0
- package/elizaos/types/generated/eliza/v1/settings_pb2.pyi +85 -0
- package/elizaos/types/generated/eliza/v1/state_pb2.py +60 -0
- package/elizaos/types/generated/eliza/v1/state_pb2.pyi +114 -0
- package/elizaos/types/generated/eliza/v1/task_pb2.py +42 -0
- package/elizaos/types/generated/eliza/v1/task_pb2.pyi +58 -0
- package/elizaos/types/generated/eliza/v1/tee_pb2.py +52 -0
- package/elizaos/types/generated/eliza/v1/tee_pb2.pyi +90 -0
- package/elizaos/types/generated/eliza/v1/testing_pb2.py +39 -0
- package/elizaos/types/generated/eliza/v1/testing_pb2.pyi +23 -0
- package/elizaos/types/model.py +30 -0
- package/elizaos/types/runtime.py +6 -2
- package/elizaos/utils/validation.py +76 -0
- package/package.json +3 -2
- package/tests/test_action_parameters.py +2 -3
- package/tests/test_actions_provider_examples.py +58 -1
- package/tests/test_advanced_memory_behavior.py +0 -2
- package/tests/test_advanced_memory_flag.py +0 -2
- package/tests/test_advanced_planning_behavior.py +11 -5
- package/tests/test_async_embedding.py +124 -0
- package/tests/test_autonomy.py +24 -3
- package/tests/test_runtime.py +8 -17
- package/tests/test_schedule_follow_up_action.py +260 -0
- package/tests/test_send_message_action_targets.py +114 -0
- package/tests/test_settings_crypto.py +0 -2
- package/tests/test_validation.py +141 -0
- package/tests/verify_memory_architecture.py +192 -0
- package/uv.lock +1565 -0
- package/elizaos/basic_capabilities/providers/capabilities.py +0 -62
|
@@ -2,12 +2,12 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import contextlib
|
|
4
4
|
from dataclasses import dataclass, field
|
|
5
|
-
from typing import TYPE_CHECKING
|
|
6
|
-
from uuid import UUID
|
|
5
|
+
from typing import TYPE_CHECKING, Any
|
|
7
6
|
|
|
8
7
|
from elizaos.generated.spec_helpers import require_action_spec
|
|
9
8
|
from elizaos.types import Action, ActionExample, ActionResult, Content
|
|
10
9
|
from elizaos.types.memory import Memory as MemoryType
|
|
10
|
+
from elizaos.types.primitives import UUID, as_uuid
|
|
11
11
|
|
|
12
12
|
if TYPE_CHECKING:
|
|
13
13
|
from elizaos.types import HandlerCallback, HandlerOptions, IAgentRuntime, Memory, State
|
|
@@ -36,6 +36,54 @@ def _convert_spec_examples() -> list[list[ActionExample]]:
|
|
|
36
36
|
return []
|
|
37
37
|
|
|
38
38
|
|
|
39
|
+
def _parse_uuid(value: object) -> UUID | None:
|
|
40
|
+
if not isinstance(value, str) or not value.strip():
|
|
41
|
+
return None
|
|
42
|
+
with contextlib.suppress(Exception):
|
|
43
|
+
return as_uuid(value.strip())
|
|
44
|
+
return None
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _normalize_parameters(options: HandlerOptions | None) -> dict[str, Any]:
|
|
48
|
+
raw = getattr(options, "parameters", None)
|
|
49
|
+
if isinstance(raw, dict):
|
|
50
|
+
return raw
|
|
51
|
+
|
|
52
|
+
if raw is None:
|
|
53
|
+
return {}
|
|
54
|
+
|
|
55
|
+
if hasattr(raw, "items"):
|
|
56
|
+
try:
|
|
57
|
+
return {str(k): v for k, v in raw.items()}
|
|
58
|
+
except Exception:
|
|
59
|
+
return {}
|
|
60
|
+
|
|
61
|
+
return {}
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def _coerce_entity_name(entity: object) -> list[str]:
|
|
65
|
+
if isinstance(entity, dict):
|
|
66
|
+
names = entity.get("names")
|
|
67
|
+
if isinstance(names, list):
|
|
68
|
+
return [str(n).strip() for n in names if isinstance(n, str) and n.strip()]
|
|
69
|
+
name = entity.get("name")
|
|
70
|
+
if isinstance(name, str) and name.strip():
|
|
71
|
+
return [name.strip()]
|
|
72
|
+
return []
|
|
73
|
+
|
|
74
|
+
names = getattr(entity, "names", None)
|
|
75
|
+
if isinstance(names, list):
|
|
76
|
+
clean = [str(n).strip() for n in names if isinstance(n, str) and str(n).strip()]
|
|
77
|
+
if clean:
|
|
78
|
+
return clean
|
|
79
|
+
|
|
80
|
+
name = getattr(entity, "name", None)
|
|
81
|
+
if isinstance(name, str) and name.strip():
|
|
82
|
+
return [name.strip()]
|
|
83
|
+
|
|
84
|
+
return []
|
|
85
|
+
|
|
86
|
+
|
|
39
87
|
@dataclass
|
|
40
88
|
class SendMessageAction:
|
|
41
89
|
name: str = _spec["name"]
|
|
@@ -58,9 +106,14 @@ class SendMessageAction:
|
|
|
58
106
|
callback: HandlerCallback | None = None,
|
|
59
107
|
responses: list[Memory] | None = None,
|
|
60
108
|
) -> ActionResult:
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
109
|
+
params = _normalize_parameters(options)
|
|
110
|
+
|
|
111
|
+
text_param = params.get("text")
|
|
112
|
+
message_text = str(text_param).strip() if isinstance(text_param, str) else ""
|
|
113
|
+
if not message_text and responses and responses[0].content:
|
|
114
|
+
message_text = str(responses[0].content.text or "").strip()
|
|
115
|
+
if not message_text and message.content and isinstance(message.content.text, str):
|
|
116
|
+
message_text = message.content.text.strip()
|
|
64
117
|
|
|
65
118
|
if not message_text:
|
|
66
119
|
return ActionResult(
|
|
@@ -72,18 +125,91 @@ class SendMessageAction:
|
|
|
72
125
|
|
|
73
126
|
target_room_id = message.room_id
|
|
74
127
|
target_entity_id: UUID | None = None
|
|
128
|
+
target_type = "room"
|
|
129
|
+
|
|
130
|
+
target_type_param = params.get("targetType") or params.get("target_type")
|
|
131
|
+
target_param = params.get("target")
|
|
132
|
+
source_param = params.get("source")
|
|
133
|
+
|
|
134
|
+
source = (
|
|
135
|
+
source_param.strip()
|
|
136
|
+
if isinstance(source_param, str) and source_param.strip()
|
|
137
|
+
else (
|
|
138
|
+
message.content.source
|
|
139
|
+
if message.content and isinstance(message.content.source, str)
|
|
140
|
+
else "agent"
|
|
141
|
+
)
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
if isinstance(target_type_param, str):
|
|
145
|
+
normalized_target_type = target_type_param.strip().lower()
|
|
146
|
+
if normalized_target_type in {"user", "entity"}:
|
|
147
|
+
target_type = "user"
|
|
148
|
+
elif normalized_target_type == "room":
|
|
149
|
+
target_type = "room"
|
|
150
|
+
|
|
151
|
+
if isinstance(target_param, str) and target_param.strip():
|
|
152
|
+
target_value = target_param.strip()
|
|
153
|
+
if target_type == "room":
|
|
154
|
+
parsed_room = _parse_uuid(target_value)
|
|
155
|
+
if parsed_room:
|
|
156
|
+
target_room_id = parsed_room
|
|
157
|
+
else:
|
|
158
|
+
world_id = None
|
|
159
|
+
room_data = (
|
|
160
|
+
getattr(getattr(state, "data", None), "room", None) if state else None
|
|
161
|
+
)
|
|
162
|
+
if room_data is not None:
|
|
163
|
+
world_id = getattr(room_data, "world_id", None) or getattr(
|
|
164
|
+
room_data, "worldId", None
|
|
165
|
+
)
|
|
166
|
+
if world_id is None:
|
|
167
|
+
with contextlib.suppress(Exception):
|
|
168
|
+
current_room = await runtime.get_room(message.room_id)
|
|
169
|
+
if current_room:
|
|
170
|
+
world_id = getattr(current_room, "world_id", None) or getattr(
|
|
171
|
+
current_room, "worldId", None
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
if world_id is not None:
|
|
175
|
+
with contextlib.suppress(Exception):
|
|
176
|
+
rooms = await runtime.get_rooms(world_id)
|
|
177
|
+
for room in rooms:
|
|
178
|
+
room_name = getattr(room, "name", None)
|
|
179
|
+
if (
|
|
180
|
+
isinstance(room_name, str)
|
|
181
|
+
and room_name.strip().lower() == target_value.lower()
|
|
182
|
+
):
|
|
183
|
+
room_id = getattr(room, "id", None)
|
|
184
|
+
if room_id is not None:
|
|
185
|
+
target_room_id = as_uuid(str(room_id))
|
|
186
|
+
break
|
|
187
|
+
else:
|
|
188
|
+
parsed_entity = _parse_uuid(target_value)
|
|
189
|
+
if parsed_entity:
|
|
190
|
+
target_entity_id = parsed_entity
|
|
191
|
+
else:
|
|
192
|
+
with contextlib.suppress(Exception):
|
|
193
|
+
entities = await runtime.get_entities_for_room(message.room_id)
|
|
194
|
+
for entity in entities:
|
|
195
|
+
names = _coerce_entity_name(entity)
|
|
196
|
+
if any(name.lower() == target_value.lower() for name in names):
|
|
197
|
+
entity_id = getattr(entity, "id", None)
|
|
198
|
+
if entity_id is not None:
|
|
199
|
+
target_entity_id = as_uuid(str(entity_id))
|
|
200
|
+
break
|
|
75
201
|
|
|
76
202
|
if message.content and message.content.target:
|
|
77
203
|
target = message.content.target
|
|
78
204
|
if isinstance(target, dict):
|
|
79
205
|
room_str = target.get("roomId")
|
|
80
206
|
entity_str = target.get("entityId")
|
|
81
|
-
if room_str:
|
|
82
|
-
with contextlib.suppress(
|
|
83
|
-
target_room_id =
|
|
84
|
-
if entity_str:
|
|
85
|
-
with contextlib.suppress(
|
|
86
|
-
target_entity_id =
|
|
207
|
+
if room_str and target_type == "room":
|
|
208
|
+
with contextlib.suppress(Exception):
|
|
209
|
+
target_room_id = as_uuid(room_str)
|
|
210
|
+
if entity_str and target_type == "user":
|
|
211
|
+
with contextlib.suppress(Exception):
|
|
212
|
+
target_entity_id = as_uuid(entity_str)
|
|
87
213
|
|
|
88
214
|
if not target_room_id:
|
|
89
215
|
return ActionResult(
|
|
@@ -95,16 +221,28 @@ class SendMessageAction:
|
|
|
95
221
|
|
|
96
222
|
message_content = Content(
|
|
97
223
|
text=message_text,
|
|
98
|
-
source=
|
|
224
|
+
source=source,
|
|
99
225
|
actions=["SEND_MESSAGE"],
|
|
100
226
|
)
|
|
101
227
|
|
|
228
|
+
send_message_to_target = getattr(runtime, "send_message_to_target", None)
|
|
229
|
+
if callable(send_message_to_target):
|
|
230
|
+
with contextlib.suppress(Exception):
|
|
231
|
+
from elizaos.types.runtime import TargetInfo
|
|
232
|
+
|
|
233
|
+
await send_message_to_target(
|
|
234
|
+
TargetInfo(
|
|
235
|
+
roomId=str(target_room_id),
|
|
236
|
+
entityId=str(target_entity_id) if target_entity_id else None,
|
|
237
|
+
source=source,
|
|
238
|
+
),
|
|
239
|
+
message_content,
|
|
240
|
+
)
|
|
241
|
+
|
|
102
242
|
# Create the message memory
|
|
103
243
|
import time
|
|
104
244
|
import uuid as uuid_module
|
|
105
245
|
|
|
106
|
-
from elizaos.types.primitives import as_uuid
|
|
107
|
-
|
|
108
246
|
message_memory = MemoryType(
|
|
109
247
|
id=as_uuid(str(uuid_module.uuid4())),
|
|
110
248
|
entity_id=runtime.agent_id,
|
|
@@ -142,16 +280,25 @@ class SendMessageAction:
|
|
|
142
280
|
if callback:
|
|
143
281
|
await callback(response_content)
|
|
144
282
|
|
|
283
|
+
target_id = (
|
|
284
|
+
target_entity_id if target_type == "user" and target_entity_id else target_room_id
|
|
285
|
+
)
|
|
145
286
|
return ActionResult(
|
|
146
|
-
text="Message sent
|
|
287
|
+
text="Message sent",
|
|
147
288
|
values={
|
|
148
289
|
"success": True,
|
|
149
290
|
"messageSent": True,
|
|
291
|
+
"targetType": target_type,
|
|
292
|
+
"target": str(target_id),
|
|
293
|
+
"source": source,
|
|
150
294
|
"targetRoomId": str(target_room_id),
|
|
151
295
|
"targetEntityId": str(target_entity_id) if target_entity_id else None,
|
|
152
296
|
},
|
|
153
297
|
data={
|
|
154
298
|
"actionName": "SEND_MESSAGE",
|
|
299
|
+
"targetType": target_type,
|
|
300
|
+
"target": str(target_id),
|
|
301
|
+
"source": source,
|
|
155
302
|
"targetRoomId": str(target_room_id),
|
|
156
303
|
"messagePreview": message_text[:100],
|
|
157
304
|
},
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
from .action import send_to_admin_action
|
|
3
|
+
from .action import disable_autonomy_action, enable_autonomy_action, send_to_admin_action
|
|
4
|
+
from .evaluators import post_action_evaluator
|
|
4
5
|
from .providers import admin_chat_provider, autonomy_status_provider
|
|
5
6
|
from .routes import autonomy_routes
|
|
6
7
|
from .service import AUTONOMY_SERVICE_TYPE, AutonomyService
|
|
@@ -12,6 +13,9 @@ __all__ = [
|
|
|
12
13
|
"AutonomyService",
|
|
13
14
|
"AUTONOMY_SERVICE_TYPE",
|
|
14
15
|
"send_to_admin_action",
|
|
16
|
+
"enable_autonomy_action",
|
|
17
|
+
"disable_autonomy_action",
|
|
18
|
+
"post_action_evaluator",
|
|
15
19
|
"admin_chat_provider",
|
|
16
20
|
"autonomy_status_provider",
|
|
17
21
|
"autonomy_routes",
|
|
@@ -195,3 +195,164 @@ send_to_admin_action = Action(
|
|
|
195
195
|
validate=_validate_send_to_admin,
|
|
196
196
|
handler=_handle_send_to_admin,
|
|
197
197
|
)
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
# ── ENABLE_AUTONOMY / DISABLE_AUTONOMY actions ─────────────────────────
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
def _is_autonomy_running(runtime: IAgentRuntime) -> bool:
|
|
204
|
+
"""Return True if the autonomy loop is currently active."""
|
|
205
|
+
svc = runtime.get_service(AUTONOMY_SERVICE_TYPE)
|
|
206
|
+
if svc and isinstance(svc, AutonomyService):
|
|
207
|
+
return svc.is_loop_running()
|
|
208
|
+
return getattr(runtime, "enable_autonomy", False)
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
async def _validate_enable_autonomy(
|
|
212
|
+
runtime: IAgentRuntime,
|
|
213
|
+
message: Memory,
|
|
214
|
+
_state: State | None = None,
|
|
215
|
+
) -> bool:
|
|
216
|
+
"""Valid only when autonomy is currently paused / disabled."""
|
|
217
|
+
return not _is_autonomy_running(runtime)
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
async def _validate_disable_autonomy(
|
|
221
|
+
runtime: IAgentRuntime,
|
|
222
|
+
message: Memory,
|
|
223
|
+
_state: State | None = None,
|
|
224
|
+
) -> bool:
|
|
225
|
+
"""Valid only when autonomy is currently running / enabled."""
|
|
226
|
+
return _is_autonomy_running(runtime)
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
async def _handle_enable_autonomy(
|
|
230
|
+
runtime: IAgentRuntime,
|
|
231
|
+
message: Memory,
|
|
232
|
+
state: State | None = None,
|
|
233
|
+
options: HandlerOptions | None = None,
|
|
234
|
+
callback: Callable[[Content], Awaitable[None]] | None = None,
|
|
235
|
+
responses: list[Memory] | None = None,
|
|
236
|
+
) -> ActionResult:
|
|
237
|
+
"""Enable the autonomous loop."""
|
|
238
|
+
autonomy_service = runtime.get_service(AUTONOMY_SERVICE_TYPE)
|
|
239
|
+
|
|
240
|
+
if not autonomy_service or not isinstance(autonomy_service, AutonomyService):
|
|
241
|
+
runtime.enable_autonomy = True
|
|
242
|
+
result_text = "Autonomy enabled (runtime flag). The autonomy service is not running."
|
|
243
|
+
if callback:
|
|
244
|
+
await callback(Content(text=result_text))
|
|
245
|
+
return ActionResult(success=True, text=result_text, data={"enabled": True})
|
|
246
|
+
|
|
247
|
+
await autonomy_service.enable_autonomy()
|
|
248
|
+
result_text = "Autonomy has been enabled."
|
|
249
|
+
if callback:
|
|
250
|
+
await callback(Content(text=result_text))
|
|
251
|
+
return ActionResult(success=True, text=result_text, data={"enabled": True})
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
async def _handle_disable_autonomy(
|
|
255
|
+
runtime: IAgentRuntime,
|
|
256
|
+
message: Memory,
|
|
257
|
+
state: State | None = None,
|
|
258
|
+
options: HandlerOptions | None = None,
|
|
259
|
+
callback: Callable[[Content], Awaitable[None]] | None = None,
|
|
260
|
+
responses: list[Memory] | None = None,
|
|
261
|
+
) -> ActionResult:
|
|
262
|
+
"""Disable the autonomous loop."""
|
|
263
|
+
autonomy_service = runtime.get_service(AUTONOMY_SERVICE_TYPE)
|
|
264
|
+
|
|
265
|
+
if not autonomy_service or not isinstance(autonomy_service, AutonomyService):
|
|
266
|
+
runtime.enable_autonomy = False
|
|
267
|
+
result_text = "Autonomy disabled (runtime flag)."
|
|
268
|
+
if callback:
|
|
269
|
+
await callback(Content(text=result_text))
|
|
270
|
+
return ActionResult(success=True, text=result_text, data={"enabled": False})
|
|
271
|
+
|
|
272
|
+
await autonomy_service.disable_autonomy()
|
|
273
|
+
result_text = "Autonomy has been disabled."
|
|
274
|
+
if callback:
|
|
275
|
+
await callback(Content(text=result_text))
|
|
276
|
+
return ActionResult(success=True, text=result_text, data={"enabled": False})
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
enable_autonomy_action = Action(
|
|
280
|
+
name="ENABLE_AUTONOMY",
|
|
281
|
+
description=(
|
|
282
|
+
"Enable the agent's autonomous operation. "
|
|
283
|
+
"Use this when asked to start autonomy, go autonomous, or activate autonomous behavior. "
|
|
284
|
+
"Only available when autonomy is currently paused."
|
|
285
|
+
),
|
|
286
|
+
similes=["START_AUTONOMY", "ACTIVATE_AUTONOMY", "GO_AUTONOMOUS"],
|
|
287
|
+
examples=[
|
|
288
|
+
[
|
|
289
|
+
{
|
|
290
|
+
"name": "User",
|
|
291
|
+
"content": {"text": "Enable autonomy"},
|
|
292
|
+
},
|
|
293
|
+
{
|
|
294
|
+
"name": "Agent",
|
|
295
|
+
"content": {
|
|
296
|
+
"text": "Autonomy has been enabled.",
|
|
297
|
+
"action": "ENABLE_AUTONOMY",
|
|
298
|
+
},
|
|
299
|
+
},
|
|
300
|
+
],
|
|
301
|
+
[
|
|
302
|
+
{
|
|
303
|
+
"name": "User",
|
|
304
|
+
"content": {"text": "Go autonomous"},
|
|
305
|
+
},
|
|
306
|
+
{
|
|
307
|
+
"name": "Agent",
|
|
308
|
+
"content": {
|
|
309
|
+
"text": "Autonomy has been enabled.",
|
|
310
|
+
"action": "ENABLE_AUTONOMY",
|
|
311
|
+
},
|
|
312
|
+
},
|
|
313
|
+
],
|
|
314
|
+
],
|
|
315
|
+
validate=_validate_enable_autonomy,
|
|
316
|
+
handler=_handle_enable_autonomy,
|
|
317
|
+
)
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
disable_autonomy_action = Action(
|
|
321
|
+
name="DISABLE_AUTONOMY",
|
|
322
|
+
description=(
|
|
323
|
+
"Disable the agent's autonomous operation. "
|
|
324
|
+
"Use this when asked to stop, pause, or deactivate autonomous behavior. "
|
|
325
|
+
"Only available when autonomy is currently running."
|
|
326
|
+
),
|
|
327
|
+
similes=["STOP_AUTONOMY", "PAUSE_AUTONOMY", "DEACTIVATE_AUTONOMY"],
|
|
328
|
+
examples=[
|
|
329
|
+
[
|
|
330
|
+
{
|
|
331
|
+
"name": "User",
|
|
332
|
+
"content": {"text": "Disable autonomy"},
|
|
333
|
+
},
|
|
334
|
+
{
|
|
335
|
+
"name": "Agent",
|
|
336
|
+
"content": {
|
|
337
|
+
"text": "Autonomy has been disabled.",
|
|
338
|
+
"action": "DISABLE_AUTONOMY",
|
|
339
|
+
},
|
|
340
|
+
},
|
|
341
|
+
],
|
|
342
|
+
[
|
|
343
|
+
{
|
|
344
|
+
"name": "User",
|
|
345
|
+
"content": {"text": "Stop being autonomous"},
|
|
346
|
+
},
|
|
347
|
+
{
|
|
348
|
+
"name": "Agent",
|
|
349
|
+
"content": {
|
|
350
|
+
"text": "Autonomy has been disabled.",
|
|
351
|
+
"action": "DISABLE_AUTONOMY",
|
|
352
|
+
},
|
|
353
|
+
},
|
|
354
|
+
],
|
|
355
|
+
],
|
|
356
|
+
validate=_validate_disable_autonomy,
|
|
357
|
+
handler=_handle_disable_autonomy,
|
|
358
|
+
)
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
"""Post-action evaluator for the autonomous loop.
|
|
2
|
+
|
|
3
|
+
After actions complete during an autonomous cycle, this evaluator asks the LLM
|
|
4
|
+
whether the agent has satisfied its goal or should continue with more actions.
|
|
5
|
+
If the LLM says CONTINUE, the evaluator recursively triggers another full
|
|
6
|
+
message-handling cycle so the agent can pick and execute additional actions.
|
|
7
|
+
This repeats until the LLM says PAUSE or the safety limit is reached.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import logging
|
|
13
|
+
from typing import TYPE_CHECKING, Any
|
|
14
|
+
|
|
15
|
+
from elizaos.types.components import ActionResult, Evaluator, HandlerOptions
|
|
16
|
+
from elizaos.types.memory import Memory
|
|
17
|
+
from elizaos.types.model import ModelType
|
|
18
|
+
from elizaos.types.primitives import Content
|
|
19
|
+
|
|
20
|
+
from .service import AUTONOMY_SERVICE_TYPE, AutonomyService
|
|
21
|
+
|
|
22
|
+
if TYPE_CHECKING:
|
|
23
|
+
from collections.abc import Awaitable, Callable
|
|
24
|
+
|
|
25
|
+
from elizaos.types.runtime import IAgentRuntime
|
|
26
|
+
from elizaos.types.state import State
|
|
27
|
+
|
|
28
|
+
_logger = logging.getLogger(__name__)
|
|
29
|
+
|
|
30
|
+
# Safety limit – the evaluator will stop recursing after this many
|
|
31
|
+
# consecutive CONTINUE decisions within a single evaluation chain.
|
|
32
|
+
MAX_CONTINUATION_DEPTH = 10
|
|
33
|
+
|
|
34
|
+
POST_ACTION_EVALUATION_TEMPLATE = """\
|
|
35
|
+
You are evaluating whether an autonomous agent has completed its current objective.
|
|
36
|
+
|
|
37
|
+
Recent actions and their results:
|
|
38
|
+
{action_results}
|
|
39
|
+
|
|
40
|
+
Recent context:
|
|
41
|
+
{recent_context}
|
|
42
|
+
|
|
43
|
+
Based on the above, decide:
|
|
44
|
+
- If the agent has completed everything it set out to do, or there is nothing \
|
|
45
|
+
more it can meaningfully do right now, respond with exactly: PAUSE
|
|
46
|
+
- If there are remaining steps, errors that need retrying, or follow-up actions \
|
|
47
|
+
the agent should take immediately, respond with exactly: CONTINUE
|
|
48
|
+
|
|
49
|
+
Respond with a single word: CONTINUE or PAUSE
|
|
50
|
+
"""
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
async def _validate_post_action(
|
|
54
|
+
runtime: IAgentRuntime,
|
|
55
|
+
message: Memory,
|
|
56
|
+
_state: State | None = None,
|
|
57
|
+
) -> bool:
|
|
58
|
+
"""Run when the message originated from the autonomy service."""
|
|
59
|
+
if not message.content:
|
|
60
|
+
return False
|
|
61
|
+
|
|
62
|
+
# Check content.data for autonomy markers
|
|
63
|
+
data = message.content.data
|
|
64
|
+
if isinstance(data, dict):
|
|
65
|
+
if data.get("isAutonomous") is True or data.get("source") == "autonomy-service":
|
|
66
|
+
return True
|
|
67
|
+
|
|
68
|
+
# Check content.source field
|
|
69
|
+
source = getattr(message.content, "source", None)
|
|
70
|
+
return bool(isinstance(source, str) and source == "autonomy-service")
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def _collect_action_results_text(runtime: IAgentRuntime, message: Memory) -> str:
|
|
74
|
+
"""Build a human-readable summary of most recent action results."""
|
|
75
|
+
if not message.id:
|
|
76
|
+
return "(no action results available)"
|
|
77
|
+
|
|
78
|
+
results = runtime.get_action_results(message.id)
|
|
79
|
+
if not results:
|
|
80
|
+
return "(no action results available)"
|
|
81
|
+
|
|
82
|
+
lines: list[str] = []
|
|
83
|
+
for r in results:
|
|
84
|
+
name = ""
|
|
85
|
+
data = getattr(r, "data", None)
|
|
86
|
+
if isinstance(data, dict):
|
|
87
|
+
v = data.get("actionName")
|
|
88
|
+
if isinstance(v, str):
|
|
89
|
+
name = v
|
|
90
|
+
success = getattr(r, "success", True)
|
|
91
|
+
text = getattr(r, "text", "")
|
|
92
|
+
status = "success" if success else "failed"
|
|
93
|
+
lines.append(f"- {name} ({status}): {text}")
|
|
94
|
+
|
|
95
|
+
return "\n".join(lines) if lines else "(no action results available)"
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
async def _collect_recent_context(runtime: IAgentRuntime, room_id: Any) -> str:
|
|
99
|
+
"""Gather a short snippet of recent memories for the evaluation prompt."""
|
|
100
|
+
try:
|
|
101
|
+
recent_memories = await runtime.get_memories(
|
|
102
|
+
{"roomId": room_id, "count": 5, "tableName": "memories"}
|
|
103
|
+
)
|
|
104
|
+
except Exception:
|
|
105
|
+
return "(no recent context)"
|
|
106
|
+
|
|
107
|
+
if not recent_memories:
|
|
108
|
+
return "(no recent context)"
|
|
109
|
+
|
|
110
|
+
ctx_lines: list[str] = []
|
|
111
|
+
for m in recent_memories:
|
|
112
|
+
if m.content and m.content.text:
|
|
113
|
+
ctx_lines.append(m.content.text[:200])
|
|
114
|
+
|
|
115
|
+
return "\n".join(ctx_lines[-3:]) if ctx_lines else "(no recent context)"
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
async def _handle_post_action(
|
|
119
|
+
runtime: IAgentRuntime,
|
|
120
|
+
message: Memory,
|
|
121
|
+
state: State | None = None,
|
|
122
|
+
options: HandlerOptions | None = None,
|
|
123
|
+
callback: Callable[[Content], Awaitable[None]] | None = None,
|
|
124
|
+
responses: list[Memory] | None = None,
|
|
125
|
+
) -> ActionResult | None:
|
|
126
|
+
"""Evaluate whether the agent should continue with more actions.
|
|
127
|
+
|
|
128
|
+
Runs after ``processActions`` completes during an autonomy cycle.
|
|
129
|
+
If the LLM says CONTINUE, it recursively triggers another autonomous
|
|
130
|
+
think iteration. This repeats until the LLM says PAUSE or the
|
|
131
|
+
safety depth limit is reached.
|
|
132
|
+
"""
|
|
133
|
+
autonomy_service = runtime.get_service(AUTONOMY_SERVICE_TYPE)
|
|
134
|
+
if not autonomy_service or not isinstance(autonomy_service, AutonomyService):
|
|
135
|
+
return None
|
|
136
|
+
|
|
137
|
+
# Don't evaluate if autonomy is not actually running
|
|
138
|
+
if not autonomy_service.is_loop_running():
|
|
139
|
+
return None
|
|
140
|
+
|
|
141
|
+
# Track recursion depth via an attribute on the service instance
|
|
142
|
+
depth: int = getattr(autonomy_service, "_eval_depth", 0)
|
|
143
|
+
|
|
144
|
+
action_results_text = _collect_action_results_text(runtime, message)
|
|
145
|
+
|
|
146
|
+
room_id = autonomy_service.get_autonomous_room_id() or message.room_id
|
|
147
|
+
recent_context = await _collect_recent_context(runtime, room_id)
|
|
148
|
+
|
|
149
|
+
prompt = POST_ACTION_EVALUATION_TEMPLATE.format(
|
|
150
|
+
action_results=action_results_text,
|
|
151
|
+
recent_context=recent_context,
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
try:
|
|
155
|
+
result = await runtime.use_model(
|
|
156
|
+
ModelType.TEXT_SMALL,
|
|
157
|
+
{"prompt": prompt, "temperature": 0.1, "maxTokens": 10},
|
|
158
|
+
)
|
|
159
|
+
decision = str(result).strip().upper()
|
|
160
|
+
except Exception as e:
|
|
161
|
+
_logger.warning(f"Post-action evaluation LLM call failed: {e}")
|
|
162
|
+
autonomy_service._eval_depth = 0 # type: ignore[attr-defined]
|
|
163
|
+
return None
|
|
164
|
+
|
|
165
|
+
if "CONTINUE" in decision:
|
|
166
|
+
depth += 1
|
|
167
|
+
|
|
168
|
+
if depth >= MAX_CONTINUATION_DEPTH:
|
|
169
|
+
runtime.logger.warning(
|
|
170
|
+
f"[post-action-evaluator] Safety limit reached ({MAX_CONTINUATION_DEPTH} "
|
|
171
|
+
"consecutive actions). Pausing."
|
|
172
|
+
)
|
|
173
|
+
autonomy_service._eval_depth = 0 # type: ignore[attr-defined]
|
|
174
|
+
return ActionResult(
|
|
175
|
+
success=True,
|
|
176
|
+
text=f"Paused after {MAX_CONTINUATION_DEPTH} consecutive actions",
|
|
177
|
+
data={"decision": "PAUSE", "reason": "depth_limit", "depth": depth},
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
runtime.logger.info(
|
|
181
|
+
f"[post-action-evaluator] CONTINUE (depth {depth}) – "
|
|
182
|
+
"triggering another autonomous think cycle"
|
|
183
|
+
)
|
|
184
|
+
autonomy_service._eval_depth = depth # type: ignore[attr-defined]
|
|
185
|
+
|
|
186
|
+
# Recurse: trigger another full think → actions → evaluate cycle
|
|
187
|
+
await autonomy_service.perform_autonomous_think()
|
|
188
|
+
|
|
189
|
+
return ActionResult(
|
|
190
|
+
success=True,
|
|
191
|
+
text=f"Continued with additional actions (depth {depth})",
|
|
192
|
+
data={"decision": "CONTINUE", "depth": depth},
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
# PAUSE (or unrecognised → default to pause)
|
|
196
|
+
runtime.logger.info(
|
|
197
|
+
f"[post-action-evaluator] PAUSE after {depth} continuation(s) – agent is satisfied"
|
|
198
|
+
)
|
|
199
|
+
autonomy_service._eval_depth = 0 # type: ignore[attr-defined]
|
|
200
|
+
|
|
201
|
+
return ActionResult(
|
|
202
|
+
success=True,
|
|
203
|
+
text="Agent is satisfied, pausing",
|
|
204
|
+
data={"decision": "PAUSE", "depth": depth},
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
post_action_evaluator = Evaluator(
|
|
209
|
+
name="POST_ACTION_EVALUATOR",
|
|
210
|
+
description=(
|
|
211
|
+
"Evaluates after autonomous actions complete to determine if the agent "
|
|
212
|
+
"should recursively continue with more actions or pause."
|
|
213
|
+
),
|
|
214
|
+
validate=_validate_post_action,
|
|
215
|
+
handler=_handle_post_action,
|
|
216
|
+
always_run=True,
|
|
217
|
+
)
|