@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.
Files changed (145) 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 +21 -11
  5. package/elizaos/advanced_capabilities/actions/follow_room.py +28 -28
  6. package/elizaos/advanced_capabilities/actions/image_generation.py +13 -26
  7. package/elizaos/advanced_capabilities/actions/mute_room.py +13 -26
  8. package/elizaos/advanced_capabilities/actions/remove_contact.py +16 -2
  9. package/elizaos/advanced_capabilities/actions/roles.py +13 -27
  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 +183 -50
  13. package/elizaos/advanced_capabilities/actions/settings.py +16 -2
  14. package/elizaos/advanced_capabilities/actions/unfollow_room.py +13 -26
  15. package/elizaos/advanced_capabilities/actions/unmute_room.py +13 -26
  16. package/elizaos/advanced_capabilities/actions/update_contact.py +16 -2
  17. package/elizaos/advanced_capabilities/actions/update_entity.py +16 -2
  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 +24 -3
  23. package/elizaos/advanced_capabilities/services/__init__.py +2 -9
  24. package/elizaos/advanced_memory/actions/reset_session.py +11 -0
  25. package/elizaos/advanced_memory/evaluators/reflection.py +134 -0
  26. package/elizaos/advanced_memory/evaluators/relationship_extraction.py +203 -0
  27. package/elizaos/advanced_memory/memory_service.py +15 -17
  28. package/elizaos/advanced_memory/test_advanced_memory.py +357 -0
  29. package/elizaos/advanced_planning/actions/schedule_follow_up.py +222 -0
  30. package/elizaos/advanced_planning/planning_service.py +26 -14
  31. package/elizaos/basic_capabilities/__init__.py +0 -2
  32. package/elizaos/basic_capabilities/providers/__init__.py +0 -3
  33. package/elizaos/basic_capabilities/providers/actions.py +118 -29
  34. package/elizaos/basic_capabilities/providers/agent_settings.py +64 -0
  35. package/elizaos/basic_capabilities/providers/character.py +19 -21
  36. package/elizaos/basic_capabilities/providers/contacts.py +79 -0
  37. package/elizaos/basic_capabilities/providers/current_time.py +7 -4
  38. package/elizaos/basic_capabilities/providers/facts.py +87 -0
  39. package/elizaos/basic_capabilities/providers/follow_ups.py +117 -0
  40. package/elizaos/basic_capabilities/providers/knowledge.py +97 -0
  41. package/elizaos/basic_capabilities/providers/relationships.py +107 -0
  42. package/elizaos/basic_capabilities/providers/roles.py +96 -0
  43. package/elizaos/basic_capabilities/providers/settings.py +56 -0
  44. package/elizaos/basic_capabilities/providers/time.py +7 -4
  45. package/elizaos/bootstrap/__init__.py +21 -2
  46. package/elizaos/bootstrap/actions/schedule_follow_up.py +65 -7
  47. package/elizaos/bootstrap/actions/send_message.py +162 -15
  48. package/elizaos/bootstrap/autonomy/__init__.py +5 -1
  49. package/elizaos/bootstrap/autonomy/action.py +161 -0
  50. package/elizaos/bootstrap/autonomy/evaluators.py +217 -0
  51. package/elizaos/bootstrap/autonomy/service.py +238 -28
  52. package/elizaos/bootstrap/plugin.py +7 -0
  53. package/elizaos/bootstrap/providers/actions.py +118 -27
  54. package/elizaos/bootstrap/providers/agent_settings.py +1 -0
  55. package/elizaos/bootstrap/providers/attachments.py +1 -0
  56. package/elizaos/bootstrap/providers/capabilities.py +1 -0
  57. package/elizaos/bootstrap/providers/character.py +1 -0
  58. package/elizaos/bootstrap/providers/choice.py +1 -0
  59. package/elizaos/bootstrap/providers/contacts.py +1 -0
  60. package/elizaos/bootstrap/providers/current_time.py +8 -2
  61. package/elizaos/bootstrap/providers/entities.py +1 -0
  62. package/elizaos/bootstrap/providers/evaluators.py +1 -0
  63. package/elizaos/bootstrap/providers/facts.py +1 -0
  64. package/elizaos/bootstrap/providers/follow_ups.py +1 -0
  65. package/elizaos/bootstrap/providers/knowledge.py +27 -3
  66. package/elizaos/bootstrap/providers/providers_list.py +1 -0
  67. package/elizaos/bootstrap/providers/relationships.py +1 -0
  68. package/elizaos/bootstrap/providers/roles.py +1 -0
  69. package/elizaos/bootstrap/providers/settings.py +1 -0
  70. package/elizaos/bootstrap/providers/time.py +8 -4
  71. package/elizaos/bootstrap/providers/world.py +1 -0
  72. package/elizaos/bootstrap/services/embedding.py +156 -1
  73. package/elizaos/deterministic.py +193 -0
  74. package/elizaos/generated/__init__.py +1 -0
  75. package/elizaos/generated/action_docs.py +3181 -0
  76. package/elizaos/generated/spec_helpers.py +175 -0
  77. package/elizaos/media/mime.py +2 -2
  78. package/elizaos/media/search.py +23 -23
  79. package/elizaos/runtime.py +215 -57
  80. package/elizaos/services/message_service.py +175 -29
  81. package/elizaos/types/components.py +2 -2
  82. package/elizaos/types/generated/__init__.py +12 -0
  83. package/elizaos/types/generated/eliza/v1/agent_pb2.py +63 -0
  84. package/elizaos/types/generated/eliza/v1/agent_pb2.pyi +159 -0
  85. package/elizaos/types/generated/eliza/v1/components_pb2.py +65 -0
  86. package/elizaos/types/generated/eliza/v1/components_pb2.pyi +160 -0
  87. package/elizaos/types/generated/eliza/v1/database_pb2.py +78 -0
  88. package/elizaos/types/generated/eliza/v1/database_pb2.pyi +305 -0
  89. package/elizaos/types/generated/eliza/v1/environment_pb2.py +58 -0
  90. package/elizaos/types/generated/eliza/v1/environment_pb2.pyi +135 -0
  91. package/elizaos/types/generated/eliza/v1/events_pb2.py +82 -0
  92. package/elizaos/types/generated/eliza/v1/events_pb2.pyi +322 -0
  93. package/elizaos/types/generated/eliza/v1/ipc_pb2.py +113 -0
  94. package/elizaos/types/generated/eliza/v1/ipc_pb2.pyi +367 -0
  95. package/elizaos/types/generated/eliza/v1/knowledge_pb2.py +41 -0
  96. package/elizaos/types/generated/eliza/v1/knowledge_pb2.pyi +26 -0
  97. package/elizaos/types/generated/eliza/v1/memory_pb2.py +55 -0
  98. package/elizaos/types/generated/eliza/v1/memory_pb2.pyi +111 -0
  99. package/elizaos/types/generated/eliza/v1/message_service_pb2.py +48 -0
  100. package/elizaos/types/generated/eliza/v1/message_service_pb2.pyi +69 -0
  101. package/elizaos/types/generated/eliza/v1/messaging_pb2.py +51 -0
  102. package/elizaos/types/generated/eliza/v1/messaging_pb2.pyi +97 -0
  103. package/elizaos/types/generated/eliza/v1/model_pb2.py +84 -0
  104. package/elizaos/types/generated/eliza/v1/model_pb2.pyi +280 -0
  105. package/elizaos/types/generated/eliza/v1/payment_pb2.py +44 -0
  106. package/elizaos/types/generated/eliza/v1/payment_pb2.pyi +70 -0
  107. package/elizaos/types/generated/eliza/v1/plugin_pb2.py +68 -0
  108. package/elizaos/types/generated/eliza/v1/plugin_pb2.pyi +145 -0
  109. package/elizaos/types/generated/eliza/v1/primitives_pb2.py +48 -0
  110. package/elizaos/types/generated/eliza/v1/primitives_pb2.pyi +92 -0
  111. package/elizaos/types/generated/eliza/v1/prompts_pb2.py +52 -0
  112. package/elizaos/types/generated/eliza/v1/prompts_pb2.pyi +74 -0
  113. package/elizaos/types/generated/eliza/v1/service_interfaces_pb2.py +211 -0
  114. package/elizaos/types/generated/eliza/v1/service_interfaces_pb2.pyi +1296 -0
  115. package/elizaos/types/generated/eliza/v1/service_pb2.py +42 -0
  116. package/elizaos/types/generated/eliza/v1/service_pb2.pyi +69 -0
  117. package/elizaos/types/generated/eliza/v1/settings_pb2.py +58 -0
  118. package/elizaos/types/generated/eliza/v1/settings_pb2.pyi +85 -0
  119. package/elizaos/types/generated/eliza/v1/state_pb2.py +60 -0
  120. package/elizaos/types/generated/eliza/v1/state_pb2.pyi +114 -0
  121. package/elizaos/types/generated/eliza/v1/task_pb2.py +42 -0
  122. package/elizaos/types/generated/eliza/v1/task_pb2.pyi +58 -0
  123. package/elizaos/types/generated/eliza/v1/tee_pb2.py +52 -0
  124. package/elizaos/types/generated/eliza/v1/tee_pb2.pyi +90 -0
  125. package/elizaos/types/generated/eliza/v1/testing_pb2.py +39 -0
  126. package/elizaos/types/generated/eliza/v1/testing_pb2.pyi +23 -0
  127. package/elizaos/types/model.py +30 -0
  128. package/elizaos/types/runtime.py +6 -2
  129. package/elizaos/utils/validation.py +76 -0
  130. package/package.json +3 -2
  131. package/tests/test_action_parameters.py +2 -3
  132. package/tests/test_actions_provider_examples.py +58 -1
  133. package/tests/test_advanced_memory_behavior.py +0 -2
  134. package/tests/test_advanced_memory_flag.py +0 -2
  135. package/tests/test_advanced_planning_behavior.py +11 -5
  136. package/tests/test_async_embedding.py +124 -0
  137. package/tests/test_autonomy.py +24 -3
  138. package/tests/test_runtime.py +8 -17
  139. package/tests/test_schedule_follow_up_action.py +260 -0
  140. package/tests/test_send_message_action_targets.py +114 -0
  141. package/tests/test_settings_crypto.py +0 -2
  142. package/tests/test_validation.py +141 -0
  143. package/tests/verify_memory_architecture.py +192 -0
  144. package/uv.lock +1565 -0
  145. package/elizaos/basic_capabilities/providers/capabilities.py +0 -62
@@ -1,14 +1,22 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import asyncio
4
+ import contextlib
4
5
  import re
5
6
  import uuid
6
7
  import xml.etree.ElementTree as ET
7
- from collections.abc import AsyncIterator, Awaitable, Callable
8
+ from collections.abc import AsyncIterator, Awaitable, Callable, Mapping, MutableMapping
8
9
  from dataclasses import dataclass
9
10
  from typing import Any
10
11
 
12
+ from google.protobuf.struct_pb2 import Value as StructValue
13
+
11
14
  from elizaos.action_docs import with_canonical_action_docs, with_canonical_evaluator_docs
15
+ from elizaos.deterministic import (
16
+ build_conversation_seed,
17
+ deterministic_hex,
18
+ deterministic_uuid,
19
+ )
12
20
  from elizaos.logger import Logger, create_logger
13
21
  from elizaos.settings import decrypt_secret, get_salt
14
22
  from elizaos.types.agent import Character, TemplateType
@@ -18,9 +26,10 @@ from elizaos.types.components import (
18
26
  Evaluator,
19
27
  HandlerCallback,
20
28
  HandlerOptions,
29
+ PreEvaluatorResult,
21
30
  Provider,
22
31
  )
23
- from elizaos.types.database import AgentRunSummaryResult, IDatabaseAdapter, Log
32
+ from elizaos.types.database import AgentRunSummaryResult, IDatabaseAdapter, Log, MemorySearchOptions
24
33
  from elizaos.types.environment import Entity, Room, World
25
34
  from elizaos.types.events import EventType
26
35
  from elizaos.types.memory import Memory
@@ -81,6 +90,48 @@ class StreamingModelHandlerWrapper:
81
90
 
82
91
  _anonymous_agent_counter = 0
83
92
 
93
+ _MISSING = object()
94
+
95
+
96
+ def _struct_value_to_python(value: StructValue) -> object | None:
97
+ kind = value.WhichOneof("kind")
98
+ if kind == "null_value":
99
+ return None
100
+ if kind == "number_value":
101
+ return value.number_value
102
+ if kind == "string_value":
103
+ return value.string_value
104
+ if kind == "bool_value":
105
+ return value.bool_value
106
+ if kind == "struct_value":
107
+ return {
108
+ key: _struct_value_to_python(item) for key, item in value.struct_value.fields.items()
109
+ }
110
+ if kind == "list_value":
111
+ return [_struct_value_to_python(item) for item in value.list_value.values]
112
+ return None
113
+
114
+
115
+ def _is_struct_compatible(value: object) -> bool:
116
+ if value is None:
117
+ return True
118
+ if isinstance(value, (str, int, float, bool)):
119
+ return True
120
+ if isinstance(value, list):
121
+ return all(_is_struct_compatible(item) for item in value)
122
+ if isinstance(value, Mapping):
123
+ return all(
124
+ isinstance(map_key, str) and _is_struct_compatible(map_value)
125
+ for map_key, map_value in value.items()
126
+ )
127
+ return False
128
+
129
+
130
+ def _to_runtime_setting_value(value: object | None) -> str | bool | int | float | None:
131
+ if value is None or isinstance(value, (str, bool, int, float)):
132
+ return value
133
+ return str(value)
134
+
84
135
 
85
136
  class AgentRuntime(IAgentRuntime):
86
137
  def __init__(
@@ -93,7 +144,7 @@ class AgentRuntime(IAgentRuntime):
93
144
  conversation_length: int = 32,
94
145
  log_level: str = "ERROR",
95
146
  disable_basic_capabilities: bool = False,
96
- enable_extended_capabilities: bool = False,
147
+ advanced_capabilities: bool = False,
97
148
  action_planning: bool | None = None,
98
149
  llm_mode: LLMMode | None = None,
99
150
  check_should_respond: bool | None = None,
@@ -112,7 +163,7 @@ class AgentRuntime(IAgentRuntime):
112
163
  is_anonymous = True
113
164
 
114
165
  self._capability_disable_basic = disable_basic_capabilities
115
- self._capability_enable_extended = enable_extended_capabilities
166
+ self._capability_advanced = advanced_capabilities
116
167
  self._capability_enable_autonomy = enable_autonomy
117
168
  self._is_anonymous_character = is_anonymous
118
169
  self._action_planning_option = action_planning
@@ -289,8 +340,8 @@ class AgentRuntime(IAgentRuntime):
289
340
  disable_basic = self._capability_disable_basic or (
290
341
  char_settings.get("DISABLE_BASIC_CAPABILITIES") in (True, "true")
291
342
  )
292
- enable_extended = self._capability_enable_extended or (
293
- char_settings.get("ENABLE_EXTENDED_CAPABILITIES") in (True, "true")
343
+ advanced_capabilities = self._capability_advanced or (
344
+ char_settings.get("ADVANCED_CAPABILITIES") in (True, "true")
294
345
  )
295
346
  skip_character_provider = self._is_anonymous_character
296
347
 
@@ -298,12 +349,12 @@ class AgentRuntime(IAgentRuntime):
298
349
  char_settings.get("ENABLE_AUTONOMY") in (True, "true")
299
350
  )
300
351
 
301
- if disable_basic or enable_extended or skip_character_provider or enable_autonomy:
352
+ if disable_basic or advanced_capabilities or skip_character_provider or enable_autonomy:
302
353
  from elizaos.bootstrap import CapabilityConfig, create_bootstrap_plugin
303
354
 
304
355
  config = CapabilityConfig(
305
356
  disable_basic=disable_basic,
306
- enable_extended=enable_extended,
357
+ advanced_capabilities=advanced_capabilities,
307
358
  skip_character_provider=skip_character_provider,
308
359
  enable_autonomy=enable_autonomy,
309
360
  )
@@ -354,37 +405,58 @@ class AgentRuntime(IAgentRuntime):
354
405
  if secret:
355
406
  if self._character.secrets is None:
356
407
  self._character.secrets = {}
357
- if isinstance(self._character.secrets, dict):
408
+ if isinstance(self._character.secrets, MutableMapping):
358
409
  self._character.secrets[key] = value # type: ignore[assignment]
359
410
  else:
360
411
  # Fall back to internal settings dict for protobuf objects
361
- self._settings[key] = value
412
+ self._settings[key] = _to_runtime_setting_value(value)
362
413
  return
363
414
 
364
415
  # Try to set on character.settings if it's a dict
365
- if isinstance(self._character.settings, dict):
416
+ if isinstance(self._character.settings, MutableMapping):
366
417
  self._character.settings[key] = value # type: ignore[assignment]
418
+ return
419
+
420
+ settings_extra = getattr(self._character.settings, "extra", None)
421
+ if (
422
+ settings_extra is not None
423
+ and hasattr(settings_extra, "update")
424
+ and _is_struct_compatible(value)
425
+ ):
426
+ settings_extra.update({key: value})
367
427
  else:
368
428
  # Fall back to internal settings dict for protobuf objects
369
- self._settings[key] = value
429
+ self._settings[key] = _to_runtime_setting_value(value)
370
430
 
371
431
  def get_setting(self, key: str) -> object | None:
372
432
  settings = self._character.settings
373
433
  secrets = self._character.secrets
374
434
 
375
- nested_secrets: dict[str, object] | None = None
376
- if isinstance(settings, dict):
435
+ nested_secrets: Mapping[str, object] | None = None
436
+ extra_value: object = _MISSING
437
+ if isinstance(settings, Mapping):
377
438
  nested = settings.get("secrets")
378
- if isinstance(nested, dict):
439
+ if isinstance(nested, Mapping):
379
440
  nested_secrets = nested
441
+ else:
442
+ settings_extra = getattr(settings, "extra", None)
443
+ settings_fields = getattr(settings_extra, "fields", None)
444
+ if isinstance(settings_fields, Mapping) and key in settings_fields:
445
+ struct_candidate = settings_fields[key]
446
+ if isinstance(struct_candidate, StructValue):
447
+ extra_value = _struct_value_to_python(struct_candidate)
448
+ else:
449
+ extra_value = struct_candidate
380
450
 
381
451
  value: object | None
382
- if isinstance(secrets, dict) and key in secrets:
452
+ if isinstance(secrets, Mapping) and key in secrets:
383
453
  value = secrets.get(key)
384
- elif isinstance(settings, dict) and key in settings:
454
+ elif isinstance(settings, Mapping) and key in settings:
385
455
  value = settings.get(key)
386
- elif isinstance(nested_secrets, dict) and key in nested_secrets:
456
+ elif isinstance(nested_secrets, Mapping) and key in nested_secrets:
387
457
  value = nested_secrets.get(key)
458
+ elif extra_value is not _MISSING:
459
+ value = extra_value
388
460
  else:
389
461
  value = self._settings.get(key)
390
462
 
@@ -409,12 +481,17 @@ class AgentRuntime(IAgentRuntime):
409
481
 
410
482
  def get_all_settings(self) -> dict[str, object | None]:
411
483
  keys: set[str] = set(self._settings.keys())
412
- if isinstance(self._character.settings, dict):
484
+ if isinstance(self._character.settings, Mapping):
413
485
  keys.update(self._character.settings.keys())
414
486
  nested = self._character.settings.get("secrets")
415
- if isinstance(nested, dict):
487
+ if isinstance(nested, Mapping):
416
488
  keys.update(nested.keys())
417
- if isinstance(self._character.secrets, dict):
489
+ else:
490
+ settings_extra = getattr(self._character.settings, "extra", None)
491
+ settings_fields = getattr(settings_extra, "fields", None)
492
+ if isinstance(settings_fields, Mapping):
493
+ keys.update(settings_fields.keys())
494
+ if isinstance(self._character.secrets, Mapping):
418
495
  keys.update(self._character.secrets.keys())
419
496
 
420
497
  return {k: self.get_setting(k) for k in keys}
@@ -608,8 +685,14 @@ class AgentRuntime(IAgentRuntime):
608
685
  errors.append(
609
686
  f"Required parameter '{param_def.name}' was not provided for action {action.name}"
610
687
  )
611
- elif getattr(param_def.schema, "default_value", None):
612
- validated[param_def.name] = param_def.schema.default_value
688
+ else:
689
+ default_value = getattr(param_def.schema, "default_value", None)
690
+ if isinstance(default_value, StructValue):
691
+ parsed_default = _struct_value_to_python(default_value)
692
+ if parsed_default is not None:
693
+ validated[param_def.name] = parsed_default
694
+ elif default_value is not None:
695
+ validated[param_def.name] = default_value
613
696
  continue
614
697
 
615
698
  schema_type = param_def.schema.type
@@ -770,6 +853,7 @@ class AgentRuntime(IAgentRuntime):
770
853
  if data_params is not None:
771
854
  # Convert protobuf Struct to dict for _parse_action_params
772
855
  from google.protobuf.json_format import MessageToDict
856
+
773
857
  if hasattr(data_params, "DESCRIPTOR"):
774
858
  params_raw = MessageToDict(data_params)
775
859
  else:
@@ -802,11 +886,8 @@ class AgentRuntime(IAgentRuntime):
802
886
  actionName=action.name,
803
887
  errors=errors,
804
888
  )
805
- try:
889
+ with contextlib.suppress(AttributeError, ValueError):
806
890
  options_obj.parameter_errors = errors
807
- except (AttributeError, ValueError):
808
- # Protobuf HandlerOptions may not have parameter_errors field
809
- pass
810
891
 
811
892
  if validated_params:
812
893
  from google.protobuf import struct_pb2
@@ -891,7 +972,7 @@ class AgentRuntime(IAgentRuntime):
891
972
  self,
892
973
  message: Memory,
893
974
  state: State | None = None,
894
- ) -> "PreEvaluatorResult":
975
+ ) -> PreEvaluatorResult:
895
976
  """Run phase='pre' evaluators as middleware before memory storage.
896
977
 
897
978
  Pre-evaluators can inspect, rewrite, or block a message before it
@@ -902,8 +983,6 @@ class AgentRuntime(IAgentRuntime):
902
983
  Returns:
903
984
  A merged PreEvaluatorResult.
904
985
  """
905
- from elizaos.types.components import PreEvaluatorResult
906
-
907
986
  pre_evaluators = [e for e in self._evaluators if getattr(e, "phase", "post") == "pre"]
908
987
  if not pre_evaluators:
909
988
  return PreEvaluatorResult(blocked=False)
@@ -1057,12 +1136,17 @@ class AgentRuntime(IAgentRuntime):
1057
1136
  include_list: list[str] | None = None,
1058
1137
  only_include: bool = False,
1059
1138
  skip_cache: bool = False,
1139
+ trajectory_phase: str | None = None,
1060
1140
  ) -> State:
1061
1141
  # If we're running inside a trajectory step, always bypass the state cache
1062
1142
  # so providers are executed and logged for training/benchmark traces.
1063
1143
  traj_step_id: str | None = None
1064
1144
  if message.metadata is not None:
1065
1145
  maybe_step = getattr(message.metadata, "trajectoryStepId", None)
1146
+ if not maybe_step and hasattr(message.metadata, "message"):
1147
+ # Check nested MessageMetadata for parsing parity
1148
+ maybe_step = message.metadata.message.trajectory_step_id
1149
+
1066
1150
  if isinstance(maybe_step, str) and maybe_step:
1067
1151
  traj_step_id = maybe_step
1068
1152
  skip_cache = True
@@ -1133,6 +1217,9 @@ class AgentRuntime(IAgentRuntime):
1133
1217
  out[k] = _as_json_scalar(v)
1134
1218
  return out
1135
1219
 
1220
+ # Resolve the purpose label for trajectory provider accesses
1221
+ traj_purpose = f"compose_state:{trajectory_phase}" if trajectory_phase else "compose_state"
1222
+
1136
1223
  text_parts: list[str] = []
1137
1224
  for provider in providers_to_run:
1138
1225
  if provider.private:
@@ -1155,18 +1242,18 @@ class AgentRuntime(IAgentRuntime):
1155
1242
 
1156
1243
  # Log provider access to trajectory service (if available)
1157
1244
  if traj_step_id and traj_logger is not None:
1158
- try:
1159
- user_text = message.content.text or ""
1245
+ # Trajectory logging must never break core message flow.
1246
+ with contextlib.suppress(Exception):
1160
1247
  traj_logger.log_provider_access(
1161
1248
  step_id=traj_step_id,
1162
1249
  provider_name=provider.name,
1163
- data=_as_json_dict(result.data or {}),
1164
- purpose="compose_state",
1165
- query={"message": _as_json_scalar(user_text)},
1250
+ data={
1251
+ "textLength": len(result.text) if result.text else 0,
1252
+ "hasValues": bool(result.values),
1253
+ "hasData": bool(result.data),
1254
+ },
1255
+ purpose=traj_purpose,
1166
1256
  )
1167
- except Exception:
1168
- # Trajectory logging must never break core message flow.
1169
- pass
1170
1257
 
1171
1258
  state.text = "\n".join(text_parts)
1172
1259
  # Match TypeScript behavior: expose providers text under {{providers}}.
@@ -1260,16 +1347,25 @@ class AgentRuntime(IAgentRuntime):
1260
1347
  max_tokens_raw = params.get("maxTokens") if isinstance(params, dict) else None
1261
1348
  max_tokens = int(max_tokens_raw) if isinstance(max_tokens_raw, int) else 0
1262
1349
 
1350
+ # Truncate embedding vectors to avoid bloating trajectory files
1351
+ result_str = str(result)
1352
+ is_embedding = "EMBEDDING" in str(effective_model_type).upper()
1353
+ if is_embedding and len(result_str) > 200:
1354
+ dim = result_str.count(",") + 1
1355
+ result_str = f"[embedding vector dim={dim}]"
1356
+ elif len(result_str) > 2000:
1357
+ result_str = result_str[:2000]
1358
+
1263
1359
  traj_svc.log_llm_call( # type: ignore[call-arg]
1264
1360
  step_id=step_id,
1265
1361
  model=str(effective_model_type),
1266
1362
  system_prompt=system_prompt,
1267
- user_prompt=prompt,
1268
- response=str(result),
1363
+ user_prompt=prompt[:2000] if prompt else "",
1364
+ response=result_str,
1269
1365
  temperature=temperature,
1270
1366
  max_tokens=max_tokens,
1271
1367
  purpose="action",
1272
- action_type="runtime.use_model",
1368
+ action_type="runtime.useModel",
1273
1369
  latency_ms=max(0, end_ms - start_ms),
1274
1370
  )
1275
1371
  except Exception:
@@ -1648,6 +1744,12 @@ class AgentRuntime(IAgentRuntime):
1648
1744
  if self._adapter:
1649
1745
  await self._adapter.delete_component(component_id)
1650
1746
 
1747
+ async def search_memories(self, params: MemorySearchOptions) -> list[Memory]:
1748
+ """Search memories by embedding."""
1749
+ if not self._adapter:
1750
+ raise RuntimeError("Database adapter not set")
1751
+ return await self._adapter.search_memories(params)
1752
+
1651
1753
  async def get_memories(
1652
1754
  self,
1653
1755
  params: dict[str, Any] | None = None,
@@ -1982,6 +2084,12 @@ class AgentRuntime(IAgentRuntime):
1982
2084
 
1983
2085
  schema_key = ",".join(s.field for s in schema)
1984
2086
  model_schema_key = f"{model_type_str}:{schema_key}"
2087
+ deterministic_seed = build_conversation_seed(
2088
+ self,
2089
+ None,
2090
+ state,
2091
+ f"dynamic-prompt:{model_schema_key}",
2092
+ )
1985
2093
 
1986
2094
  # Get validation level from settings or options (mirrors TypeScript behavior)
1987
2095
  default_context_level = 2
@@ -2023,7 +2131,11 @@ class AgentRuntime(IAgentRuntime):
2023
2131
  row.validate_field if row.validate_field is not None else default_validate
2024
2132
  )
2025
2133
  if needs_validation:
2026
- per_field_codes[row.field] = str(uuid.uuid4())[:8]
2134
+ per_field_codes[row.field] = deterministic_hex(
2135
+ deterministic_seed,
2136
+ f"field-code:{row.field}",
2137
+ 8,
2138
+ )
2027
2139
 
2028
2140
  # Streaming extractor (created on first iteration if streaming enabled)
2029
2141
  extractor: ValidationStreamExtractor | None = None
@@ -2147,9 +2259,18 @@ class AgentRuntime(IAgentRuntime):
2147
2259
  example_lines.append(container_end)
2148
2260
  example = "\n".join(example_lines)
2149
2261
 
2150
- init_code = str(uuid.uuid4())
2151
- mid_code = str(uuid.uuid4())
2152
- final_code = str(uuid.uuid4())
2262
+ init_code = deterministic_uuid(
2263
+ deterministic_seed,
2264
+ f"init-code:{current_retry}",
2265
+ )
2266
+ mid_code = deterministic_uuid(
2267
+ deterministic_seed,
2268
+ f"mid-code:{current_retry}",
2269
+ )
2270
+ final_code = deterministic_uuid(
2271
+ deterministic_seed,
2272
+ f"final-code:{current_retry}",
2273
+ )
2153
2274
 
2154
2275
  section_start = "<output>" if is_xml else "# Strict Output instructions"
2155
2276
  section_end = "</output>" if is_xml else ""
@@ -2223,25 +2344,40 @@ end code: {final_code}
2223
2344
  if not stream_fields and any(row.field == "text" for row in schema):
2224
2345
  stream_fields = ["text"]
2225
2346
 
2226
- stream_message_id = f"stream-{uuid.uuid4().hex[:12]}"
2347
+ stream_message_id = "stream-" + deterministic_hex(
2348
+ deterministic_seed,
2349
+ f"stream-message-id:{current_retry}",
2350
+ 20,
2351
+ )
2352
+
2353
+ on_stream_chunk = options.on_stream_chunk
2354
+ on_stream_event = options.on_stream_event
2355
+
2356
+ def _emit_chunk(
2357
+ chunk: str,
2358
+ _field: str | None,
2359
+ cb=on_stream_chunk,
2360
+ msg_id=stream_message_id,
2361
+ ) -> None:
2362
+ if cb is not None:
2363
+ cb(chunk, msg_id)
2364
+
2365
+ def _emit_event(
2366
+ event: StreamEvent,
2367
+ cb=on_stream_event,
2368
+ msg_id=stream_message_id,
2369
+ ) -> None:
2370
+ if cb is not None:
2371
+ cb(event, msg_id)
2227
2372
 
2228
- # Capture stream_message_id in default parameter to avoid late binding
2229
2373
  extractor = ValidationStreamExtractor(
2230
2374
  ValidationStreamExtractorConfig(
2231
2375
  level=validation_level,
2232
2376
  schema=schema,
2233
2377
  stream_fields=stream_fields,
2234
2378
  expected_codes=per_field_codes,
2235
- on_chunk=lambda chunk,
2236
- _field,
2237
- msg_id=stream_message_id: options.on_stream_chunk(chunk, msg_id)
2238
- if options.on_stream_chunk
2239
- else None,
2240
- on_event=lambda event, msg_id=stream_message_id: options.on_stream_event(
2241
- event, msg_id
2242
- )
2243
- if options.on_stream_event
2244
- else None,
2379
+ on_chunk=_emit_chunk,
2380
+ on_event=_emit_event if on_stream_event is not None else None,
2245
2381
  abort_signal=options.abort_signal,
2246
2382
  has_rich_consumer=has_rich_consumer,
2247
2383
  )
@@ -2268,6 +2404,28 @@ end code: {final_code}
2268
2404
  # Flush extractor and get final state
2269
2405
  extractor.flush()
2270
2406
  response_str = "".join(response_parts)
2407
+
2408
+ # Log streaming response to trajectory (streaming bypasses use_model hook)
2409
+ try:
2410
+ from elizaos.trajectory_context import CURRENT_TRAJECTORY_STEP_ID
2411
+
2412
+ step_id = CURRENT_TRAJECTORY_STEP_ID.get()
2413
+ traj_svc = self.get_service("trajectory_logger")
2414
+ if step_id and traj_svc is not None and hasattr(traj_svc, "log_llm_call"):
2415
+ traj_svc.log_llm_call( # type: ignore[call-arg]
2416
+ step_id=step_id,
2417
+ model=stream_model_type,
2418
+ system_prompt="",
2419
+ user_prompt=str(params.get("prompt", ""))[:2000],
2420
+ response=response_str[:2000],
2421
+ temperature=0.0,
2422
+ max_tokens=int(params.get("maxTokens", 0)),
2423
+ purpose="action",
2424
+ action_type="dynamic_prompt_exec.stream",
2425
+ latency_ms=0,
2426
+ )
2427
+ except Exception:
2428
+ pass
2271
2429
  else:
2272
2430
  # Non-streaming mode: use use_model
2273
2431
  response = await self.use_model(model_type_str, params)