@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
@@ -1,7 +1,8 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  from dataclasses import dataclass, field
4
- from typing import TYPE_CHECKING
4
+ from typing import TYPE_CHECKING, Any, cast
5
+ from uuid import UUID as StdUUID
5
6
 
6
7
  from elizaos.bootstrap.utils.xml import parse_key_value_xml
7
8
  from elizaos.generated.spec_helpers import require_action_spec
@@ -29,7 +30,7 @@ _spec = require_action_spec("ADD_CONTACT")
29
30
 
30
31
  def _convert_spec_examples() -> list[list[ActionExample]]:
31
32
  """Convert spec examples to ActionExample format."""
32
- spec_examples = _spec.get("examples", [])
33
+ spec_examples = cast(list[list[dict[str, Any]]], _spec.get("examples", []))
33
34
  if spec_examples:
34
35
  return [
35
36
  [
@@ -103,7 +104,7 @@ class AddContactAction:
103
104
  notes = str(parsed.get("notes", ""))
104
105
  reason = str(parsed.get("reason", ""))
105
106
 
106
- entity_id = message.entity_id
107
+ entity_id = StdUUID(str(message.entity_id))
107
108
  preferences = ContactPreferences(notes=notes) if notes else None
108
109
 
109
110
  await rolodex_service.add_contact(
@@ -1,7 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  from dataclasses import dataclass, field
4
- from typing import TYPE_CHECKING
4
+ from typing import TYPE_CHECKING, Any, cast
5
5
 
6
6
  from elizaos.generated.spec_helpers import require_action_spec
7
7
  from elizaos.types import Action, ActionExample, ActionResult, Content
@@ -15,7 +15,7 @@ _spec = require_action_spec("FOLLOW_ROOM")
15
15
 
16
16
  def _convert_spec_examples() -> list[list[ActionExample]]:
17
17
  """Convert spec examples to ActionExample format."""
18
- spec_examples = _spec.get("examples", [])
18
+ spec_examples = cast(list[list[dict[str, Any]]], _spec.get("examples", []))
19
19
  if spec_examples:
20
20
  return [
21
21
  [
@@ -1,7 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  from dataclasses import dataclass, field
4
- from typing import TYPE_CHECKING
4
+ from typing import TYPE_CHECKING, Any, cast
5
5
 
6
6
  from elizaos.bootstrap.utils.xml import parse_key_value_xml
7
7
  from elizaos.generated.spec_helpers import require_action_spec
@@ -17,7 +17,7 @@ _spec = require_action_spec("GENERATE_IMAGE")
17
17
 
18
18
  def _convert_spec_examples() -> list[list[ActionExample]]:
19
19
  """Convert spec examples to ActionExample format."""
20
- spec_examples = _spec.get("examples", [])
20
+ spec_examples = cast(list[list[dict[str, Any]]], _spec.get("examples", []))
21
21
  if spec_examples:
22
22
  return [
23
23
  [
@@ -1,7 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  from dataclasses import dataclass, field
4
- from typing import TYPE_CHECKING
4
+ from typing import TYPE_CHECKING, Any, cast
5
5
 
6
6
  from elizaos.generated.spec_helpers import require_action_spec
7
7
  from elizaos.types import Action, ActionExample, ActionResult, Content
@@ -15,7 +15,7 @@ _spec = require_action_spec("MUTE_ROOM")
15
15
 
16
16
  def _convert_spec_examples() -> list[list[ActionExample]]:
17
17
  """Convert spec examples to ActionExample format."""
18
- spec_examples = _spec.get("examples", [])
18
+ spec_examples = cast(list[list[dict[str, Any]]], _spec.get("examples", []))
19
19
  if spec_examples:
20
20
  return [
21
21
  [
@@ -1,7 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  from dataclasses import dataclass, field
4
- from typing import TYPE_CHECKING
4
+ from typing import TYPE_CHECKING, Any, cast
5
5
 
6
6
  from elizaos.bootstrap.utils.xml import parse_key_value_xml
7
7
  from elizaos.generated.spec_helpers import require_action_spec
@@ -29,7 +29,7 @@ _spec = require_action_spec("REMOVE_CONTACT")
29
29
 
30
30
  def _convert_spec_examples() -> list[list[ActionExample]]:
31
31
  """Convert spec examples to ActionExample format."""
32
- spec_examples = _spec.get("examples", [])
32
+ spec_examples = cast(list[list[dict[str, Any]]], _spec.get("examples", []))
33
33
  if spec_examples:
34
34
  return [
35
35
  [
@@ -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"
@@ -1,7 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  from dataclasses import dataclass, field
4
- from typing import TYPE_CHECKING
4
+ from typing import TYPE_CHECKING, Any, cast
5
5
 
6
6
  from elizaos.bootstrap.utils.xml import parse_key_value_xml
7
7
  from elizaos.generated.spec_helpers import require_action_spec
@@ -29,7 +29,7 @@ _spec = require_action_spec("SEARCH_CONTACTS")
29
29
 
30
30
  def _convert_spec_examples() -> list[list[ActionExample]]:
31
31
  """Convert spec examples to ActionExample format."""
32
- spec_examples = _spec.get("examples", [])
32
+ spec_examples = cast(list[list[dict[str, Any]]], _spec.get("examples", []))
33
33
  if spec_examples:
34
34
  return [
35
35
  [
@@ -109,7 +109,7 @@ class SearchContactsAction:
109
109
 
110
110
  contact_details: list[dict[str, str]] = []
111
111
  for contact in contacts:
112
- entity = await runtime.get_entity(contact.entity_id)
112
+ entity = await runtime.get_entity(str(contact.entity_id))
113
113
  name = entity.name if entity and entity.name else "Unknown"
114
114
  contact_details.append(
115
115
  {
@@ -2,7 +2,7 @@ from __future__ import annotations
2
2
 
3
3
  import contextlib
4
4
  from dataclasses import dataclass, field
5
- from typing import TYPE_CHECKING, Any
5
+ from typing import TYPE_CHECKING, Any, cast
6
6
 
7
7
  from elizaos.generated.spec_helpers import require_action_spec
8
8
  from elizaos.types import Action, ActionExample, ActionResult, Content
@@ -18,7 +18,7 @@ _spec = require_action_spec("SEND_MESSAGE")
18
18
 
19
19
  def _convert_spec_examples() -> list[list[ActionExample]]:
20
20
  """Convert spec examples to ActionExample format."""
21
- spec_examples = _spec.get("examples", [])
21
+ spec_examples = cast(list[list[dict[str, Any]]], _spec.get("examples", []))
22
22
  if spec_examples:
23
23
  return [
24
24
  [
@@ -1,7 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  from dataclasses import dataclass, field
4
- from typing import TYPE_CHECKING
4
+ from typing import TYPE_CHECKING, Any, cast
5
5
 
6
6
  from elizaos.bootstrap.utils.xml import parse_key_value_xml
7
7
  from elizaos.generated.spec_helpers import require_action_spec
@@ -17,7 +17,7 @@ _spec = require_action_spec("UPDATE_SETTINGS")
17
17
 
18
18
  def _convert_spec_examples() -> list[list[ActionExample]]:
19
19
  """Convert spec examples to ActionExample format."""
20
- spec_examples = _spec.get("examples", [])
20
+ spec_examples = cast(list[list[dict[str, Any]]], _spec.get("examples", []))
21
21
  if spec_examples:
22
22
  return [
23
23
  [
@@ -1,7 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  from dataclasses import dataclass, field
4
- from typing import TYPE_CHECKING
4
+ from typing import TYPE_CHECKING, Any, cast
5
5
 
6
6
  from elizaos.generated.spec_helpers import require_action_spec
7
7
  from elizaos.types import Action, ActionExample, ActionResult, Content
@@ -15,7 +15,7 @@ _spec = require_action_spec("UNFOLLOW_ROOM")
15
15
 
16
16
  def _convert_spec_examples() -> list[list[ActionExample]]:
17
17
  """Convert spec examples to ActionExample format."""
18
- spec_examples = _spec.get("examples", [])
18
+ spec_examples = cast(list[list[dict[str, Any]]], _spec.get("examples", []))
19
19
  if spec_examples:
20
20
  return [
21
21
  [
@@ -1,7 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  from dataclasses import dataclass, field
4
- from typing import TYPE_CHECKING
4
+ from typing import TYPE_CHECKING, Any, cast
5
5
 
6
6
  from elizaos.generated.spec_helpers import require_action_spec
7
7
  from elizaos.types import Action, ActionExample, ActionResult, Content
@@ -15,7 +15,7 @@ _spec = require_action_spec("UNMUTE_ROOM")
15
15
 
16
16
  def _convert_spec_examples() -> list[list[ActionExample]]:
17
17
  """Convert spec examples to ActionExample format."""
18
- spec_examples = _spec.get("examples", [])
18
+ spec_examples = cast(list[list[dict[str, Any]]], _spec.get("examples", []))
19
19
  if spec_examples:
20
20
  return [
21
21
  [
@@ -1,7 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  from dataclasses import dataclass, field
4
- from typing import TYPE_CHECKING
4
+ from typing import TYPE_CHECKING, Any, cast
5
5
 
6
6
  from elizaos.bootstrap.utils.xml import parse_key_value_xml
7
7
  from elizaos.generated.spec_helpers import require_action_spec
@@ -29,7 +29,7 @@ _spec = require_action_spec("UPDATE_CONTACT")
29
29
 
30
30
  def _convert_spec_examples() -> list[list[ActionExample]]:
31
31
  """Convert spec examples to ActionExample format."""
32
- spec_examples = _spec.get("examples", [])
32
+ spec_examples = cast(list[list[dict[str, Any]]], _spec.get("examples", []))
33
33
  if spec_examples:
34
34
  return [
35
35
  [
@@ -1,7 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  from dataclasses import dataclass, field
4
- from typing import TYPE_CHECKING
4
+ from typing import TYPE_CHECKING, Any, cast
5
5
  from uuid import UUID
6
6
 
7
7
  from elizaos.bootstrap.utils.xml import parse_key_value_xml
@@ -18,7 +18,7 @@ _spec = require_action_spec("UPDATE_ENTITY")
18
18
 
19
19
  def _convert_spec_examples() -> list[list[ActionExample]]:
20
20
  """Convert spec examples to ActionExample format."""
21
- spec_examples = _spec.get("examples", [])
21
+ spec_examples = cast(list[list[dict[str, Any]]], _spec.get("examples", []))
22
22
  if spec_examples:
23
23
  return [
24
24
  [
@@ -3,7 +3,6 @@ from __future__ import annotations
3
3
  from typing import TYPE_CHECKING
4
4
 
5
5
  from elizaos.types import Provider, ProviderResult
6
- from elizaos.types.database import MemorySearchOptions
7
6
 
8
7
  if TYPE_CHECKING:
9
8
  from elizaos.types import IAgentRuntime, Memory, State
@@ -38,14 +37,14 @@ async def get_knowledge_context(
38
37
  # 3. Search using the most recent embedding if available
39
38
  if embeddings:
40
39
  primary_embedding = embeddings[0]
41
- params = MemorySearchOptions(
42
- table_name="knowledge",
43
- room_id=message.room_id,
44
- embedding=primary_embedding,
45
- match_threshold=0.75,
46
- match_count=5,
47
- unique=True,
48
- )
40
+ params = {
41
+ "tableName": "knowledge",
42
+ "roomId": str(message.room_id),
43
+ "embedding": primary_embedding,
44
+ "matchThreshold": 0.75,
45
+ "matchCount": 5,
46
+ "unique": True,
47
+ }
49
48
  relevant_knowledge = await runtime.search_memories(params)
50
49
  elif query_text:
51
50
  # Fallback skipped for parity with TS/Bootstrap
@@ -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"
@@ -1,11 +1,143 @@
1
- from ...types import Action
2
-
3
- # TODO: Implement reset_session action
4
- reset_session_action: Action = {
5
- "name": "RESET_SESSION",
6
- "similes": [],
7
- "description": "Reset the session (Implementation TODO)",
8
- "handler": None,
9
- "validate": None,
10
- "examples": [],
11
- }
1
+ from __future__ import annotations
2
+
3
+ import time
4
+ from dataclasses import dataclass, field
5
+ from typing import TYPE_CHECKING
6
+
7
+ from elizaos.types import Action, ActionExample, ActionResult, Content
8
+
9
+ if TYPE_CHECKING:
10
+ from elizaos.types import HandlerCallback, HandlerOptions, IAgentRuntime, Memory, State
11
+
12
+
13
+ @dataclass
14
+ class ResetSessionAction:
15
+ name: str = "RESET_SESSION"
16
+ similes: list[str] = field(
17
+ default_factory=lambda: ["CLEAR_HISTORY", "NEW_SESSION", "FORGET", "START_OVER", "RESET"]
18
+ )
19
+ description: str = (
20
+ "Resets the conversation session by creating a compaction point. "
21
+ "Messages before this point will not be included in future context."
22
+ )
23
+
24
+ async def validate(
25
+ self, runtime: IAgentRuntime, message: Memory, state: State | None = None
26
+ ) -> bool:
27
+ room = None
28
+ if state and getattr(state, "data", None) and getattr(state.data, "room", None):
29
+ room = state.data.room
30
+ elif message.room_id:
31
+ room = await runtime.get_room(message.room_id)
32
+
33
+ if not room or not getattr(room, "world_id", None):
34
+ return True
35
+
36
+ world = await runtime.get_world(room.world_id)
37
+ if not world or not getattr(world, "metadata", None):
38
+ return False
39
+
40
+ roles = world.metadata.get("roles", {})
41
+ user_role = roles.get(str(message.entity_id), "NONE")
42
+ return user_role in ("OWNER", "ADMIN")
43
+
44
+ async def handler(
45
+ self,
46
+ runtime: IAgentRuntime,
47
+ message: Memory,
48
+ state: State | None = None,
49
+ options: HandlerOptions | None = None,
50
+ callback: HandlerCallback | None = None,
51
+ responses: list[Memory] | None = None,
52
+ ) -> ActionResult:
53
+ del options, responses
54
+
55
+ room = None
56
+ if state and getattr(state, "data", None) and getattr(state.data, "room", None):
57
+ room = state.data.room
58
+ elif message.room_id:
59
+ room = await runtime.get_room(message.room_id)
60
+
61
+ if room is None:
62
+ if callback:
63
+ await callback(
64
+ Content(
65
+ text="Unable to reset session - room not found.",
66
+ actions=["RESET_SESSION_FAILED"],
67
+ source=getattr(message.content, "source", None),
68
+ )
69
+ )
70
+ return ActionResult(
71
+ text="Room not found",
72
+ values={"error": "room_not_found"},
73
+ data={"actionName": "RESET_SESSION"},
74
+ success=False,
75
+ )
76
+
77
+ metadata = dict(getattr(room, "metadata", {}) or {})
78
+ previous_compaction = metadata.get("lastCompactionAt")
79
+ compaction_history = list(metadata.get("compactionHistory", []))
80
+ now = int(time.time() * 1000)
81
+
82
+ compaction_history.append(
83
+ {
84
+ "timestamp": now,
85
+ "triggeredBy": str(message.entity_id),
86
+ "reason": "manual_reset",
87
+ }
88
+ )
89
+ metadata["lastCompactionAt"] = now
90
+ metadata["compactionHistory"] = compaction_history[-10:]
91
+
92
+ if (
93
+ hasattr(room, "metadata")
94
+ and hasattr(room.metadata, "clear")
95
+ and hasattr(room.metadata, "update")
96
+ ):
97
+ room.metadata.clear()
98
+ room.metadata.update(metadata)
99
+ else:
100
+ room.metadata = metadata
101
+
102
+ await runtime.update_room(room)
103
+
104
+ if callback:
105
+ await callback(
106
+ Content(
107
+ text="Session has been reset. I'll start fresh from here.",
108
+ actions=["RESET_SESSION"],
109
+ source=getattr(message.content, "source", None),
110
+ )
111
+ )
112
+
113
+ result = ActionResult(
114
+ text="Session reset successfully",
115
+ values={
116
+ "success": True,
117
+ "compactionAt": now,
118
+ "roomId": str(room.id),
119
+ },
120
+ data={
121
+ "actionName": "RESET_SESSION",
122
+ "compactionAt": now,
123
+ "roomId": str(room.id),
124
+ },
125
+ success=True,
126
+ )
127
+ if previous_compaction is not None:
128
+ result.values["previousCompactionAt"] = previous_compaction
129
+ return result
130
+
131
+ @property
132
+ def examples(self) -> list[list[ActionExample]]:
133
+ return []
134
+
135
+
136
+ reset_session_action = Action(
137
+ name=ResetSessionAction.name,
138
+ similes=ResetSessionAction().similes,
139
+ description=ResetSessionAction.description,
140
+ validate=ResetSessionAction().validate,
141
+ handler=ResetSessionAction().handler,
142
+ examples=ResetSessionAction().examples,
143
+ )
@@ -3,7 +3,8 @@ from __future__ import annotations
3
3
  import heapq
4
4
  import re
5
5
  import time
6
- from typing import cast
6
+ from collections import OrderedDict
7
+ from typing import TypeVar, cast
7
8
  from uuid import UUID, uuid4
8
9
 
9
10
  from elizaos.types.model import ModelType
@@ -27,6 +28,7 @@ from .types import (
27
28
  _TABLE_SESSION_SUMMARY = "session_summary"
28
29
  _TABLE_LONG_TERM_MEMORY = "long_term_memory"
29
30
  _GLOBAL_LONG_TERM_ROOM_ID = string_to_uuid("advanced-memory:long-term")
31
+ _OrderedValueT = TypeVar("_OrderedValueT")
30
32
 
31
33
 
32
34
  def _parse_summary_xml(xml: str) -> SummaryResult:
@@ -79,14 +81,28 @@ def _top_k_by_confidence(items: list[LongTermMemory], limit: int) -> list[LongTe
79
81
 
80
82
  class MemoryService(Service):
81
83
  service_type = "memory"
84
+ _MAX_LOCAL_SESSION_SUMMARIES = 500
85
+ _MAX_LOCAL_EXTRACTION_CHECKPOINTS = 500
86
+ _MAX_LOCAL_LONG_TERM_ENTITIES = 500
87
+ _MAX_LOCAL_LONG_TERM_PER_ENTITY = 200
82
88
 
83
89
  def __init__(self, runtime=None) -> None:
84
90
  super().__init__(runtime=runtime)
85
91
  self._config: MemoryConfig = MemoryConfig()
86
92
  # Fallback storage for runtimes without a DB adapter (tests/benchmarks).
87
- self._session_summaries: dict[str, SessionSummary] = {}
88
- self._long_term: dict[str, list[LongTermMemory]] = {}
89
- self._extraction_checkpoints: dict[str, int] = {}
93
+ self._session_summaries: OrderedDict[str, SessionSummary] = OrderedDict()
94
+ self._long_term: OrderedDict[str, list[LongTermMemory]] = OrderedDict()
95
+ self._extraction_checkpoints: OrderedDict[str, int] = OrderedDict()
96
+
97
+ @staticmethod
98
+ def _touch_ordered(mapping: OrderedDict[str, _OrderedValueT], key: str) -> None:
99
+ if key in mapping:
100
+ mapping.move_to_end(key)
101
+
102
+ @staticmethod
103
+ def _prune_ordered(mapping: OrderedDict[str, _OrderedValueT], max_entries: int) -> None:
104
+ while len(mapping) > max_entries:
105
+ mapping.popitem(last=False)
90
106
 
91
107
  @property
92
108
  def capability_description(self) -> str:
@@ -156,7 +172,11 @@ class MemoryService(Service):
156
172
  return 0
157
173
  except Exception:
158
174
  return 0
159
- return int(self._extraction_checkpoints.get(key, 0))
175
+ checkpoint = self._extraction_checkpoints.get(key)
176
+ if checkpoint is None:
177
+ return 0
178
+ self._touch_ordered(self._extraction_checkpoints, key)
179
+ return int(checkpoint)
160
180
 
161
181
  async def set_last_extraction_checkpoint(
162
182
  self, entity_id: UUID, room_id: UUID, message_count: int
@@ -167,6 +187,8 @@ class MemoryService(Service):
167
187
  _ = await runtime.set_cache(key, int(message_count))
168
188
  return
169
189
  self._extraction_checkpoints[key] = int(message_count)
190
+ self._touch_ordered(self._extraction_checkpoints, key)
191
+ self._prune_ordered(self._extraction_checkpoints, self._MAX_LOCAL_EXTRACTION_CHECKPOINTS)
170
192
 
171
193
  async def should_run_extraction(
172
194
  self, entity_id: UUID, room_id: UUID, current_message_count: int
@@ -219,7 +241,11 @@ class MemoryService(Service):
219
241
  # Best-effort; ignore corrupt rows.
220
242
  continue
221
243
 
222
- return self._session_summaries.get(str(room_id))
244
+ summary = self._session_summaries.get(str(room_id))
245
+ if summary is None:
246
+ return None
247
+ self._touch_ordered(self._session_summaries, str(room_id))
248
+ return summary
223
249
 
224
250
  async def store_session_summary(
225
251
  self,
@@ -271,7 +297,10 @@ class MemoryService(Service):
271
297
  )
272
298
  return s
273
299
 
274
- self._session_summaries[str(room_id)] = s
300
+ room_key = str(room_id)
301
+ self._session_summaries[room_key] = s
302
+ self._touch_ordered(self._session_summaries, room_key)
303
+ self._prune_ordered(self._session_summaries, self._MAX_LOCAL_SESSION_SUMMARIES)
275
304
  return s
276
305
 
277
306
  async def update_session_summary(
@@ -322,9 +351,11 @@ class MemoryService(Service):
322
351
  )
323
352
  return
324
353
 
325
- existing = self._session_summaries.get(str(room_id))
354
+ room_key = str(room_id)
355
+ existing = self._session_summaries.get(room_key)
326
356
  if not existing or existing.id != summary_id:
327
357
  return
358
+ self._touch_ordered(self._session_summaries, room_key)
328
359
  if "summary" in updates and isinstance(updates["summary"], str):
329
360
  existing.summary = updates["summary"]
330
361
  if "message_count" in updates and isinstance(updates["message_count"], (int, float, str)):
@@ -381,7 +412,17 @@ class MemoryService(Service):
381
412
  )
382
413
  return m
383
414
 
384
- self._long_term.setdefault(str(entity_id), []).append(m)
415
+ entity_key = str(entity_id)
416
+ bucket = self._long_term.get(entity_key)
417
+ if bucket is None:
418
+ bucket = []
419
+ self._long_term[entity_key] = bucket
420
+ else:
421
+ self._touch_ordered(self._long_term, entity_key)
422
+ bucket.append(m)
423
+ if len(bucket) > self._MAX_LOCAL_LONG_TERM_PER_ENTITY:
424
+ del bucket[: len(bucket) - self._MAX_LOCAL_LONG_TERM_PER_ENTITY]
425
+ self._prune_ordered(self._long_term, self._MAX_LOCAL_LONG_TERM_ENTITIES)
385
426
  return m
386
427
 
387
428
  async def get_long_term_memories(
@@ -437,7 +478,10 @@ class MemoryService(Service):
437
478
  continue
438
479
  return _top_k_by_confidence(out, limit)
439
480
 
440
- local_mems = self._long_term.get(str(entity_id), [])
481
+ entity_key = str(entity_id)
482
+ local_mems = self._long_term.get(entity_key, [])
483
+ if entity_key in self._long_term:
484
+ self._touch_ordered(self._long_term, entity_key)
441
485
  if category is not None:
442
486
  local_mems = [m for m in local_mems if m.category == category]
443
487
  return _top_k_by_confidence(local_mems, limit)
@@ -2,6 +2,7 @@ from __future__ import annotations
2
2
 
3
3
  from elizaos.types import Plugin
4
4
 
5
+ from .actions.reset_session import reset_session_action
5
6
  from .evaluators import long_term_extraction_evaluator, summarization_evaluator
6
7
  from .memory_service import MemoryService
7
8
  from .providers import context_summary_provider, long_term_memory_provider
@@ -21,7 +22,7 @@ def create_advanced_memory_plugin() -> Plugin:
21
22
  init=init_plugin,
22
23
  config={},
23
24
  services=[MemoryService],
24
- actions=[],
25
+ actions=[reset_session_action],
25
26
  providers=[long_term_memory_provider, context_summary_provider],
26
27
  evaluators=[summarization_evaluator, long_term_extraction_evaluator],
27
28
  )
@@ -1,11 +1,11 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  from dataclasses import dataclass, field
4
- from enum import Enum
4
+ from enum import StrEnum
5
5
  from uuid import UUID
6
6
 
7
7
 
8
- class LongTermMemoryCategory(str, Enum):
8
+ class LongTermMemoryCategory(StrEnum):
9
9
  EPISODIC = "episodic"
10
10
  SEMANTIC = "semantic"
11
11
  PROCEDURAL = "procedural"
@@ -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 typing import TYPE_CHECKING
5
+ from typing import TYPE_CHECKING, Any, cast
6
6
  from uuid import UUID as StdUUID
7
7
 
8
8
  from elizaos.bootstrap.utils.xml import parse_key_value_xml
@@ -32,7 +32,7 @@ _spec = require_action_spec("SCHEDULE_FOLLOW_UP")
32
32
 
33
33
  def _convert_spec_examples() -> list[list[ActionExample]]:
34
34
  """Convert spec examples to ActionExample format."""
35
- spec_examples = _spec.get("examples", [])
35
+ spec_examples = cast(list[list[dict[str, Any]]], _spec.get("examples", []))
36
36
  if spec_examples:
37
37
  return [
38
38
  [