@elizaos/python 2.0.0-alpha.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +26 -0
- package/README.md +239 -0
- package/elizaos/__init__.py +280 -0
- package/elizaos/action_docs.py +149 -0
- package/elizaos/advanced_capabilities/__init__.py +85 -0
- package/elizaos/advanced_capabilities/actions/__init__.py +54 -0
- package/elizaos/advanced_capabilities/actions/add_contact.py +139 -0
- package/elizaos/advanced_capabilities/actions/follow_room.py +151 -0
- package/elizaos/advanced_capabilities/actions/image_generation.py +148 -0
- package/elizaos/advanced_capabilities/actions/mute_room.py +164 -0
- package/elizaos/advanced_capabilities/actions/remove_contact.py +145 -0
- package/elizaos/advanced_capabilities/actions/roles.py +207 -0
- package/elizaos/advanced_capabilities/actions/schedule_follow_up.py +154 -0
- package/elizaos/advanced_capabilities/actions/search_contacts.py +145 -0
- package/elizaos/advanced_capabilities/actions/send_message.py +187 -0
- package/elizaos/advanced_capabilities/actions/settings.py +151 -0
- package/elizaos/advanced_capabilities/actions/unfollow_room.py +164 -0
- package/elizaos/advanced_capabilities/actions/unmute_room.py +164 -0
- package/elizaos/advanced_capabilities/actions/update_contact.py +164 -0
- package/elizaos/advanced_capabilities/actions/update_entity.py +161 -0
- package/elizaos/advanced_capabilities/evaluators/__init__.py +18 -0
- package/elizaos/advanced_capabilities/evaluators/reflection.py +134 -0
- package/elizaos/advanced_capabilities/evaluators/relationship_extraction.py +203 -0
- package/elizaos/advanced_capabilities/providers/__init__.py +36 -0
- package/elizaos/advanced_capabilities/providers/agent_settings.py +60 -0
- package/elizaos/advanced_capabilities/providers/contacts.py +77 -0
- package/elizaos/advanced_capabilities/providers/facts.py +82 -0
- package/elizaos/advanced_capabilities/providers/follow_ups.py +113 -0
- package/elizaos/advanced_capabilities/providers/knowledge.py +83 -0
- package/elizaos/advanced_capabilities/providers/relationships.py +112 -0
- package/elizaos/advanced_capabilities/providers/roles.py +97 -0
- package/elizaos/advanced_capabilities/providers/settings.py +51 -0
- package/elizaos/advanced_capabilities/services/__init__.py +18 -0
- package/elizaos/advanced_capabilities/services/follow_up.py +138 -0
- package/elizaos/advanced_capabilities/services/rolodex.py +244 -0
- package/elizaos/advanced_memory/__init__.py +3 -0
- package/elizaos/advanced_memory/evaluators.py +97 -0
- package/elizaos/advanced_memory/memory_service.py +556 -0
- package/elizaos/advanced_memory/plugin.py +30 -0
- package/elizaos/advanced_memory/prompts.py +12 -0
- package/elizaos/advanced_memory/providers.py +90 -0
- package/elizaos/advanced_memory/types.py +65 -0
- package/elizaos/advanced_planning/__init__.py +10 -0
- package/elizaos/advanced_planning/actions.py +145 -0
- package/elizaos/advanced_planning/message_classifier.py +127 -0
- package/elizaos/advanced_planning/planning_service.py +712 -0
- package/elizaos/advanced_planning/plugin.py +40 -0
- package/elizaos/advanced_planning/prompts.py +4 -0
- package/elizaos/basic_capabilities/__init__.py +66 -0
- package/elizaos/basic_capabilities/actions/__init__.py +24 -0
- package/elizaos/basic_capabilities/actions/choice.py +140 -0
- package/elizaos/basic_capabilities/actions/ignore.py +66 -0
- package/elizaos/basic_capabilities/actions/none.py +56 -0
- package/elizaos/basic_capabilities/actions/reply.py +120 -0
- package/elizaos/basic_capabilities/providers/__init__.py +54 -0
- package/elizaos/basic_capabilities/providers/action_state.py +113 -0
- package/elizaos/basic_capabilities/providers/actions.py +263 -0
- package/elizaos/basic_capabilities/providers/attachments.py +76 -0
- package/elizaos/basic_capabilities/providers/capabilities.py +62 -0
- package/elizaos/basic_capabilities/providers/character.py +113 -0
- package/elizaos/basic_capabilities/providers/choice.py +73 -0
- package/elizaos/basic_capabilities/providers/context_bench.py +44 -0
- package/elizaos/basic_capabilities/providers/current_time.py +58 -0
- package/elizaos/basic_capabilities/providers/entities.py +99 -0
- package/elizaos/basic_capabilities/providers/evaluators.py +54 -0
- package/elizaos/basic_capabilities/providers/providers_list.py +55 -0
- package/elizaos/basic_capabilities/providers/recent_messages.py +85 -0
- package/elizaos/basic_capabilities/providers/time.py +45 -0
- package/elizaos/basic_capabilities/providers/world.py +93 -0
- package/elizaos/basic_capabilities/services/__init__.py +18 -0
- package/elizaos/basic_capabilities/services/embedding.py +122 -0
- package/elizaos/basic_capabilities/services/task.py +178 -0
- package/elizaos/bootstrap/__init__.py +12 -0
- package/elizaos/bootstrap/actions/__init__.py +68 -0
- package/elizaos/bootstrap/actions/add_contact.py +149 -0
- package/elizaos/bootstrap/actions/choice.py +147 -0
- package/elizaos/bootstrap/actions/follow_room.py +151 -0
- package/elizaos/bootstrap/actions/ignore.py +80 -0
- package/elizaos/bootstrap/actions/image_generation.py +135 -0
- package/elizaos/bootstrap/actions/mute_room.py +151 -0
- package/elizaos/bootstrap/actions/none.py +71 -0
- package/elizaos/bootstrap/actions/remove_contact.py +159 -0
- package/elizaos/bootstrap/actions/reply.py +140 -0
- package/elizaos/bootstrap/actions/roles.py +193 -0
- package/elizaos/bootstrap/actions/schedule_follow_up.py +164 -0
- package/elizaos/bootstrap/actions/search_contacts.py +159 -0
- package/elizaos/bootstrap/actions/send_message.py +173 -0
- package/elizaos/bootstrap/actions/settings.py +165 -0
- package/elizaos/bootstrap/actions/unfollow_room.py +151 -0
- package/elizaos/bootstrap/actions/unmute_room.py +151 -0
- package/elizaos/bootstrap/actions/update_contact.py +178 -0
- package/elizaos/bootstrap/actions/update_entity.py +175 -0
- package/elizaos/bootstrap/autonomy/__init__.py +18 -0
- package/elizaos/bootstrap/autonomy/action.py +197 -0
- package/elizaos/bootstrap/autonomy/providers.py +165 -0
- package/elizaos/bootstrap/autonomy/routes.py +171 -0
- package/elizaos/bootstrap/autonomy/service.py +562 -0
- package/elizaos/bootstrap/autonomy/types.py +18 -0
- package/elizaos/bootstrap/evaluators/__init__.py +19 -0
- package/elizaos/bootstrap/evaluators/reflection.py +118 -0
- package/elizaos/bootstrap/evaluators/relationship_extraction.py +192 -0
- package/elizaos/bootstrap/plugin.py +140 -0
- package/elizaos/bootstrap/providers/__init__.py +80 -0
- package/elizaos/bootstrap/providers/action_state.py +71 -0
- package/elizaos/bootstrap/providers/actions.py +256 -0
- package/elizaos/bootstrap/providers/agent_settings.py +63 -0
- package/elizaos/bootstrap/providers/attachments.py +76 -0
- package/elizaos/bootstrap/providers/capabilities.py +66 -0
- package/elizaos/bootstrap/providers/character.py +128 -0
- package/elizaos/bootstrap/providers/choice.py +77 -0
- package/elizaos/bootstrap/providers/contacts.py +78 -0
- package/elizaos/bootstrap/providers/context_bench.py +49 -0
- package/elizaos/bootstrap/providers/current_time.py +56 -0
- package/elizaos/bootstrap/providers/entities.py +99 -0
- package/elizaos/bootstrap/providers/evaluators.py +58 -0
- package/elizaos/bootstrap/providers/facts.py +86 -0
- package/elizaos/bootstrap/providers/follow_ups.py +116 -0
- package/elizaos/bootstrap/providers/knowledge.py +73 -0
- package/elizaos/bootstrap/providers/providers_list.py +59 -0
- package/elizaos/bootstrap/providers/recent_messages.py +85 -0
- package/elizaos/bootstrap/providers/relationships.py +106 -0
- package/elizaos/bootstrap/providers/roles.py +95 -0
- package/elizaos/bootstrap/providers/settings.py +55 -0
- package/elizaos/bootstrap/providers/time.py +45 -0
- package/elizaos/bootstrap/providers/world.py +97 -0
- package/elizaos/bootstrap/services/__init__.py +26 -0
- package/elizaos/bootstrap/services/embedding.py +122 -0
- package/elizaos/bootstrap/services/follow_up.py +138 -0
- package/elizaos/bootstrap/services/rolodex.py +244 -0
- package/elizaos/bootstrap/services/task.py +585 -0
- package/elizaos/bootstrap/types.py +54 -0
- package/elizaos/bootstrap/utils/__init__.py +7 -0
- package/elizaos/bootstrap/utils/xml.py +69 -0
- package/elizaos/character.py +149 -0
- package/elizaos/logger.py +179 -0
- package/elizaos/media/__init__.py +45 -0
- package/elizaos/media/mime.py +315 -0
- package/elizaos/media/search.py +161 -0
- package/elizaos/media/tests/__init__.py +1 -0
- package/elizaos/media/tests/test_mime.py +117 -0
- package/elizaos/media/tests/test_search.py +156 -0
- package/elizaos/plugin.py +191 -0
- package/elizaos/prompts.py +1071 -0
- package/elizaos/py.typed +0 -0
- package/elizaos/runtime.py +2572 -0
- package/elizaos/services/__init__.py +49 -0
- package/elizaos/services/hook_service.py +511 -0
- package/elizaos/services/message_service.py +1248 -0
- package/elizaos/settings.py +182 -0
- package/elizaos/streaming_context.py +159 -0
- package/elizaos/trajectory_context.py +18 -0
- package/elizaos/types/__init__.py +512 -0
- package/elizaos/types/agent.py +31 -0
- package/elizaos/types/components.py +208 -0
- package/elizaos/types/database.py +64 -0
- package/elizaos/types/environment.py +46 -0
- package/elizaos/types/events.py +47 -0
- package/elizaos/types/memory.py +45 -0
- package/elizaos/types/model.py +393 -0
- package/elizaos/types/plugin.py +188 -0
- package/elizaos/types/primitives.py +100 -0
- package/elizaos/types/runtime.py +460 -0
- package/elizaos/types/service.py +113 -0
- package/elizaos/types/service_interfaces.py +244 -0
- package/elizaos/types/state.py +188 -0
- package/elizaos/types/task.py +29 -0
- package/elizaos/utils/__init__.py +108 -0
- package/elizaos/utils/spec_examples.py +48 -0
- package/elizaos/utils/streaming.py +426 -0
- package/elizaos_atropos_shared/__init__.py +1 -0
- package/elizaos_atropos_shared/canonical_eliza.py +282 -0
- package/package.json +19 -0
- package/pyproject.toml +143 -0
- package/requirements-dev.in +11 -0
- package/requirements-dev.lock +134 -0
- package/requirements.in +9 -0
- package/requirements.lock +64 -0
- package/tests/__init__.py +0 -0
- package/tests/test_action_parameters.py +154 -0
- package/tests/test_actions_provider_examples.py +39 -0
- package/tests/test_advanced_memory_behavior.py +96 -0
- package/tests/test_advanced_memory_flag.py +30 -0
- package/tests/test_advanced_planning_behavior.py +225 -0
- package/tests/test_advanced_planning_flag.py +26 -0
- package/tests/test_autonomy.py +445 -0
- package/tests/test_bootstrap_initialize.py +37 -0
- package/tests/test_character.py +163 -0
- package/tests/test_character_provider.py +231 -0
- package/tests/test_dynamic_prompt_exec.py +561 -0
- package/tests/test_logger_redaction.py +43 -0
- package/tests/test_plugin.py +117 -0
- package/tests/test_runtime.py +422 -0
- package/tests/test_salt_production_enforcement.py +22 -0
- package/tests/test_settings_crypto.py +118 -0
- package/tests/test_streaming.py +295 -0
- package/tests/test_types.py +221 -0
- package/tests/test_uuid_parity.py +46 -0
|
@@ -0,0 +1,562 @@
|
|
|
1
|
+
"""Autonomy service using the Task system (TypeScript parity).
|
|
2
|
+
|
|
3
|
+
This service registers an `AUTONOMY_THINK` task worker and creates a recurring
|
|
4
|
+
task that triggers autonomous thinking at a configurable interval.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import asyncio
|
|
10
|
+
import contextlib
|
|
11
|
+
import logging
|
|
12
|
+
import time
|
|
13
|
+
import uuid
|
|
14
|
+
from typing import TYPE_CHECKING, Any
|
|
15
|
+
|
|
16
|
+
from elizaos.bootstrap.services.task import Task
|
|
17
|
+
from elizaos.prompts import (
|
|
18
|
+
AUTONOMY_CONTINUOUS_CONTINUE_TEMPLATE,
|
|
19
|
+
AUTONOMY_CONTINUOUS_FIRST_TEMPLATE,
|
|
20
|
+
AUTONOMY_TASK_CONTINUE_TEMPLATE,
|
|
21
|
+
AUTONOMY_TASK_FIRST_TEMPLATE,
|
|
22
|
+
)
|
|
23
|
+
from elizaos.types.environment import Room, World
|
|
24
|
+
from elizaos.types.events import EventType
|
|
25
|
+
from elizaos.types.memory import Memory
|
|
26
|
+
from elizaos.types.primitives import UUID, Content, as_uuid
|
|
27
|
+
from elizaos.types.service import Service
|
|
28
|
+
|
|
29
|
+
from .types import AutonomyStatus
|
|
30
|
+
|
|
31
|
+
if TYPE_CHECKING:
|
|
32
|
+
from elizaos.types.runtime import IAgentRuntime
|
|
33
|
+
|
|
34
|
+
_logger = logging.getLogger(__name__)
|
|
35
|
+
|
|
36
|
+
# Service type constant for autonomy (parity with TypeScript)
|
|
37
|
+
AUTONOMY_SERVICE_TYPE = "AUTONOMY"
|
|
38
|
+
|
|
39
|
+
# Task name for autonomy thinking (parity with TypeScript)
|
|
40
|
+
AUTONOMY_TASK_NAME = "AUTONOMY_THINK"
|
|
41
|
+
|
|
42
|
+
# Tags used for autonomy tasks (parity with TypeScript).
|
|
43
|
+
# Note: TypeScript uses ["repeat", "autonomy", "internal"] without "queue".
|
|
44
|
+
AUTONOMY_TASK_TAGS = ["repeat", "autonomy", "internal"]
|
|
45
|
+
|
|
46
|
+
# Default interval in milliseconds
|
|
47
|
+
DEFAULT_INTERVAL_MS = 30_000
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class AutonomyTaskWorker:
|
|
51
|
+
"""Task worker for autonomous thinking."""
|
|
52
|
+
|
|
53
|
+
def __init__(self, service: AutonomyService) -> None:
|
|
54
|
+
self._service = service
|
|
55
|
+
|
|
56
|
+
@property
|
|
57
|
+
def name(self) -> str:
|
|
58
|
+
return AUTONOMY_TASK_NAME
|
|
59
|
+
|
|
60
|
+
async def execute(
|
|
61
|
+
self,
|
|
62
|
+
runtime: IAgentRuntime,
|
|
63
|
+
options: dict[str, Any],
|
|
64
|
+
task: Task,
|
|
65
|
+
) -> None:
|
|
66
|
+
"""Execute the autonomy task."""
|
|
67
|
+
start_time = time.time()
|
|
68
|
+
|
|
69
|
+
self._service._log(
|
|
70
|
+
"debug",
|
|
71
|
+
f"Executing autonomy task (task_id={task.id})",
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
try:
|
|
75
|
+
await self._service.perform_autonomous_think()
|
|
76
|
+
duration_ms = int((time.time() - start_time) * 1000)
|
|
77
|
+
self._service._log(
|
|
78
|
+
"debug",
|
|
79
|
+
f"Autonomy task completed successfully (duration={duration_ms}ms)",
|
|
80
|
+
)
|
|
81
|
+
except Exception as e:
|
|
82
|
+
duration_ms = int((time.time() - start_time) * 1000)
|
|
83
|
+
self._service._log(
|
|
84
|
+
"error",
|
|
85
|
+
f"Autonomy task failed: {e} (duration={duration_ms}ms)",
|
|
86
|
+
)
|
|
87
|
+
raise
|
|
88
|
+
|
|
89
|
+
async def validate(
|
|
90
|
+
self,
|
|
91
|
+
runtime: IAgentRuntime,
|
|
92
|
+
message: Memory,
|
|
93
|
+
state: Any,
|
|
94
|
+
) -> bool:
|
|
95
|
+
"""Validate the task (always returns True)."""
|
|
96
|
+
return True
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
class AutonomyService(Service):
|
|
100
|
+
"""Autonomy service using the Task system.
|
|
101
|
+
|
|
102
|
+
Provides parity with TypeScript's AutonomyService.
|
|
103
|
+
"""
|
|
104
|
+
|
|
105
|
+
service_type = AUTONOMY_SERVICE_TYPE
|
|
106
|
+
|
|
107
|
+
def __init__(self) -> None:
|
|
108
|
+
self._runtime: IAgentRuntime | None = None
|
|
109
|
+
self._is_running = False
|
|
110
|
+
self._is_thinking = False
|
|
111
|
+
self._is_stopped = False
|
|
112
|
+
self._interval_ms = DEFAULT_INTERVAL_MS
|
|
113
|
+
self._task_registered = False
|
|
114
|
+
self._settings_monitor_task: asyncio.Task[None] | None = None
|
|
115
|
+
self._autonomous_room_id = as_uuid(str(uuid.uuid4()))
|
|
116
|
+
self._autonomous_world_id = as_uuid("00000000-0000-0000-0000-000000000001")
|
|
117
|
+
|
|
118
|
+
def _log(self, level: str, msg: str) -> None:
|
|
119
|
+
if self._runtime:
|
|
120
|
+
agent_id = str(self._runtime.agent_id)
|
|
121
|
+
full_msg = f"[autonomy] {msg} (agent={agent_id})"
|
|
122
|
+
getattr(self._runtime.logger, level)(full_msg)
|
|
123
|
+
else:
|
|
124
|
+
getattr(_logger, level)(f"[autonomy] {msg}")
|
|
125
|
+
|
|
126
|
+
@classmethod
|
|
127
|
+
async def start(cls, runtime: IAgentRuntime) -> AutonomyService:
|
|
128
|
+
"""Start the autonomy service."""
|
|
129
|
+
service = cls()
|
|
130
|
+
service._runtime = runtime
|
|
131
|
+
await service._initialize()
|
|
132
|
+
return service
|
|
133
|
+
|
|
134
|
+
async def _initialize(self) -> None:
|
|
135
|
+
if not self._runtime:
|
|
136
|
+
return
|
|
137
|
+
|
|
138
|
+
self._log("info", f"Using autonomous room ID: {self._autonomous_room_id}")
|
|
139
|
+
|
|
140
|
+
# Ensure autonomous context exists
|
|
141
|
+
await self._ensure_autonomous_context()
|
|
142
|
+
|
|
143
|
+
# Register the task worker
|
|
144
|
+
await self._register_autonomy_task_worker()
|
|
145
|
+
|
|
146
|
+
autonomy_enabled = self._is_autonomy_enabled()
|
|
147
|
+
|
|
148
|
+
self._log(
|
|
149
|
+
"debug",
|
|
150
|
+
f"Autonomy enabled (setting or runtime): {autonomy_enabled}",
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
# Check if autonomy should auto-start based on runtime configuration
|
|
154
|
+
if autonomy_enabled:
|
|
155
|
+
self._log(
|
|
156
|
+
"info",
|
|
157
|
+
"Autonomy enabled (enable_autonomy: True), creating autonomy task...",
|
|
158
|
+
)
|
|
159
|
+
await self._create_autonomy_task()
|
|
160
|
+
else:
|
|
161
|
+
self._log(
|
|
162
|
+
"info",
|
|
163
|
+
"Autonomy not enabled (enable_autonomy: False or not set). "
|
|
164
|
+
"Set enable_autonomy=True in runtime options to auto-start.",
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
# Start settings monitoring
|
|
168
|
+
self._settings_monitor_task = asyncio.create_task(self._settings_monitoring())
|
|
169
|
+
|
|
170
|
+
async def _register_autonomy_task_worker(self) -> None:
|
|
171
|
+
"""Register the task worker for autonomous thinking."""
|
|
172
|
+
if self._task_registered or not self._runtime:
|
|
173
|
+
return
|
|
174
|
+
|
|
175
|
+
worker = AutonomyTaskWorker(self)
|
|
176
|
+
self._runtime.register_task_worker(worker)
|
|
177
|
+
self._task_registered = True
|
|
178
|
+
|
|
179
|
+
self._log("debug", "Registered autonomy task worker")
|
|
180
|
+
|
|
181
|
+
async def _create_autonomy_task(self) -> None:
|
|
182
|
+
"""Create the recurring autonomy task."""
|
|
183
|
+
if not self._runtime:
|
|
184
|
+
return
|
|
185
|
+
|
|
186
|
+
# Remove any existing autonomy tasks
|
|
187
|
+
await self._remove_autonomy_task()
|
|
188
|
+
|
|
189
|
+
# Create the recurring task
|
|
190
|
+
task = Task.repeating(AUTONOMY_TASK_NAME, self._interval_ms)
|
|
191
|
+
task.description = f"Autonomous thinking for agent {self._runtime.agent_id}"
|
|
192
|
+
task.world_id = self._autonomous_world_id
|
|
193
|
+
task.room_id = self._autonomous_room_id
|
|
194
|
+
task.tags = AUTONOMY_TASK_TAGS.copy()
|
|
195
|
+
|
|
196
|
+
# Ensure metadata has blocking = true
|
|
197
|
+
if task.metadata:
|
|
198
|
+
task.metadata.blocking = True
|
|
199
|
+
|
|
200
|
+
await self._runtime.create_task(task)
|
|
201
|
+
|
|
202
|
+
self._is_running = True
|
|
203
|
+
self._runtime.enable_autonomy = True
|
|
204
|
+
|
|
205
|
+
self._log(
|
|
206
|
+
"info",
|
|
207
|
+
f"Created autonomy task (interval={self._interval_ms}ms)",
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
async def _remove_autonomy_task(self) -> None:
|
|
211
|
+
"""Remove existing autonomy tasks.
|
|
212
|
+
|
|
213
|
+
Uses full AUTONOMY_TASK_TAGS for filtering (parity with TypeScript).
|
|
214
|
+
"""
|
|
215
|
+
if not self._runtime:
|
|
216
|
+
return
|
|
217
|
+
|
|
218
|
+
try:
|
|
219
|
+
existing_tasks = await self._runtime.get_tasks(
|
|
220
|
+
{
|
|
221
|
+
"tags": list(AUTONOMY_TASK_TAGS),
|
|
222
|
+
}
|
|
223
|
+
)
|
|
224
|
+
|
|
225
|
+
for task in existing_tasks:
|
|
226
|
+
task_name = (
|
|
227
|
+
getattr(task, "name", None) or task.get("name")
|
|
228
|
+
if isinstance(task, dict)
|
|
229
|
+
else None
|
|
230
|
+
)
|
|
231
|
+
task_id = (
|
|
232
|
+
getattr(task, "id", None) or task.get("id") if isinstance(task, dict) else None
|
|
233
|
+
)
|
|
234
|
+
|
|
235
|
+
if task_name == AUTONOMY_TASK_NAME and task_id:
|
|
236
|
+
await self._runtime.delete_task(task_id)
|
|
237
|
+
self._log("debug", f"Removed existing autonomy task (id={task_id})")
|
|
238
|
+
except Exception as e:
|
|
239
|
+
self._log("debug", f"Error removing autonomy tasks: {e}")
|
|
240
|
+
|
|
241
|
+
async def _ensure_autonomous_context(self) -> None:
|
|
242
|
+
if not self._runtime:
|
|
243
|
+
return
|
|
244
|
+
|
|
245
|
+
try:
|
|
246
|
+
world = World(
|
|
247
|
+
id=self._autonomous_world_id,
|
|
248
|
+
name="Autonomy World",
|
|
249
|
+
agent_id=self._runtime.agent_id,
|
|
250
|
+
message_server_id=as_uuid("00000000-0000-0000-0000-000000000000"),
|
|
251
|
+
)
|
|
252
|
+
await self._runtime.ensure_world_exists(world)
|
|
253
|
+
|
|
254
|
+
room = Room(
|
|
255
|
+
id=self._autonomous_room_id,
|
|
256
|
+
name="Autonomous Thoughts",
|
|
257
|
+
world_id=self._autonomous_world_id,
|
|
258
|
+
source="autonomy-service",
|
|
259
|
+
type="SELF",
|
|
260
|
+
)
|
|
261
|
+
await self._runtime.ensure_room_exists(room)
|
|
262
|
+
|
|
263
|
+
await self._runtime.add_participant(self._runtime.agent_id, self._autonomous_room_id)
|
|
264
|
+
|
|
265
|
+
self._log(
|
|
266
|
+
"debug",
|
|
267
|
+
f"Ensured autonomous room exists with world ID: {self._autonomous_world_id}",
|
|
268
|
+
)
|
|
269
|
+
except Exception as e:
|
|
270
|
+
self._log("error", f"Failed to ensure autonomous context: {e}")
|
|
271
|
+
raise
|
|
272
|
+
|
|
273
|
+
def _get_autonomy_mode(self) -> str:
|
|
274
|
+
if not self._runtime:
|
|
275
|
+
return "continuous"
|
|
276
|
+
raw = self._runtime.get_setting("AUTONOMY_MODE")
|
|
277
|
+
if isinstance(raw, str) and raw.strip().lower() == "task":
|
|
278
|
+
return "task"
|
|
279
|
+
return "continuous"
|
|
280
|
+
|
|
281
|
+
def _get_target_room_id(self) -> UUID | None:
|
|
282
|
+
if not self._runtime:
|
|
283
|
+
return None
|
|
284
|
+
raw = self._runtime.get_setting("AUTONOMY_TARGET_ROOM_ID")
|
|
285
|
+
if not isinstance(raw, str) or raw.strip() == "":
|
|
286
|
+
return None
|
|
287
|
+
try:
|
|
288
|
+
return as_uuid(raw.strip())
|
|
289
|
+
except Exception:
|
|
290
|
+
return None
|
|
291
|
+
|
|
292
|
+
async def _get_target_room_context_text(self) -> str:
|
|
293
|
+
if not self._runtime:
|
|
294
|
+
return "(no target room configured)"
|
|
295
|
+
target_room_id = self._get_target_room_id()
|
|
296
|
+
if not target_room_id:
|
|
297
|
+
return "(no target room configured)"
|
|
298
|
+
memories_table = await self._runtime.get_memories(
|
|
299
|
+
{"roomId": target_room_id, "count": 15, "tableName": "memories"}
|
|
300
|
+
)
|
|
301
|
+
messages_table = await self._runtime.get_memories(
|
|
302
|
+
{"roomId": target_room_id, "count": 15, "tableName": "messages"}
|
|
303
|
+
)
|
|
304
|
+
by_id: dict[str, Memory] = {}
|
|
305
|
+
for m in [*memories_table, *messages_table]:
|
|
306
|
+
mem_id = m.id or ""
|
|
307
|
+
if not mem_id:
|
|
308
|
+
continue
|
|
309
|
+
created_at = m.created_at or 0
|
|
310
|
+
existing = by_id.get(mem_id)
|
|
311
|
+
if existing is None or created_at < (existing.created_at or 0):
|
|
312
|
+
by_id[mem_id] = m
|
|
313
|
+
ordered = sorted(by_id.values(), key=lambda m: m.created_at or 0)
|
|
314
|
+
lines: list[str] = []
|
|
315
|
+
for m in ordered:
|
|
316
|
+
role = "Agent" if m.entity_id == self._runtime.agent_id else "User"
|
|
317
|
+
text = m.content.text if m.content and isinstance(m.content.text, str) else ""
|
|
318
|
+
if text.strip():
|
|
319
|
+
lines.append(f"{role}: {text}")
|
|
320
|
+
return "\n".join(lines) if lines else "(no recent messages)"
|
|
321
|
+
|
|
322
|
+
async def _settings_monitoring(self) -> None:
|
|
323
|
+
while not self._is_stopped:
|
|
324
|
+
await asyncio.sleep(10)
|
|
325
|
+
|
|
326
|
+
if not self._runtime or self._is_stopped:
|
|
327
|
+
break
|
|
328
|
+
|
|
329
|
+
try:
|
|
330
|
+
should_be_running = self._is_autonomy_enabled()
|
|
331
|
+
|
|
332
|
+
if should_be_running and not self._is_running:
|
|
333
|
+
self._log(
|
|
334
|
+
"info", "Runtime indicates autonomy should be enabled, creating task..."
|
|
335
|
+
)
|
|
336
|
+
await self._create_autonomy_task()
|
|
337
|
+
elif not should_be_running and self._is_running:
|
|
338
|
+
self._log(
|
|
339
|
+
"info", "Runtime indicates autonomy should be disabled, removing task..."
|
|
340
|
+
)
|
|
341
|
+
await self._remove_autonomy_task()
|
|
342
|
+
self._is_running = False
|
|
343
|
+
except Exception as e:
|
|
344
|
+
self._log("error", f"Error in settings monitoring: {e}")
|
|
345
|
+
|
|
346
|
+
async def perform_autonomous_think(self) -> None:
|
|
347
|
+
"""Perform one iteration of autonomous thinking.
|
|
348
|
+
|
|
349
|
+
This is called by the task worker when the task executes.
|
|
350
|
+
"""
|
|
351
|
+
if not self._runtime:
|
|
352
|
+
return
|
|
353
|
+
|
|
354
|
+
# Guard against overlapping think cycles
|
|
355
|
+
if self._is_thinking:
|
|
356
|
+
self._log(
|
|
357
|
+
"debug",
|
|
358
|
+
"Previous autonomous think still in progress, skipping this iteration",
|
|
359
|
+
)
|
|
360
|
+
return
|
|
361
|
+
|
|
362
|
+
self._is_thinking = True
|
|
363
|
+
try:
|
|
364
|
+
await self._do_think()
|
|
365
|
+
except Exception as e:
|
|
366
|
+
self._log("error", f"Error in autonomous think: {e}")
|
|
367
|
+
finally:
|
|
368
|
+
self._is_thinking = False
|
|
369
|
+
|
|
370
|
+
async def _do_think(self) -> None:
|
|
371
|
+
"""Execute the actual thinking logic."""
|
|
372
|
+
if not self._runtime:
|
|
373
|
+
return
|
|
374
|
+
|
|
375
|
+
self._log("debug", "Performing autonomous thinking...")
|
|
376
|
+
|
|
377
|
+
agent_entity = await self._runtime.get_entity_by_id(self._runtime.agent_id)
|
|
378
|
+
if not agent_entity:
|
|
379
|
+
self._log("error", "Failed to get agent entity, skipping autonomous thought")
|
|
380
|
+
return
|
|
381
|
+
|
|
382
|
+
last_thought: str | None = None
|
|
383
|
+
is_first_thought = False
|
|
384
|
+
|
|
385
|
+
recent_memories = await self._runtime.get_memories(
|
|
386
|
+
{
|
|
387
|
+
"roomId": self._autonomous_room_id,
|
|
388
|
+
"count": 3,
|
|
389
|
+
"tableName": "memories",
|
|
390
|
+
}
|
|
391
|
+
)
|
|
392
|
+
|
|
393
|
+
last_agent_thought = None
|
|
394
|
+
last_created_at = None
|
|
395
|
+
for m in recent_memories:
|
|
396
|
+
if m.entity_id == agent_entity.id and m.content and m.content.text:
|
|
397
|
+
created_at = m.created_at or 0
|
|
398
|
+
if last_created_at is None or created_at > last_created_at:
|
|
399
|
+
last_created_at = created_at
|
|
400
|
+
last_agent_thought = m
|
|
401
|
+
|
|
402
|
+
if last_agent_thought and last_agent_thought.content and last_agent_thought.content.text:
|
|
403
|
+
last_thought = last_agent_thought.content.text
|
|
404
|
+
else:
|
|
405
|
+
is_first_thought = True
|
|
406
|
+
|
|
407
|
+
mode = self._get_autonomy_mode()
|
|
408
|
+
target_context = await self._get_target_room_context_text()
|
|
409
|
+
autonomy_prompt = (
|
|
410
|
+
self._create_task_prompt(last_thought, is_first_thought, target_context)
|
|
411
|
+
if mode == "task"
|
|
412
|
+
else self._create_continuous_prompt(last_thought, is_first_thought, target_context)
|
|
413
|
+
)
|
|
414
|
+
|
|
415
|
+
entity_id = agent_entity.id if agent_entity.id else self._runtime.agent_id
|
|
416
|
+
current_time_ms = int(time.time() * 1000)
|
|
417
|
+
autonomous_message = Memory(
|
|
418
|
+
id=as_uuid(str(uuid.uuid4())),
|
|
419
|
+
entity_id=as_uuid(str(entity_id)),
|
|
420
|
+
content=Content(
|
|
421
|
+
text=autonomy_prompt,
|
|
422
|
+
source="autonomy-service",
|
|
423
|
+
),
|
|
424
|
+
room_id=self._autonomous_room_id,
|
|
425
|
+
agent_id=self._runtime.agent_id,
|
|
426
|
+
created_at=current_time_ms,
|
|
427
|
+
)
|
|
428
|
+
|
|
429
|
+
self._log(
|
|
430
|
+
"debug",
|
|
431
|
+
"Processing through Eliza agent pipeline (providers, actions, evaluators)...",
|
|
432
|
+
)
|
|
433
|
+
|
|
434
|
+
async def callback(content: Content) -> None:
|
|
435
|
+
if self._runtime:
|
|
436
|
+
self._log("debug", f"Response generated: {(content.text or '')[:100]}...")
|
|
437
|
+
|
|
438
|
+
await self._runtime.emit_event(
|
|
439
|
+
EventType.EVENT_TYPE_MESSAGE_RECEIVED,
|
|
440
|
+
{
|
|
441
|
+
"runtime": self._runtime,
|
|
442
|
+
"message": autonomous_message,
|
|
443
|
+
"callback": callback,
|
|
444
|
+
"source": "autonomy-service",
|
|
445
|
+
},
|
|
446
|
+
)
|
|
447
|
+
|
|
448
|
+
self._log("debug", "Autonomous message event emitted to agent pipeline")
|
|
449
|
+
|
|
450
|
+
def _create_continuous_prompt(
|
|
451
|
+
self, last_thought: str | None, is_first_thought: bool, target_context: str
|
|
452
|
+
) -> str:
|
|
453
|
+
template = (
|
|
454
|
+
AUTONOMY_CONTINUOUS_FIRST_TEMPLATE
|
|
455
|
+
if is_first_thought
|
|
456
|
+
else AUTONOMY_CONTINUOUS_CONTINUE_TEMPLATE
|
|
457
|
+
)
|
|
458
|
+
return self._fill_autonomy_template(template, target_context, last_thought)
|
|
459
|
+
|
|
460
|
+
def _create_task_prompt(
|
|
461
|
+
self, last_thought: str | None, is_first_thought: bool, target_context: str
|
|
462
|
+
) -> str:
|
|
463
|
+
template = (
|
|
464
|
+
AUTONOMY_TASK_FIRST_TEMPLATE if is_first_thought else AUTONOMY_TASK_CONTINUE_TEMPLATE
|
|
465
|
+
)
|
|
466
|
+
return self._fill_autonomy_template(template, target_context, last_thought)
|
|
467
|
+
|
|
468
|
+
def _fill_autonomy_template(
|
|
469
|
+
self, template: str, target_context: str, last_thought: str | None
|
|
470
|
+
) -> str:
|
|
471
|
+
output = template.replace("{{targetRoomContext}}", target_context)
|
|
472
|
+
output = output.replace("{{lastThought}}", last_thought or "")
|
|
473
|
+
return output
|
|
474
|
+
|
|
475
|
+
def is_thinking_in_progress(self) -> bool:
|
|
476
|
+
return self._is_thinking
|
|
477
|
+
|
|
478
|
+
def is_loop_running(self) -> bool:
|
|
479
|
+
return self._is_running
|
|
480
|
+
|
|
481
|
+
def get_loop_interval(self) -> int:
|
|
482
|
+
return self._interval_ms
|
|
483
|
+
|
|
484
|
+
async def set_loop_interval(self, ms: int) -> None:
|
|
485
|
+
"""Set loop interval (recreates the task with new interval if running).
|
|
486
|
+
|
|
487
|
+
Parity with TypeScript's setLoopInterval.
|
|
488
|
+
"""
|
|
489
|
+
MIN_INTERVAL = 5000
|
|
490
|
+
MAX_INTERVAL = 600000
|
|
491
|
+
|
|
492
|
+
if ms < MIN_INTERVAL:
|
|
493
|
+
self._log("warning", f"Interval too short, minimum is {MIN_INTERVAL}ms")
|
|
494
|
+
ms = MIN_INTERVAL
|
|
495
|
+
if ms > MAX_INTERVAL:
|
|
496
|
+
self._log("warning", f"Interval too long, maximum is {MAX_INTERVAL}ms")
|
|
497
|
+
ms = MAX_INTERVAL
|
|
498
|
+
|
|
499
|
+
self._interval_ms = ms
|
|
500
|
+
self._log("info", f"Loop interval set to {ms}ms")
|
|
501
|
+
|
|
502
|
+
# Recreate the task if running (parity with TypeScript)
|
|
503
|
+
if self._is_running:
|
|
504
|
+
await self._create_autonomy_task()
|
|
505
|
+
|
|
506
|
+
def get_autonomous_room_id(self) -> UUID:
|
|
507
|
+
return self._autonomous_room_id
|
|
508
|
+
|
|
509
|
+
async def enable_autonomy(self) -> None:
|
|
510
|
+
"""Enable autonomy by creating the task."""
|
|
511
|
+
if self._runtime:
|
|
512
|
+
self._runtime.enable_autonomy = True
|
|
513
|
+
await self._create_autonomy_task()
|
|
514
|
+
|
|
515
|
+
async def disable_autonomy(self) -> None:
|
|
516
|
+
"""Disable autonomy by removing the task."""
|
|
517
|
+
if self._runtime:
|
|
518
|
+
self._runtime.enable_autonomy = False
|
|
519
|
+
await self._remove_autonomy_task()
|
|
520
|
+
self._is_running = False
|
|
521
|
+
|
|
522
|
+
def get_status(self) -> AutonomyStatus:
|
|
523
|
+
enabled = self._is_autonomy_enabled()
|
|
524
|
+
|
|
525
|
+
return AutonomyStatus(
|
|
526
|
+
enabled=enabled,
|
|
527
|
+
running=self._is_running,
|
|
528
|
+
thinking=self._is_thinking,
|
|
529
|
+
interval=self._interval_ms,
|
|
530
|
+
autonomous_room_id=str(self._autonomous_room_id),
|
|
531
|
+
)
|
|
532
|
+
|
|
533
|
+
async def stop(self) -> None:
|
|
534
|
+
"""Stop the autonomy service."""
|
|
535
|
+
self._is_stopped = True
|
|
536
|
+
|
|
537
|
+
# Remove the autonomy task
|
|
538
|
+
await self._remove_autonomy_task()
|
|
539
|
+
self._is_running = False
|
|
540
|
+
|
|
541
|
+
if self._settings_monitor_task:
|
|
542
|
+
self._settings_monitor_task.cancel()
|
|
543
|
+
with contextlib.suppress(asyncio.CancelledError):
|
|
544
|
+
await self._settings_monitor_task
|
|
545
|
+
self._settings_monitor_task = None
|
|
546
|
+
|
|
547
|
+
self._log("info", "Autonomy service stopped")
|
|
548
|
+
|
|
549
|
+
@property
|
|
550
|
+
def capability_description(self) -> str:
|
|
551
|
+
return (
|
|
552
|
+
"Autonomous operation using the Task system for continuous agent thinking and actions"
|
|
553
|
+
)
|
|
554
|
+
|
|
555
|
+
def _is_autonomy_enabled(self) -> bool:
|
|
556
|
+
if not self._runtime:
|
|
557
|
+
return False
|
|
558
|
+
setting_value = self._runtime.get_setting("AUTONOMY_ENABLED")
|
|
559
|
+
setting_enabled = setting_value is True or (
|
|
560
|
+
isinstance(setting_value, str) and setting_value.strip().lower() == "true"
|
|
561
|
+
)
|
|
562
|
+
return setting_enabled or self._runtime.enable_autonomy is True
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@dataclass
|
|
7
|
+
class AutonomyStatus:
|
|
8
|
+
enabled: bool
|
|
9
|
+
running: bool
|
|
10
|
+
thinking: bool
|
|
11
|
+
interval: int
|
|
12
|
+
autonomous_room_id: str
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@dataclass
|
|
16
|
+
class AutonomyConfig:
|
|
17
|
+
interval_ms: int = 30000
|
|
18
|
+
auto_start: bool = False
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
from .reflection import reflection_evaluator
|
|
2
|
+
from .relationship_extraction import relationship_extraction_evaluator
|
|
3
|
+
|
|
4
|
+
__all__ = [
|
|
5
|
+
"reflection_evaluator",
|
|
6
|
+
"relationship_extraction_evaluator",
|
|
7
|
+
"BASIC_EVALUATORS",
|
|
8
|
+
"EXTENDED_EVALUATORS",
|
|
9
|
+
"ALL_EVALUATORS",
|
|
10
|
+
]
|
|
11
|
+
|
|
12
|
+
BASIC_EVALUATORS: list = []
|
|
13
|
+
|
|
14
|
+
EXTENDED_EVALUATORS = [
|
|
15
|
+
reflection_evaluator,
|
|
16
|
+
relationship_extraction_evaluator,
|
|
17
|
+
]
|
|
18
|
+
|
|
19
|
+
ALL_EVALUATORS = BASIC_EVALUATORS + EXTENDED_EVALUATORS
|