@elizaos/python 2.0.0-alpha.10 → 2.0.0-alpha.11

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 (99) hide show
  1. package/elizaos/__init__.py +0 -1
  2. package/elizaos/advanced_capabilities/actions/schedule_follow_up.py +70 -15
  3. package/elizaos/advanced_capabilities/actions/send_message.py +9 -184
  4. package/elizaos/advanced_memory/memory_service.py +15 -17
  5. package/elizaos/advanced_planning/planning_service.py +26 -14
  6. package/elizaos/basic_capabilities/providers/actions.py +118 -29
  7. package/elizaos/basic_capabilities/providers/character.py +19 -21
  8. package/elizaos/basic_capabilities/providers/current_time.py +7 -4
  9. package/elizaos/basic_capabilities/providers/time.py +7 -4
  10. package/elizaos/bootstrap/__init__.py +21 -2
  11. package/elizaos/bootstrap/actions/schedule_follow_up.py +65 -7
  12. package/elizaos/bootstrap/actions/send_message.py +162 -15
  13. package/elizaos/bootstrap/autonomy/service.py +230 -28
  14. package/elizaos/bootstrap/providers/actions.py +118 -27
  15. package/elizaos/bootstrap/providers/agent_settings.py +1 -0
  16. package/elizaos/bootstrap/providers/attachments.py +1 -0
  17. package/elizaos/bootstrap/providers/capabilities.py +1 -0
  18. package/elizaos/bootstrap/providers/character.py +1 -0
  19. package/elizaos/bootstrap/providers/choice.py +1 -0
  20. package/elizaos/bootstrap/providers/contacts.py +1 -0
  21. package/elizaos/bootstrap/providers/current_time.py +8 -2
  22. package/elizaos/bootstrap/providers/entities.py +1 -0
  23. package/elizaos/bootstrap/providers/evaluators.py +1 -0
  24. package/elizaos/bootstrap/providers/facts.py +1 -0
  25. package/elizaos/bootstrap/providers/follow_ups.py +1 -0
  26. package/elizaos/bootstrap/providers/knowledge.py +1 -0
  27. package/elizaos/bootstrap/providers/providers_list.py +1 -0
  28. package/elizaos/bootstrap/providers/relationships.py +1 -0
  29. package/elizaos/bootstrap/providers/roles.py +1 -0
  30. package/elizaos/bootstrap/providers/settings.py +1 -0
  31. package/elizaos/bootstrap/providers/time.py +8 -4
  32. package/elizaos/bootstrap/providers/world.py +1 -0
  33. package/elizaos/deterministic.py +193 -0
  34. package/elizaos/generated/__init__.py +1 -0
  35. package/elizaos/generated/action_docs.py +3181 -0
  36. package/elizaos/generated/spec_helpers.py +175 -0
  37. package/elizaos/media/mime.py +2 -2
  38. package/elizaos/media/search.py +23 -23
  39. package/elizaos/runtime.py +152 -39
  40. package/elizaos/services/message_service.py +2 -6
  41. package/elizaos/types/components.py +2 -2
  42. package/elizaos/types/generated/__init__.py +12 -0
  43. package/elizaos/types/generated/eliza/v1/agent_pb2.py +63 -0
  44. package/elizaos/types/generated/eliza/v1/agent_pb2.pyi +161 -0
  45. package/elizaos/types/generated/eliza/v1/components_pb2.py +65 -0
  46. package/elizaos/types/generated/eliza/v1/components_pb2.pyi +160 -0
  47. package/elizaos/types/generated/eliza/v1/database_pb2.py +78 -0
  48. package/elizaos/types/generated/eliza/v1/database_pb2.pyi +305 -0
  49. package/elizaos/types/generated/eliza/v1/environment_pb2.py +58 -0
  50. package/elizaos/types/generated/eliza/v1/environment_pb2.pyi +135 -0
  51. package/elizaos/types/generated/eliza/v1/events_pb2.py +82 -0
  52. package/elizaos/types/generated/eliza/v1/events_pb2.pyi +322 -0
  53. package/elizaos/types/generated/eliza/v1/ipc_pb2.py +113 -0
  54. package/elizaos/types/generated/eliza/v1/ipc_pb2.pyi +367 -0
  55. package/elizaos/types/generated/eliza/v1/knowledge_pb2.py +41 -0
  56. package/elizaos/types/generated/eliza/v1/knowledge_pb2.pyi +26 -0
  57. package/elizaos/types/generated/eliza/v1/memory_pb2.py +55 -0
  58. package/elizaos/types/generated/eliza/v1/memory_pb2.pyi +111 -0
  59. package/elizaos/types/generated/eliza/v1/message_service_pb2.py +48 -0
  60. package/elizaos/types/generated/eliza/v1/message_service_pb2.pyi +69 -0
  61. package/elizaos/types/generated/eliza/v1/messaging_pb2.py +51 -0
  62. package/elizaos/types/generated/eliza/v1/messaging_pb2.pyi +97 -0
  63. package/elizaos/types/generated/eliza/v1/model_pb2.py +84 -0
  64. package/elizaos/types/generated/eliza/v1/model_pb2.pyi +280 -0
  65. package/elizaos/types/generated/eliza/v1/payment_pb2.py +44 -0
  66. package/elizaos/types/generated/eliza/v1/payment_pb2.pyi +70 -0
  67. package/elizaos/types/generated/eliza/v1/plugin_pb2.py +68 -0
  68. package/elizaos/types/generated/eliza/v1/plugin_pb2.pyi +145 -0
  69. package/elizaos/types/generated/eliza/v1/primitives_pb2.py +48 -0
  70. package/elizaos/types/generated/eliza/v1/primitives_pb2.pyi +92 -0
  71. package/elizaos/types/generated/eliza/v1/prompts_pb2.py +52 -0
  72. package/elizaos/types/generated/eliza/v1/prompts_pb2.pyi +74 -0
  73. package/elizaos/types/generated/eliza/v1/service_interfaces_pb2.py +211 -0
  74. package/elizaos/types/generated/eliza/v1/service_interfaces_pb2.pyi +1296 -0
  75. package/elizaos/types/generated/eliza/v1/service_pb2.py +42 -0
  76. package/elizaos/types/generated/eliza/v1/service_pb2.pyi +69 -0
  77. package/elizaos/types/generated/eliza/v1/settings_pb2.py +58 -0
  78. package/elizaos/types/generated/eliza/v1/settings_pb2.pyi +85 -0
  79. package/elizaos/types/generated/eliza/v1/state_pb2.py +60 -0
  80. package/elizaos/types/generated/eliza/v1/state_pb2.pyi +114 -0
  81. package/elizaos/types/generated/eliza/v1/task_pb2.py +42 -0
  82. package/elizaos/types/generated/eliza/v1/task_pb2.pyi +58 -0
  83. package/elizaos/types/generated/eliza/v1/tee_pb2.py +52 -0
  84. package/elizaos/types/generated/eliza/v1/tee_pb2.pyi +90 -0
  85. package/elizaos/types/generated/eliza/v1/testing_pb2.py +39 -0
  86. package/elizaos/types/generated/eliza/v1/testing_pb2.pyi +23 -0
  87. package/elizaos/types/model.py +3 -0
  88. package/elizaos/types/runtime.py +1 -1
  89. package/package.json +3 -2
  90. package/tests/test_action_parameters.py +2 -3
  91. package/tests/test_advanced_memory_behavior.py +0 -2
  92. package/tests/test_advanced_memory_flag.py +0 -2
  93. package/tests/test_advanced_planning_behavior.py +11 -5
  94. package/tests/test_autonomy.py +11 -1
  95. package/tests/test_runtime.py +8 -17
  96. package/tests/test_schedule_follow_up_action.py +260 -0
  97. package/tests/test_send_message_action_targets.py +114 -0
  98. package/tests/test_settings_crypto.py +0 -2
  99. package/uv.lock +1565 -0
@@ -108,7 +108,6 @@ from elizaos.types import (
108
108
  string_to_uuid,
109
109
  )
110
110
  from elizaos.types.database import IDatabaseAdapter # noqa: E402
111
- from elizaos.types.environment import ChannelType # noqa: E402
112
111
  from elizaos.types.primitives import ( # noqa: E402
113
112
  ChannelType,
114
113
  Content,
@@ -3,8 +3,10 @@ from __future__ import annotations
3
3
  from dataclasses import dataclass, field
4
4
  from datetime import datetime
5
5
  from typing import TYPE_CHECKING
6
+ from uuid import UUID as StdUUID
6
7
 
7
8
  from elizaos.bootstrap.utils.xml import parse_key_value_xml
9
+ from elizaos.deterministic import get_prompt_reference_datetime
8
10
  from elizaos.generated.spec_helpers import require_action_spec
9
11
  from elizaos.prompts import SCHEDULE_FOLLOW_UP_TEMPLATE
10
12
  from elizaos.types import (
@@ -34,6 +36,25 @@ def _convert_spec_examples() -> list[list[ActionExample]]:
34
36
  return convert_spec_examples(_spec)
35
37
 
36
38
 
39
+ def _normalize_priority(raw_priority: str) -> str:
40
+ normalized = raw_priority.strip().lower()
41
+ if normalized in {"high", "medium", "low"}:
42
+ return normalized
43
+ return "medium"
44
+
45
+
46
+ def _coerce_uuid(value: object | None) -> StdUUID | None:
47
+ if value is None:
48
+ return None
49
+ text = str(value).strip()
50
+ if not text:
51
+ return None
52
+ try:
53
+ return StdUUID(text)
54
+ except ValueError:
55
+ return None
56
+
57
+
37
58
  @dataclass
38
59
  class ScheduleFollowUpAction:
39
60
  name: str = _spec["name"]
@@ -79,7 +100,12 @@ class ScheduleFollowUpAction:
79
100
  )
80
101
 
81
102
  state = await runtime.compose_state(message, ["RECENT_MESSAGES", "ENTITIES"])
82
- state.values["currentDateTime"] = datetime.utcnow().isoformat()
103
+ state.values["currentDateTime"] = get_prompt_reference_datetime(
104
+ runtime,
105
+ message,
106
+ state,
107
+ "action:schedule_follow_up",
108
+ ).isoformat()
83
109
 
84
110
  prompt = runtime.compose_prompt_from_state(
85
111
  state=state,
@@ -100,24 +126,53 @@ class ScheduleFollowUpAction:
100
126
  contact_name = str(parsed.get("contactName", ""))
101
127
  scheduled_at_str = str(parsed.get("scheduledAt", ""))
102
128
  reason = str(parsed.get("reason", "Follow-up"))
103
- priority = str(parsed.get("priority", "medium"))
129
+ priority = _normalize_priority(str(parsed.get("priority", "medium")))
104
130
  follow_up_message = str(parsed.get("message", ""))
131
+ parsed_entity_id = _coerce_uuid(parsed.get("entityId"))
132
+ message_entity_id = _coerce_uuid(message.entity_id)
133
+
134
+ try:
135
+ scheduled_at = datetime.fromisoformat(scheduled_at_str.replace("Z", "+00:00"))
136
+ except ValueError:
137
+ return ActionResult(
138
+ text="Could not parse the follow-up date/time",
139
+ success=False,
140
+ values={"error": True},
141
+ data={"error": "Invalid follow-up datetime"},
142
+ )
105
143
 
106
- scheduled_at = datetime.fromisoformat(scheduled_at_str.replace("Z", "+00:00"))
144
+ entity_id_uuid = parsed_entity_id or message_entity_id
107
145
 
108
- from uuid import UUID as StdUUID
146
+ if entity_id_uuid is None and contact_name:
147
+ contacts = await rolodex_service.search_contacts(search_term=contact_name)
148
+ if contacts:
149
+ entity_id_uuid = contacts[0].entity_id
109
150
 
110
- entity_id = message.entity_id
111
- entity_id_uuid = StdUUID(str(entity_id)) if entity_id else None
112
- if entity_id_uuid:
113
- await follow_up_service.schedule_follow_up(
114
- entity_id=entity_id_uuid,
115
- scheduled_at=scheduled_at,
116
- reason=reason,
117
- priority=priority,
118
- message=follow_up_message,
151
+ if entity_id_uuid is None:
152
+ return ActionResult(
153
+ text=f"Could not determine which contact to schedule for ({contact_name}).",
154
+ success=False,
155
+ values={"error": True},
156
+ data={"error": "Missing contact entity id"},
119
157
  )
120
158
 
159
+ contact = await rolodex_service.get_contact(entity_id_uuid)
160
+ if contact is None:
161
+ return ActionResult(
162
+ text=f"Contact '{contact_name}' was not found in the rolodex.",
163
+ success=False,
164
+ values={"error": True},
165
+ data={"error": "Contact not found"},
166
+ )
167
+
168
+ await follow_up_service.schedule_follow_up(
169
+ entity_id=entity_id_uuid,
170
+ scheduled_at=scheduled_at,
171
+ reason=reason,
172
+ priority=priority,
173
+ message=follow_up_message,
174
+ )
175
+
121
176
  response_text = f"I've scheduled a follow-up with {contact_name} for {scheduled_at.strftime('%B %d, %Y')}. Reason: {reason}"
122
177
 
123
178
  if callback:
@@ -127,11 +182,11 @@ class ScheduleFollowUpAction:
127
182
  text=response_text,
128
183
  success=True,
129
184
  values={
130
- "contactId": str(entity_id),
185
+ "contactId": str(entity_id_uuid),
131
186
  "scheduledAt": scheduled_at.isoformat(),
132
187
  },
133
188
  data={
134
- "contactId": str(entity_id),
189
+ "contactId": str(entity_id_uuid),
135
190
  "contactName": contact_name,
136
191
  "scheduledAt": scheduled_at.isoformat(),
137
192
  "reason": reason,
@@ -1,187 +1,12 @@
1
- from __future__ import annotations
1
+ """Advanced capabilities SEND_MESSAGE action.
2
2
 
3
- import contextlib
4
- from dataclasses import dataclass, field
5
- from typing import TYPE_CHECKING
3
+ Keep this action implementation aligned with bootstrap to guarantee identical
4
+ target parsing and dispatch behavior across capability variants.
5
+ """
6
6
 
7
- from elizaos.generated.spec_helpers import require_action_spec
8
- from elizaos.types import Action, ActionExample, ActionResult, Content
9
- from elizaos.types.memory import Memory as MemoryType
10
-
11
- if TYPE_CHECKING:
12
- from elizaos.types import HandlerCallback, HandlerOptions, IAgentRuntime, Memory, State
13
-
14
- # Get text content from centralized specs
15
- _spec = require_action_spec("SEND_MESSAGE")
16
-
17
-
18
- def _convert_spec_examples() -> list[list[ActionExample]]:
19
- """Convert spec examples to ActionExample format."""
20
- spec_examples = _spec.get("examples", [])
21
- if not isinstance(spec_examples, list):
22
- return []
23
- result: list[list[ActionExample]] = []
24
- for example in spec_examples:
25
- if not isinstance(example, list):
26
- continue
27
- row: list[ActionExample] = []
28
- for msg in example:
29
- if not isinstance(msg, dict):
30
- continue
31
- content = msg.get("content", {})
32
- text = ""
33
- actions: list[str] | None = None
34
- if isinstance(content, dict):
35
- text_val = content.get("text", "")
36
- text = str(text_val) if text_val else ""
37
- actions_val = content.get("actions")
38
- if isinstance(actions_val, list) and all(isinstance(a, str) for a in actions_val):
39
- actions = list(actions_val)
40
- row.append(
41
- ActionExample(
42
- name=str(msg.get("name", "")),
43
- content=Content(text=text, actions=actions),
44
- )
45
- )
46
- if row:
47
- result.append(row)
48
- return result
49
-
50
-
51
- @dataclass
52
- class SendMessageAction:
53
- name: str = _spec["name"]
54
- similes: list[str] = field(default_factory=lambda: list(_spec.get("similes", [])))
55
- description: str = _spec["description"]
56
-
57
- async def validate(
58
- self, runtime: IAgentRuntime, message: Memory, _state: State | None = None
59
- ) -> bool:
60
- if message.content and message.content.target:
61
- return True
62
- return True
63
-
64
- async def handler(
65
- self,
66
- runtime: IAgentRuntime,
67
- message: Memory,
68
- state: State | None = None,
69
- options: HandlerOptions | None = None,
70
- callback: HandlerCallback | None = None,
71
- responses: list[Memory] | None = None,
72
- ) -> ActionResult:
73
- message_text = ""
74
- if responses and responses[0].content:
75
- message_text = str(responses[0].content.text or "")
76
-
77
- if not message_text:
78
- return ActionResult(
79
- text="No message content to send",
80
- values={"success": False, "error": "no_content"},
81
- data={"actionName": "SEND_MESSAGE"},
82
- success=False,
83
- )
84
-
85
- from uuid import UUID as StdUUID
86
-
87
- target_room_id_val: str | None = str(message.room_id) if message.room_id else None
88
- target_entity_id: str | None = None
89
-
90
- if message.content and message.content.target:
91
- target = message.content.target
92
- if isinstance(target, dict):
93
- room_str = target.get("roomId")
94
- entity_str = target.get("entityId")
95
- if room_str:
96
- with contextlib.suppress(ValueError):
97
- target_room_id_val = str(StdUUID(str(room_str)))
98
- if entity_str:
99
- with contextlib.suppress(ValueError):
100
- target_entity_id = str(StdUUID(str(entity_str)))
101
-
102
- if not target_room_id_val:
103
- return ActionResult(
104
- text="No target room specified",
105
- values={"success": False, "error": "no_target"},
106
- data={"actionName": "SEND_MESSAGE"},
107
- success=False,
108
- )
109
-
110
- message_content = Content(
111
- text=message_text,
112
- source="agent",
113
- actions=["SEND_MESSAGE"],
114
- )
115
-
116
- # Create the message memory for event emission
117
- import time
118
- import uuid as uuid_module
119
-
120
- from elizaos.types.primitives import as_uuid
121
-
122
- message_memory = MemoryType(
123
- id=as_uuid(str(uuid_module.uuid4())),
124
- entity_id=runtime.agent_id,
125
- room_id=as_uuid(target_room_id_val) if target_room_id_val else None,
126
- content=message_content,
127
- created_at=int(time.time() * 1000),
128
- )
129
-
130
- await runtime.create_memory(
131
- content=message_content,
132
- room_id=target_room_id_val,
133
- entity_id=runtime.agent_id,
134
- memory_type="message",
135
- metadata={
136
- "type": "SEND_MESSAGE",
137
- "targetEntityId": str(target_entity_id) if target_entity_id else None,
138
- },
139
- )
140
-
141
- # Emit MESSAGE_SENT event
142
- await runtime.emit_event(
143
- "MESSAGE_SENT",
144
- {
145
- "runtime": runtime,
146
- "source": "send-message-action",
147
- "message": message_memory,
148
- },
149
- )
150
-
151
- response_content = Content(
152
- text=f"Message sent: {message_text[:50]}...",
153
- actions=["SEND_MESSAGE"],
154
- )
155
-
156
- if callback:
157
- await callback(response_content)
158
-
159
- return ActionResult(
160
- text="Message sent to room",
161
- values={
162
- "success": True,
163
- "messageSent": True,
164
- "targetRoomId": str(target_room_id_val),
165
- "targetEntityId": str(target_entity_id) if target_entity_id else None,
166
- },
167
- data={
168
- "actionName": "SEND_MESSAGE",
169
- "targetRoomId": str(target_room_id_val),
170
- "messagePreview": message_text[:100],
171
- },
172
- success=True,
173
- )
174
-
175
- @property
176
- def examples(self) -> list[list[ActionExample]]:
177
- return _convert_spec_examples()
178
-
179
-
180
- send_message_action = Action(
181
- name=SendMessageAction.name,
182
- similes=SendMessageAction().similes,
183
- description=SendMessageAction.description,
184
- validate=SendMessageAction().validate,
185
- handler=SendMessageAction().handler,
186
- examples=SendMessageAction().examples,
7
+ from elizaos.bootstrap.actions.send_message import ( # re-export for parity
8
+ SendMessageAction,
9
+ send_message_action,
187
10
  )
11
+
12
+ __all__ = ["SendMessageAction", "send_message_action"]
@@ -95,38 +95,36 @@ class MemoryService(Service):
95
95
  @classmethod
96
96
  async def start(cls, runtime):
97
97
  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(
98
+ if (v := runtime.get_setting("MEMORY_SUMMARIZATION_THRESHOLD")) is not None and isinstance(
101
99
  v, (int, float, str)
102
100
  ):
103
101
  svc._config.short_term_summarization_threshold = int(v)
104
- if (v := settings.get("MEMORY_RETAIN_RECENT")) is not None and isinstance(
102
+ if (v := runtime.get_setting("MEMORY_RETAIN_RECENT")) is not None and isinstance(
105
103
  v, (int, float, str)
106
104
  ):
107
105
  svc._config.short_term_retain_recent = int(v)
108
- if (v := settings.get("MEMORY_SUMMARIZATION_INTERVAL")) is not None and isinstance(
106
+ if (v := runtime.get_setting("MEMORY_SUMMARIZATION_INTERVAL")) is not None and isinstance(
109
107
  v, (int, float, str)
110
108
  ):
111
109
  svc._config.short_term_summarization_interval = int(v)
112
- if (v := settings.get("MEMORY_MAX_NEW_MESSAGES")) is not None and isinstance(
110
+ if (v := runtime.get_setting("MEMORY_MAX_NEW_MESSAGES")) is not None and isinstance(
113
111
  v, (int, float, str)
114
112
  ):
115
113
  svc._config.summary_max_new_messages = int(v)
116
- if (v := settings.get("MEMORY_LONG_TERM_ENABLED")) is not None:
114
+ if (v := runtime.get_setting("MEMORY_LONG_TERM_ENABLED")) is not None:
117
115
  if str(v).lower() == "false":
118
116
  svc._config.long_term_extraction_enabled = False
119
117
  elif str(v).lower() == "true":
120
118
  svc._config.long_term_extraction_enabled = True
121
- if (v := settings.get("MEMORY_CONFIDENCE_THRESHOLD")) is not None and isinstance(
119
+ if (v := runtime.get_setting("MEMORY_CONFIDENCE_THRESHOLD")) is not None and isinstance(
122
120
  v, (int, float, str)
123
121
  ):
124
122
  svc._config.long_term_confidence_threshold = float(v)
125
- if (v := settings.get("MEMORY_EXTRACTION_THRESHOLD")) is not None and isinstance(
123
+ if (v := runtime.get_setting("MEMORY_EXTRACTION_THRESHOLD")) is not None and isinstance(
126
124
  v, (int, float, str)
127
125
  ):
128
126
  svc._config.long_term_extraction_threshold = int(v)
129
- if (v := settings.get("MEMORY_EXTRACTION_INTERVAL")) is not None and isinstance(
127
+ if (v := runtime.get_setting("MEMORY_EXTRACTION_INTERVAL")) is not None and isinstance(
130
128
  v, (int, float, str)
131
129
  ):
132
130
  svc._config.long_term_extraction_interval = int(v)
@@ -146,7 +144,7 @@ class MemoryService(Service):
146
144
  return f"memory:extraction:{entity_id}:{room_id}"
147
145
 
148
146
  async def get_last_extraction_checkpoint(self, entity_id: UUID, room_id: UUID) -> int:
149
- runtime = self.runtime
147
+ runtime = self._runtime
150
148
  key = self._checkpoint_key(entity_id, room_id)
151
149
  if runtime is not None and getattr(runtime, "_adapter", None) is not None:
152
150
  cached = await runtime.get_cache(key)
@@ -163,7 +161,7 @@ class MemoryService(Service):
163
161
  async def set_last_extraction_checkpoint(
164
162
  self, entity_id: UUID, room_id: UUID, message_count: int
165
163
  ) -> None:
166
- runtime = self.runtime
164
+ runtime = self._runtime
167
165
  key = self._checkpoint_key(entity_id, room_id)
168
166
  if runtime is not None and getattr(runtime, "_adapter", None) is not None:
169
167
  _ = await runtime.set_cache(key, int(message_count))
@@ -182,7 +180,7 @@ class MemoryService(Service):
182
180
  return current_cp > last_cp
183
181
 
184
182
  async def get_current_session_summary(self, room_id: UUID) -> SessionSummary | None:
185
- runtime = self.runtime
183
+ runtime = self._runtime
186
184
  if runtime is None:
187
185
  return None
188
186
 
@@ -234,7 +232,7 @@ class MemoryService(Service):
234
232
  topics: list[str] | None = None,
235
233
  metadata: dict[str, object] | None = None,
236
234
  ) -> SessionSummary:
237
- runtime = self.runtime
235
+ runtime = self._runtime
238
236
  s = SessionSummary(
239
237
  id=uuid4(),
240
238
  agent_id=agent_id,
@@ -279,7 +277,7 @@ class MemoryService(Service):
279
277
  async def update_session_summary(
280
278
  self, summary_id: UUID, room_id: UUID, **updates: object
281
279
  ) -> None:
282
- runtime = self.runtime
280
+ runtime = self._runtime
283
281
  if runtime is not None and getattr(runtime, "_adapter", None) is not None:
284
282
  existing = await self.get_current_session_summary(room_id)
285
283
  if not existing or existing.id != summary_id:
@@ -350,7 +348,7 @@ class MemoryService(Service):
350
348
  source: str | None = None,
351
349
  metadata: dict[str, object] | None = None,
352
350
  ) -> LongTermMemory:
353
- runtime = self.runtime
351
+ runtime = self._runtime
354
352
  m = LongTermMemory(
355
353
  id=uuid4(),
356
354
  agent_id=agent_id,
@@ -394,7 +392,7 @@ class MemoryService(Service):
394
392
  ) -> list[LongTermMemory]:
395
393
  if limit <= 0:
396
394
  return []
397
- runtime = self.runtime
395
+ runtime = self._runtime
398
396
  if runtime is not None and getattr(runtime, "_adapter", None) is not None:
399
397
  db_mems = await runtime.get_memories(
400
398
  {
@@ -10,7 +10,7 @@ from uuid import UUID, uuid4
10
10
  from google.protobuf.json_format import MessageToDict
11
11
 
12
12
  from elizaos.logger import Logger
13
- from elizaos.types.components import ActionContext, ActionResult, HandlerCallback, HandlerOptions
13
+ from elizaos.types.components import ActionContext, ActionResult, HandlerCallback
14
14
  from elizaos.types.memory import Memory
15
15
  from elizaos.types.primitives import Content
16
16
  from elizaos.types.service import Service
@@ -538,6 +538,8 @@ Focus on:
538
538
  ]
539
539
  results = await asyncio.gather(*tasks, return_exceptions=True)
540
540
  for r in results:
541
+ if isinstance(r, Exception):
542
+ raise RuntimeError(f"Plan step failed during parallel execution: {r}") from r
541
543
  if isinstance(r, ActionResult):
542
544
  execution.results.append(r)
543
545
 
@@ -586,6 +588,10 @@ Focus on:
586
588
 
587
589
  for step, r in zip(ready_batch, results, strict=False):
588
590
  completed_count += 1
591
+ if isinstance(r, Exception):
592
+ raise RuntimeError(
593
+ f"Plan step '{step.action_name}' failed during DAG execution: {r}"
594
+ ) from r
589
595
  if isinstance(r, ActionResult):
590
596
  execution.results.append(r)
591
597
 
@@ -622,13 +628,16 @@ Focus on:
622
628
  if execution.abort_event.is_set():
623
629
  raise RuntimeError("Plan execution aborted")
624
630
  try:
625
- options = HandlerOptions(
626
- action_context=action_context,
627
- parameters=step.parameters,
628
- )
629
- # Attach extra execution context (allowed by extra="allow")
630
- options.previous_results = previous_results # type: ignore[attr-defined]
631
- options.context = {"workingMemory": execution.working_memory} # type: ignore[attr-defined]
631
+ options = type(
632
+ "_PlanningHandlerOptions",
633
+ (),
634
+ {
635
+ "action_context": action_context,
636
+ "parameters": step.parameters,
637
+ "previous_results": previous_results,
638
+ "context": {"workingMemory": execution.working_memory},
639
+ },
640
+ )()
632
641
 
633
642
  validate_fn = getattr(action, "validate", None) or getattr(
634
643
  action, "validate_fn", None
@@ -641,12 +650,15 @@ Focus on:
641
650
  if result is None:
642
651
  return None
643
652
 
644
- if result.data is None:
645
- result.data = {}
646
- if isinstance(result.data, dict):
647
- result.data["stepId"] = str(step.id)
648
- result.data["actionName"] = step.action_name
649
- result.data["executedAt"] = int(time.time() * 1000)
653
+ step_metadata = {
654
+ "stepId": str(step.id),
655
+ "actionName": step.action_name,
656
+ "executedAt": int(time.time() * 1000),
657
+ }
658
+ if isinstance(result.data, dict) or hasattr(result.data, "update"):
659
+ result.data.update(step_metadata)
660
+ else:
661
+ result.data = step_metadata
650
662
  return result
651
663
  except Exception as e:
652
664
  retries += 1