-
Notifications
You must be signed in to change notification settings - Fork 51
Expand file tree
/
Copy pathrun_worker.py
More file actions
215 lines (181 loc) · 7.75 KB
/
Copy pathrun_worker.py
File metadata and controls
215 lines (181 loc) · 7.75 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
"""
Temporal worker entry point for health check workflows.
Each worker process handles one task queue for clean separation and scaling.
"""
import asyncio
import os
import uuid
from concurrent.futures import ThreadPoolExecutor
import httpx
from temporalio.worker import UnsandboxedWorkflowRunner, Worker
from src.adapters.temporal.client_factory import TemporalClientFactory
from src.config.dependencies import (
GlobalDependencies,
database_async_read_only_session_maker,
database_async_read_write_engine,
database_async_read_write_session_maker,
httpx_client,
startup_global_dependencies,
)
from src.config.environment_variables import EnvironmentVariables
from src.domain.repositories.agent_repository import AgentRepository
from src.temporal.activities.healthcheck_activities import HealthCheckActivities
from src.temporal.activities.retention_cleanup_activities import (
RetentionCleanupActivities,
)
from src.temporal.task_retention_factory import build_task_retention_use_case
from src.temporal.workflows.healthcheck_workflow import HealthCheckWorkflow
from src.temporal.workflows.retention_cleanup_workflow import (
RetentionCleanupSweepWorkflow,
RetentionCleanupTaskWorkflow,
)
from src.utils.logging import make_logger
logger = make_logger(__name__)
# Task queue name for agentex server operations
AGENTEX_SERVER_TASK_QUEUE = "agentex-server"
# Global worker instance
health_check_worker: Worker | None = None
async def run_worker(
task_queue: str = AGENTEX_SERVER_TASK_QUEUE,
dependency_overrides: dict | None = None,
workflows: list | None = None,
activities: list | None = None,
max_workers: int = 10,
max_concurrent_activities: int = 50,
) -> None:
"""
Run the Temporal worker for specified workflows and activities.
Args:
task_queue: The task queue to process (default: agentex-server)
dependency_overrides: Optional dependency overrides for testing
workflows: List of workflow classes to register
activities: List of activity functions to register
max_workers: Maximum number of activity worker threads
max_concurrent_activities: Maximum concurrent activities
Raises:
TemporalError: If worker creation or execution fails
"""
global health_check_worker
try:
# Initialize global dependencies
await startup_global_dependencies()
# Get environment variables
environment_variables = EnvironmentVariables.refresh()
logger.info(f"Starting Health Check worker for task queue: {task_queue}")
logger.info(f"Temporal address: {environment_variables.TEMPORAL_ADDRESS}")
# Check if Temporal is configured
if not TemporalClientFactory.is_temporal_configured(environment_variables):
logger.warning("Temporal is not configured, skipping worker creation")
raise ValueError("Temporal is not properly configured")
# Check for metrics configuration
host_url = os.environ.get("DD_AGENT_HOST")
metrics_url = f"http://[{host_url}]:4317" if host_url else None
if metrics_url:
logger.info(f"Configuring worker with metrics URL: {metrics_url}")
# Create Temporal client
client = await TemporalClientFactory.create_client_from_env(
environment_variables=environment_variables,
metrics_url=metrics_url,
)
# Create the worker directly (no manager needed)
health_check_worker = Worker(
client,
task_queue=task_queue,
activity_executor=ThreadPoolExecutor(max_workers=max_workers),
workflows=workflows or [],
activities=activities or [],
workflow_runner=UnsandboxedWorkflowRunner(),
max_concurrent_activities=max_concurrent_activities,
build_id=str(uuid.uuid4()),
)
logger.info(
f"Health Check worker created successfully for task queue: {task_queue}"
)
logger.info(
f"Registered {len(workflows or [])} workflows and {len(activities or [])} activities"
)
if workflows:
logger.info(f"Workflows: {[w.__name__ for w in workflows]}")
if activities:
logger.info(f"Activities: {[a.__name__ for a in activities]}")
# Run the worker (this will block until the worker is stopped)
await health_check_worker.run()
except Exception as e:
logger.error(f"Worker failed: {e}")
raise
finally:
# Cleanup
if health_check_worker:
logger.info("Shutting down worker...")
await health_check_worker.shutdown()
def create_agentex_server_worker(
agent_repo: AgentRepository,
http_client: httpx.AsyncClient,
global_dependencies: GlobalDependencies,
) -> asyncio.Task:
"""
Create the single Temporal worker that serves the `agentex-server` task queue.
Registers ALL workflows + activities that run on this queue — health checks
AND retention cleanup — in one worker. Workers polling the same task queue
must register the same set of types (the queue is not typed), so these live
together in one worker rather than as separate processes/containers.
"""
task_queue = os.environ.get("AGENTEX_SERVER_TASK_QUEUE", AGENTEX_SERVER_TASK_QUEUE)
logger.info("Starting agentex-server Temporal worker")
logger.info(f"Task queue: {task_queue}")
health_check_activities = HealthCheckActivities(
agent_repo=agent_repo,
http_client=http_client,
)
# Build the retention use case per activity run (not once here): the OIDC
# client refresh swaps global_dependencies.mongodb_database to a fresh client
# and closes the old one, so a use case captured at startup would eventually
# hold Mongo collections bound to a closed client. global_dependencies is the
# singleton, so each call reads the current mongodb_database.
retention_activities = RetentionCleanupActivities(
use_case_factory=lambda: build_task_retention_use_case(global_dependencies),
)
return asyncio.create_task(
run_worker(
task_queue=task_queue,
workflows=[
HealthCheckWorkflow,
RetentionCleanupSweepWorkflow,
RetentionCleanupTaskWorkflow,
],
activities=[
health_check_activities.check_status_activity,
health_check_activities.update_agent_status_activity,
retention_activities.load_cleanup_config,
retention_activities.find_cleanup_candidates,
retention_activities.find_multi_agent_cleanup_candidates,
retention_activities.clean_task,
],
max_workers=50,
max_concurrent_activities=50,
)
)
async def main() -> None:
"""Main entry point for the agentex-server Temporal worker."""
try:
await startup_global_dependencies()
global_dependencies = GlobalDependencies()
engine = database_async_read_write_engine()
session_maker = database_async_read_write_session_maker(engine)
read_only_session_maker = database_async_read_only_session_maker(engine)
agent_repo = AgentRepository(session_maker, read_only_session_maker)
worker_task = create_agentex_server_worker(
agent_repo=agent_repo,
http_client=httpx_client(),
global_dependencies=global_dependencies,
)
await worker_task
except KeyboardInterrupt:
logger.info("Received interrupt signal, shutting down worker...")
if health_check_worker:
await health_check_worker.shutdown()
except Exception as e:
logger.error(f"Worker startup failed: {e}")
raise
if __name__ == "__main__":
asyncio.run(main())