@elizaos/python 2.0.0-alpha.27 → 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 (75) hide show
  1. package/elizaos/advanced_capabilities/actions/add_contact.py +4 -3
  2. package/elizaos/advanced_capabilities/actions/follow_room.py +2 -2
  3. package/elizaos/advanced_capabilities/actions/image_generation.py +2 -2
  4. package/elizaos/advanced_capabilities/actions/mute_room.py +2 -2
  5. package/elizaos/advanced_capabilities/actions/remove_contact.py +2 -2
  6. package/elizaos/advanced_capabilities/actions/roles.py +5 -4
  7. package/elizaos/advanced_capabilities/actions/search_contacts.py +3 -3
  8. package/elizaos/advanced_capabilities/actions/send_message.py +2 -2
  9. package/elizaos/advanced_capabilities/actions/settings.py +2 -2
  10. package/elizaos/advanced_capabilities/actions/unfollow_room.py +2 -2
  11. package/elizaos/advanced_capabilities/actions/unmute_room.py +2 -2
  12. package/elizaos/advanced_capabilities/actions/update_contact.py +2 -2
  13. package/elizaos/advanced_capabilities/actions/update_entity.py +2 -2
  14. package/elizaos/advanced_capabilities/providers/knowledge.py +8 -9
  15. package/elizaos/advanced_capabilities/services/rolodex.py +2 -2
  16. package/elizaos/advanced_memory/actions/reset_session.py +143 -11
  17. package/elizaos/advanced_memory/memory_service.py +54 -10
  18. package/elizaos/advanced_memory/plugin.py +2 -1
  19. package/elizaos/advanced_memory/types.py +2 -2
  20. package/elizaos/advanced_planning/actions/schedule_follow_up.py +2 -2
  21. package/elizaos/basic_capabilities/providers/contacts.py +1 -1
  22. package/elizaos/basic_capabilities/providers/follow_ups.py +1 -1
  23. package/elizaos/basic_capabilities/providers/knowledge.py +8 -9
  24. package/elizaos/basic_capabilities/providers/recent_messages.py +5 -0
  25. package/elizaos/basic_capabilities/providers/relationships.py +19 -13
  26. package/elizaos/basic_capabilities/services/embedding.py +10 -7
  27. package/elizaos/basic_capabilities/services/task.py +3 -3
  28. package/elizaos/bootstrap/actions/__init__.py +3 -0
  29. package/elizaos/bootstrap/actions/reset_session.py +3 -0
  30. package/elizaos/bootstrap/actions/roles.py +5 -4
  31. package/elizaos/bootstrap/providers/knowledge.py +8 -9
  32. package/elizaos/bootstrap/providers/recent_messages.py +5 -0
  33. package/elizaos/bootstrap/providers/relationships.py +19 -13
  34. package/elizaos/bootstrap/services/embedding.py +53 -10
  35. package/elizaos/bootstrap/services/rolodex.py +2 -2
  36. package/elizaos/bootstrap/services/task.py +3 -3
  37. package/elizaos/media/mime.py +2 -2
  38. package/elizaos/runtime.py +10 -9
  39. package/elizaos/services/hook_service.py +3 -3
  40. package/elizaos/types/generated/eliza/v1/agent_pb2.py +4 -4
  41. package/elizaos/types/generated/eliza/v1/components_pb2.py +4 -4
  42. package/elizaos/types/generated/eliza/v1/database_pb2.py +4 -4
  43. package/elizaos/types/generated/eliza/v1/environment_pb2.py +4 -4
  44. package/elizaos/types/generated/eliza/v1/events_pb2.py +4 -4
  45. package/elizaos/types/generated/eliza/v1/ipc_pb2.py +4 -4
  46. package/elizaos/types/generated/eliza/v1/knowledge_pb2.py +4 -4
  47. package/elizaos/types/generated/eliza/v1/memory_pb2.py +4 -4
  48. package/elizaos/types/generated/eliza/v1/message_service_pb2.py +4 -4
  49. package/elizaos/types/generated/eliza/v1/messaging_pb2.py +4 -4
  50. package/elizaos/types/generated/eliza/v1/model_pb2.py +4 -4
  51. package/elizaos/types/generated/eliza/v1/payment_pb2.py +4 -4
  52. package/elizaos/types/generated/eliza/v1/plugin_pb2.py +4 -4
  53. package/elizaos/types/generated/eliza/v1/primitives_pb2.py +4 -4
  54. package/elizaos/types/generated/eliza/v1/prompts_pb2.py +4 -4
  55. package/elizaos/types/generated/eliza/v1/service_interfaces_pb2.py +4 -4
  56. package/elizaos/types/generated/eliza/v1/service_pb2.py +4 -4
  57. package/elizaos/types/generated/eliza/v1/settings_pb2.py +4 -4
  58. package/elizaos/types/generated/eliza/v1/state_pb2.py +4 -4
  59. package/elizaos/types/generated/eliza/v1/task_pb2.py +4 -4
  60. package/elizaos/types/generated/eliza/v1/tee_pb2.py +4 -4
  61. package/elizaos/types/generated/eliza/v1/testing_pb2.py +4 -4
  62. package/elizaos/types/model.py +3 -3
  63. package/elizaos/types/primitives.py +3 -3
  64. package/elizaos/types/runtime.py +12 -2
  65. package/elizaos/types/state.py +2 -2
  66. package/elizaos/utils/streaming.py +3 -3
  67. package/package.json +3 -3
  68. package/pyproject.toml +1 -2
  69. package/requirements-dev.lock +2 -2
  70. package/requirements.in +1 -2
  71. package/requirements.lock +2 -2
  72. package/tests/test_history_compaction.py +104 -0
  73. package/tests/test_memory_bounds.py +115 -0
  74. package/tests/test_validation.py +1 -1
  75. package/uv.lock +10 -10
@@ -30,7 +30,7 @@ async def get_contacts_context(
30
30
 
31
31
  contact_details: list[dict[str, str]] = []
32
32
  for contact in contacts:
33
- entity = await runtime.get_entity(contact.entity_id)
33
+ entity = await runtime.get_entity(str(contact.entity_id))
34
34
  name = entity.name if entity and entity.name else "Unknown"
35
35
  contact_details.append(
36
36
  {
@@ -38,7 +38,7 @@ async def get_follow_ups_context(
38
38
 
39
39
  for task in upcoming:
40
40
  scheduled = datetime.fromisoformat(task.scheduled_at.replace("Z", "+00:00"))
41
- entity = await runtime.get_entity(task.entity_id)
41
+ entity = await runtime.get_entity(str(task.entity_id))
42
42
  name = entity.name if entity and entity.name else "Unknown"
43
43
 
44
44
  item = {
@@ -4,7 +4,6 @@ from typing import TYPE_CHECKING
4
4
 
5
5
  from elizaos.generated.spec_helpers import require_provider_spec
6
6
  from elizaos.types import Provider, ProviderResult
7
- from elizaos.types.database import MemorySearchOptions
8
7
 
9
8
  if TYPE_CHECKING:
10
9
  from elizaos.types import IAgentRuntime, Memory, State
@@ -42,14 +41,14 @@ async def get_knowledge_context(
42
41
  # 3. Search using the most recent embedding if available
43
42
  if embeddings:
44
43
  primary_embedding = embeddings[0]
45
- params = MemorySearchOptions(
46
- table_name="knowledge",
47
- room_id=message.room_id,
48
- embedding=primary_embedding,
49
- match_threshold=0.75,
50
- match_count=5,
51
- unique=True,
52
- )
44
+ params = {
45
+ "tableName": "knowledge",
46
+ "roomId": str(message.room_id),
47
+ "embedding": primary_embedding,
48
+ "matchThreshold": 0.75,
49
+ "matchCount": 5,
50
+ "unique": True,
51
+ }
53
52
  relevant_knowledge = await runtime.search_memories(params)
54
53
  elif query_text:
55
54
  # Fallback to search_knowledge if no embeddings found?
@@ -25,12 +25,17 @@ async def get_recent_messages_context(
25
25
 
26
26
  sections: list[str] = []
27
27
  message_list: list[dict[str, str | int]] = []
28
+ room = await runtime.get_room(room_id)
29
+ last_compaction_at = None
30
+ if room and getattr(room, "metadata", None):
31
+ last_compaction_at = room.metadata.get("lastCompactionAt")
28
32
 
29
33
  recent_messages = await runtime.get_memories(
30
34
  room_id=room_id,
31
35
  limit=20,
32
36
  order_by="created_at",
33
37
  order_direction="desc",
38
+ start=last_compaction_at,
34
39
  )
35
40
 
36
41
  recent_messages = list(reversed(recent_messages))
@@ -39,11 +39,10 @@ async def get_relationships(
39
39
  )
40
40
 
41
41
  try:
42
- relationships = await runtime.get_relationships(entity_id=entity_id)
42
+ relationships = await runtime.get_relationships({"entityId": str(entity_id)})
43
43
  except Exception as e:
44
44
  runtime.logger.debug(
45
- {"src": "provider:relationships", "agentId": runtime.agent_id, "error": str(e)},
46
- "Failed to get relationships",
45
+ f"Failed to get relationships: src=provider:relationships agentId={runtime.agent_id} error={e}"
47
46
  )
48
47
  relationships = []
49
48
 
@@ -54,24 +53,31 @@ async def get_relationships(
54
53
  data={"relationships": []},
55
54
  )
56
55
 
57
- sorted_relationships = sorted(
58
- relationships,
59
- key=lambda r: (r.get("metadata", {}) or {}).get("interactions", 0),
60
- reverse=True,
61
- )[:30]
56
+ def _get_interactions(relationship: object) -> int:
57
+ if isinstance(relationship, dict):
58
+ metadata = relationship.get("metadata", {})
59
+ if isinstance(metadata, dict):
60
+ value = metadata.get("interactions", 0)
61
+ return int(value) if isinstance(value, (int, float)) else 0
62
+ return 0
63
+
64
+ sorted_relationships = sorted(relationships, key=_get_interactions, reverse=True)[:30]
62
65
 
63
66
  formatted_relationships: list[str] = []
64
- entity_cache: dict[object, str] = {}
67
+ entity_cache: dict[str, str] = {}
65
68
  for rel in sorted_relationships:
69
+ if not isinstance(rel, dict):
70
+ continue
66
71
  target_id = rel.get("targetEntityId")
67
72
  if not target_id:
68
73
  continue
69
74
 
70
- target_name = entity_cache.get(target_id)
75
+ target_id_str = str(target_id)
76
+ target_name = entity_cache.get(target_id_str)
71
77
  if target_name is None:
72
- target_entity = await runtime.get_entity(target_id)
73
- target_name = target_entity.name if target_entity else str(target_id)[:8]
74
- entity_cache[target_id] = target_name
78
+ target_entity = await runtime.get_entity(target_id_str)
79
+ target_name = target_entity.name if target_entity else target_id_str[:8]
80
+ entity_cache[target_id_str] = target_name
75
81
 
76
82
  formatted_relationships.append(format_relationship(rel, target_name))
77
83
 
@@ -1,5 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
+ from collections import OrderedDict
3
4
  from typing import TYPE_CHECKING
4
5
 
5
6
  from elizaos.types import ModelType, Service, ServiceType
@@ -18,7 +19,7 @@ class EmbeddingService(Service):
18
19
 
19
20
  def __init__(self) -> None:
20
21
  self._runtime: IAgentRuntime | None = None
21
- self._cache: dict[str, list[float]] = {}
22
+ self._cache: OrderedDict[str, list[float]] = OrderedDict()
22
23
  self._cache_enabled: bool = True
23
24
  self._max_cache_size: int = 1000
24
25
 
@@ -51,7 +52,9 @@ class EmbeddingService(Service):
51
52
  raise ValueError("Embedding service not started - no runtime available")
52
53
 
53
54
  if self._cache_enabled and text in self._cache:
54
- return self._cache[text]
55
+ embedding = self._cache.pop(text)
56
+ self._cache[text] = embedding
57
+ return embedding
55
58
 
56
59
  # Truncate to stay within embedding model token limits
57
60
  embed_text = text
@@ -87,9 +90,10 @@ class EmbeddingService(Service):
87
90
  return embeddings
88
91
 
89
92
  def _add_to_cache(self, text: str, embedding: list[float]) -> None:
90
- if len(self._cache) >= self._max_cache_size:
91
- oldest_key = next(iter(self._cache))
92
- del self._cache[oldest_key]
93
+ if text in self._cache:
94
+ self._cache.pop(text)
95
+ elif len(self._cache) >= self._max_cache_size:
96
+ self._cache.popitem(last=False)
93
97
  self._cache[text] = embedding
94
98
 
95
99
  def clear_cache(self) -> None:
@@ -105,8 +109,7 @@ class EmbeddingService(Service):
105
109
  raise ValueError("Cache size must be positive")
106
110
  self._max_cache_size = size
107
111
  while len(self._cache) > self._max_cache_size:
108
- oldest_key = next(iter(self._cache))
109
- del self._cache[oldest_key]
112
+ self._cache.popitem(last=False)
110
113
 
111
114
  async def similarity(self, text1: str, text2: str) -> float:
112
115
  embedding1 = await self.embed(text1)
@@ -2,7 +2,7 @@ from __future__ import annotations
2
2
 
3
3
  from dataclasses import dataclass, field
4
4
  from datetime import UTC, datetime
5
- from enum import Enum
5
+ from enum import StrEnum
6
6
  from typing import TYPE_CHECKING
7
7
  from uuid import UUID, uuid4
8
8
 
@@ -12,7 +12,7 @@ if TYPE_CHECKING:
12
12
  from elizaos.types import IAgentRuntime
13
13
 
14
14
 
15
- class TaskStatus(str, Enum):
15
+ class TaskStatus(StrEnum):
16
16
  PENDING = "PENDING"
17
17
  IN_PROGRESS = "IN_PROGRESS"
18
18
  COMPLETED = "COMPLETED"
@@ -20,7 +20,7 @@ class TaskStatus(str, Enum):
20
20
  CANCELLED = "CANCELLED"
21
21
 
22
22
 
23
- class TaskPriority(str, Enum):
23
+ class TaskPriority(StrEnum):
24
24
  LOW = "LOW"
25
25
  MEDIUM = "MEDIUM"
26
26
  HIGH = "HIGH"
@@ -7,6 +7,7 @@ from .mute_room import mute_room_action
7
7
  from .none import none_action
8
8
  from .remove_contact import remove_contact_action
9
9
  from .reply import reply_action
10
+ from .reset_session import reset_session_action
10
11
  from .roles import update_role_action
11
12
  from .schedule_follow_up import schedule_follow_up_action
12
13
  from .search_contacts import search_contacts_action
@@ -26,6 +27,7 @@ __all__ = [
26
27
  "mute_room_action",
27
28
  "none_action",
28
29
  "remove_contact_action",
30
+ "reset_session_action",
29
31
  "reply_action",
30
32
  "schedule_follow_up_action",
31
33
  "search_contacts_action",
@@ -54,6 +56,7 @@ EXTENDED_ACTIONS = [
54
56
  generate_image_action,
55
57
  mute_room_action,
56
58
  remove_contact_action,
59
+ reset_session_action,
57
60
  schedule_follow_up_action,
58
61
  search_contacts_action,
59
62
  send_message_action,
@@ -0,0 +1,3 @@
1
+ from elizaos.advanced_memory.actions.reset_session import reset_session_action
2
+
3
+ __all__ = ["reset_session_action"]
@@ -1,12 +1,13 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  from dataclasses import dataclass, field
4
- from enum import Enum
5
- from typing import TYPE_CHECKING
4
+ from enum import StrEnum
5
+ from typing import TYPE_CHECKING, Any, cast
6
6
  from uuid import UUID
7
7
 
8
8
  from elizaos.bootstrap.utils.xml import parse_key_value_xml
9
9
  from elizaos.generated.spec_helpers import require_action_spec
10
+ from elizaos.prompts import UPDATE_ROLE_TEMPLATE
10
11
  from elizaos.types import Action, ActionExample, ActionResult, Content, ModelType
11
12
 
12
13
  if TYPE_CHECKING:
@@ -18,7 +19,7 @@ _spec = require_action_spec("UPDATE_ROLE")
18
19
 
19
20
  def _convert_spec_examples() -> list[list[ActionExample]]:
20
21
  """Convert spec examples to ActionExample format."""
21
- spec_examples = _spec.get("examples", [])
22
+ spec_examples = cast(list[list[dict[str, Any]]], _spec.get("examples", []))
22
23
  if spec_examples:
23
24
  return [
24
25
  [
@@ -36,7 +37,7 @@ def _convert_spec_examples() -> list[list[ActionExample]]:
36
37
  return []
37
38
 
38
39
 
39
- class Role(str, Enum):
40
+ class Role(StrEnum):
40
41
  OWNER = "OWNER"
41
42
  ADMIN = "ADMIN"
42
43
  MEMBER = "MEMBER"
@@ -4,7 +4,6 @@ from typing import TYPE_CHECKING
4
4
 
5
5
  from elizaos.generated.spec_helpers import require_provider_spec
6
6
  from elizaos.types import Provider, ProviderResult
7
- from elizaos.types.database import MemorySearchOptions
8
7
 
9
8
  if TYPE_CHECKING:
10
9
  from elizaos.types import IAgentRuntime, Memory, State
@@ -42,14 +41,14 @@ async def get_knowledge_context(
42
41
  # 3. Search using the most recent embedding if available
43
42
  if embeddings:
44
43
  primary_embedding = embeddings[0]
45
- params = MemorySearchOptions(
46
- table_name="knowledge",
47
- room_id=message.room_id,
48
- embedding=primary_embedding,
49
- match_threshold=0.75,
50
- match_count=5,
51
- unique=True,
52
- )
44
+ params = {
45
+ "tableName": "knowledge",
46
+ "roomId": str(message.room_id),
47
+ "embedding": primary_embedding,
48
+ "matchThreshold": 0.75,
49
+ "matchCount": 5,
50
+ "unique": True,
51
+ }
53
52
  relevant_knowledge = await runtime.search_memories(params)
54
53
  elif query_text:
55
54
  # Fallback to search_knowledge if no embeddings found?
@@ -25,12 +25,17 @@ async def get_recent_messages_context(
25
25
 
26
26
  sections: list[str] = []
27
27
  message_list: list[dict[str, str | int]] = []
28
+ room = await runtime.get_room(room_id)
29
+ last_compaction_at = None
30
+ if room and getattr(room, "metadata", None):
31
+ last_compaction_at = room.metadata.get("lastCompactionAt")
28
32
 
29
33
  recent_messages = await runtime.get_memories(
30
34
  room_id=room_id,
31
35
  limit=20,
32
36
  order_by="created_at",
33
37
  order_direction="desc",
38
+ start=last_compaction_at,
34
39
  )
35
40
 
36
41
  recent_messages = list(reversed(recent_messages))
@@ -39,11 +39,10 @@ async def get_relationships(
39
39
  )
40
40
 
41
41
  try:
42
- relationships = await runtime.get_relationships(entity_id=entity_id)
42
+ relationships = await runtime.get_relationships({"entityId": str(entity_id)})
43
43
  except Exception as e:
44
44
  runtime.logger.debug(
45
- {"src": "provider:relationships", "agentId": runtime.agent_id, "error": str(e)},
46
- "Failed to get relationships",
45
+ f"Failed to get relationships: src=provider:relationships agentId={runtime.agent_id} error={e}"
47
46
  )
48
47
  relationships = []
49
48
 
@@ -54,24 +53,31 @@ async def get_relationships(
54
53
  data={"relationships": []},
55
54
  )
56
55
 
57
- sorted_relationships = sorted(
58
- relationships,
59
- key=lambda r: (r.get("metadata", {}) or {}).get("interactions", 0),
60
- reverse=True,
61
- )[:30]
56
+ def _get_interactions(relationship: object) -> int:
57
+ if isinstance(relationship, dict):
58
+ metadata = relationship.get("metadata", {})
59
+ if isinstance(metadata, dict):
60
+ value = metadata.get("interactions", 0)
61
+ return int(value) if isinstance(value, (int, float)) else 0
62
+ return 0
63
+
64
+ sorted_relationships = sorted(relationships, key=_get_interactions, reverse=True)[:30]
62
65
 
63
66
  formatted_relationships: list[str] = []
64
- entity_cache: dict[object, str] = {}
67
+ entity_cache: dict[str, str] = {}
65
68
  for rel in sorted_relationships:
69
+ if not isinstance(rel, dict):
70
+ continue
66
71
  target_id = rel.get("targetEntityId")
67
72
  if not target_id:
68
73
  continue
69
74
 
70
- target_name = entity_cache.get(target_id)
75
+ target_id_str = str(target_id)
76
+ target_name = entity_cache.get(target_id_str)
71
77
  if target_name is None:
72
- target_entity = await runtime.get_entity(target_id)
73
- target_name = target_entity.name if target_entity else str(target_id)[:8]
74
- entity_cache[target_id] = target_name
78
+ target_entity = await runtime.get_entity(target_id_str)
79
+ target_name = target_entity.name if target_entity else target_id_str[:8]
80
+ entity_cache[target_id_str] = target_name
75
81
 
76
82
  formatted_relationships.append(format_relationship(rel, target_name))
77
83
 
@@ -2,6 +2,7 @@ from __future__ import annotations
2
2
 
3
3
  import asyncio
4
4
  import contextlib
5
+ from collections import OrderedDict
5
6
  from typing import TYPE_CHECKING, Any
6
7
 
7
8
  from elizaos.types import ModelType, Service, ServiceType
@@ -21,10 +22,14 @@ class EmbeddingService(Service):
21
22
 
22
23
  def __init__(self) -> None:
23
24
  self._runtime: IAgentRuntime | None = None
24
- self._cache: dict[str, list[float]] = {}
25
+ self._cache: OrderedDict[str, list[float]] = OrderedDict()
25
26
  self._cache_enabled: bool = True
26
27
  self._max_cache_size: int = 1000
27
- self._queue: asyncio.Queue = asyncio.Queue()
28
+ self._queue_max_size: int = 1000
29
+ self._queue: asyncio.Queue[tuple[str | None, Any]] = asyncio.Queue(
30
+ maxsize=self._queue_max_size
31
+ )
32
+ self._pending_payload_keys: set[str] = set()
28
33
  self._worker_task: asyncio.Task | None = None
29
34
 
30
35
  @classmethod
@@ -60,6 +65,8 @@ class EmbeddingService(Service):
60
65
  agentId=str(self._runtime.agent_id),
61
66
  )
62
67
  self._cache.clear()
68
+ self._pending_payload_keys.clear()
69
+ self._queue = asyncio.Queue(maxsize=self._queue_max_size)
63
70
  self._runtime = None
64
71
 
65
72
  # Max characters for embedding input (~8K tokens at ~4 chars/token)
@@ -70,7 +77,9 @@ class EmbeddingService(Service):
70
77
  raise ValueError("Embedding service not started - no runtime available")
71
78
 
72
79
  if self._cache_enabled and text in self._cache:
73
- return self._cache[text]
80
+ embedding = self._cache.pop(text)
81
+ self._cache[text] = embedding
82
+ return embedding
74
83
 
75
84
  # Truncate to stay within embedding model token limits
76
85
  embed_text = text
@@ -106,9 +115,10 @@ class EmbeddingService(Service):
106
115
  return embeddings
107
116
 
108
117
  def _add_to_cache(self, text: str, embedding: list[float]) -> None:
109
- if len(self._cache) >= self._max_cache_size:
110
- oldest_key = next(iter(self._cache))
111
- del self._cache[oldest_key]
118
+ if text in self._cache:
119
+ self._cache.pop(text)
120
+ elif len(self._cache) >= self._max_cache_size:
121
+ self._cache.popitem(last=False)
112
122
  self._cache[text] = embedding
113
123
 
114
124
  def clear_cache(self) -> None:
@@ -124,8 +134,7 @@ class EmbeddingService(Service):
124
134
  raise ValueError("Cache size must be positive")
125
135
  self._max_cache_size = size
126
136
  while len(self._cache) > self._max_cache_size:
127
- oldest_key = next(iter(self._cache))
128
- del self._cache[oldest_key]
137
+ self._cache.popitem(last=False)
129
138
 
130
139
  async def similarity(self, text1: str, text2: str) -> float:
131
140
  embedding1 = await self.embed(text1)
@@ -142,13 +151,45 @@ class EmbeddingService(Service):
142
151
 
143
152
  async def _handle_embedding_request(self, payload: Any) -> None:
144
153
  """Handle embedding generation request event."""
145
- await self._queue.put(payload)
154
+ payload_key = self._get_payload_key(payload)
155
+ if payload_key is not None:
156
+ if payload_key in self._pending_payload_keys:
157
+ return
158
+ self._pending_payload_keys.add(payload_key)
159
+
160
+ try:
161
+ await self._queue.put((payload_key, payload))
162
+ except Exception:
163
+ if payload_key is not None:
164
+ self._pending_payload_keys.discard(payload_key)
165
+ raise
166
+
167
+ def _get_payload_key(self, payload: Any) -> str | None:
168
+ memory_data = getattr(payload, "memory", None)
169
+ if memory_data is None:
170
+ extra = getattr(payload, "extra", None)
171
+ if hasattr(extra, "__getitem__"):
172
+ with contextlib.suppress(Exception):
173
+ if "memory" in extra:
174
+ memory_data = extra["memory"]
175
+ if memory_data is None and isinstance(payload, dict):
176
+ memory_data = payload.get("memory")
177
+ if memory_data is None:
178
+ return None
179
+
180
+ if isinstance(memory_data, dict):
181
+ memory_id = memory_data.get("id")
182
+ else:
183
+ memory_id = getattr(memory_data, "id", None)
184
+ if memory_id is None:
185
+ return None
186
+ return str(memory_id)
146
187
 
147
188
  async def _worker(self) -> None:
148
189
  """Background worker for processing embedding requests."""
149
190
  while True:
150
191
  try:
151
- payload = await self._queue.get()
192
+ payload_key, payload = await self._queue.get()
152
193
  except asyncio.CancelledError:
153
194
  break
154
195
 
@@ -158,6 +199,8 @@ class EmbeddingService(Service):
158
199
  if self._runtime:
159
200
  self._runtime.logger.error(f"Error in embedding worker: {e}", exc_info=True)
160
201
  finally:
202
+ if payload_key is not None:
203
+ self._pending_payload_keys.discard(payload_key)
161
204
  self._queue.task_done()
162
205
 
163
206
  async def _process_embedding_request(self, payload: Any) -> None:
@@ -2,7 +2,7 @@ from __future__ import annotations
2
2
 
3
3
  from dataclasses import dataclass, field
4
4
  from datetime import datetime
5
- from enum import Enum
5
+ from enum import StrEnum
6
6
  from typing import TYPE_CHECKING
7
7
  from uuid import UUID
8
8
 
@@ -12,7 +12,7 @@ if TYPE_CHECKING:
12
12
  from elizaos.types import IAgentRuntime
13
13
 
14
14
 
15
- class ContactCategory(str, Enum):
15
+ class ContactCategory(StrEnum):
16
16
  FRIEND = "friend"
17
17
  FAMILY = "family"
18
18
  COLLEAGUE = "colleague"
@@ -14,7 +14,7 @@ import asyncio
14
14
  import contextlib
15
15
  import time
16
16
  from dataclasses import dataclass
17
- from enum import Enum
17
+ from enum import StrEnum
18
18
  from typing import TYPE_CHECKING, Any, Protocol, runtime_checkable
19
19
  from uuid import UUID, uuid4
20
20
 
@@ -27,7 +27,7 @@ if TYPE_CHECKING:
27
27
  TICK_INTERVAL_MS = 1000
28
28
 
29
29
 
30
- class TaskStatus(str, Enum):
30
+ class TaskStatus(StrEnum):
31
31
  """Task status enum."""
32
32
 
33
33
  PENDING = "pending"
@@ -37,7 +37,7 @@ class TaskStatus(str, Enum):
37
37
  CANCELLED = "cancelled"
38
38
 
39
39
 
40
- class TaskPriority(str, Enum):
40
+ class TaskPriority(StrEnum):
41
41
  """Task priority enum."""
42
42
 
43
43
  LOW = "low"
@@ -7,7 +7,7 @@ Provides robust MIME type detection from file buffers, headers, and extensions.
7
7
  from __future__ import annotations
8
8
 
9
9
  import re
10
- from enum import Enum
10
+ from enum import StrEnum
11
11
  from urllib.parse import urlparse
12
12
 
13
13
  # Try to import python-magic for MIME sniffing, fallback to filetype
@@ -26,7 +26,7 @@ except ImportError:
26
26
  HAS_FILETYPE = False
27
27
 
28
28
 
29
- class MediaKind(str, Enum):
29
+ class MediaKind(StrEnum):
30
30
  """Media kind categories."""
31
31
 
32
32
  IMAGE = "image"
@@ -1680,11 +1680,11 @@ class AgentRuntime(IAgentRuntime):
1680
1680
  if self._adapter:
1681
1681
  await self._adapter.ensure_embedding_dimension(dimension)
1682
1682
 
1683
- async def get_entity(self, entity_id: UUID) -> Any | None:
1683
+ async def get_entity(self, entity_id: UUID | str) -> Any | None:
1684
1684
  """Get a single entity by ID."""
1685
1685
  if not self._adapter:
1686
1686
  return None
1687
- entities = await self._adapter.get_entities_by_ids([entity_id])
1687
+ entities = await self._adapter.get_entities_by_ids([str(entity_id)])
1688
1688
  return entities[0] if entities else None
1689
1689
 
1690
1690
  async def get_entities_by_ids(self, entity_ids: list[UUID]) -> list[Any] | None:
@@ -1744,7 +1744,7 @@ class AgentRuntime(IAgentRuntime):
1744
1744
  if self._adapter:
1745
1745
  await self._adapter.delete_component(component_id)
1746
1746
 
1747
- async def search_memories(self, params: MemorySearchOptions) -> list[Memory]:
1747
+ async def search_memories(self, params: MemorySearchOptions | dict[str, Any]) -> list[Memory]:
1748
1748
  """Search memories by embedding."""
1749
1749
  if not self._adapter:
1750
1750
  raise RuntimeError("Database adapter not set")
@@ -1828,11 +1828,6 @@ class AgentRuntime(IAgentRuntime):
1828
1828
  return AgentRunSummaryResult(runs=[], total=0, has_more=False)
1829
1829
  return await self._adapter.get_agent_run_summaries(params)
1830
1830
 
1831
- async def search_memories(self, params: dict[str, Any]) -> list[Any]:
1832
- if not self._adapter:
1833
- return []
1834
- return await self._adapter.search_memories(params)
1835
-
1836
1831
  async def create_memory(
1837
1832
  self,
1838
1833
  memory: dict[str, object] | None = None,
@@ -2412,6 +2407,12 @@ end code: {final_code}
2412
2407
  step_id = CURRENT_TRAJECTORY_STEP_ID.get()
2413
2408
  traj_svc = self.get_service("trajectory_logger")
2414
2409
  if step_id and traj_svc is not None and hasattr(traj_svc, "log_llm_call"):
2410
+ max_tokens_value = params.get("maxTokens", 0)
2411
+ max_tokens = (
2412
+ int(max_tokens_value)
2413
+ if isinstance(max_tokens_value, (int, float, str, bytes, bytearray))
2414
+ else 0
2415
+ )
2415
2416
  traj_svc.log_llm_call( # type: ignore[call-arg]
2416
2417
  step_id=step_id,
2417
2418
  model=stream_model_type,
@@ -2419,7 +2420,7 @@ end code: {final_code}
2419
2420
  user_prompt=str(params.get("prompt", ""))[:2000],
2420
2421
  response=response_str[:2000],
2421
2422
  temperature=0.0,
2422
- max_tokens=int(params.get("maxTokens", 0)),
2423
+ max_tokens=max_tokens,
2423
2424
  purpose="action",
2424
2425
  action_type="dynamic_prompt_exec.stream",
2425
2426
  latency_ms=0,
@@ -21,7 +21,7 @@ import uuid
21
21
  from collections.abc import Callable
22
22
  from dataclasses import dataclass, field
23
23
  from datetime import datetime
24
- from enum import Enum
24
+ from enum import StrEnum
25
25
  from typing import TYPE_CHECKING, Any
26
26
 
27
27
  from elizaos.types.service import Service, ServiceType
@@ -30,7 +30,7 @@ if TYPE_CHECKING:
30
30
  from elizaos.types.runtime import IAgentRuntime
31
31
 
32
32
 
33
- class HookSource(str, Enum):
33
+ class HookSource(StrEnum):
34
34
  """Source of a hook registration."""
35
35
 
36
36
  BUNDLED = "bundled"
@@ -40,7 +40,7 @@ class HookSource(str, Enum):
40
40
  RUNTIME = "runtime"
41
41
 
42
42
 
43
- class HookEventType(str, Enum):
43
+ class HookEventType(StrEnum):
44
44
  """Hook-specific event types."""
45
45
 
46
46
  HOOK_COMMAND_NEW = "HOOK_COMMAND_NEW"