2026-03-26 21:57:56 +00:00
|
|
|
"""Temporal worker entrypoint for activity-core.
|
|
|
|
|
|
|
|
|
|
Starts two workers (wired up in T20):
|
|
|
|
|
- orchestrator-tq: RunActivityWorkflow + its activities
|
|
|
|
|
- task-execution-tq: TaskExecutorWorkflow
|
|
|
|
|
|
2026-03-28 01:04:43 +01:00
|
|
|
T23: Calls sync_schedules before entering the worker run loop to ensure
|
|
|
|
|
all cron ActivityDefinitions have live Temporal Schedules.
|
|
|
|
|
|
|
|
|
|
T31: Exposes Prometheus metrics via the Temporal SDK runtime on :9090/metrics.
|
|
|
|
|
|
2026-03-26 21:57:56 +00:00
|
|
|
Run with:
|
|
|
|
|
TEMPORAL_HOST=localhost:7233 \
|
|
|
|
|
ACTCORE_DB_URL=postgresql+asyncpg://actcore:actcore@localhost:5433/actcore \
|
|
|
|
|
python -m activity_core.worker
|
|
|
|
|
|
|
|
|
|
Environment variables:
|
2026-03-28 01:04:43 +01:00
|
|
|
TEMPORAL_HOST Temporal frontend address (default: localhost:7233)
|
|
|
|
|
TEMPORAL_NAMESPACE Temporal namespace (default: default)
|
|
|
|
|
ACTCORE_DB_URL App DB connection string (required)
|
|
|
|
|
PROMETHEUS_BIND_ADDR Prometheus metrics bind (default: 0.0.0.0:9090)
|
2026-03-26 21:57:56 +00:00
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
from __future__ import annotations
|
|
|
|
|
|
|
|
|
|
import asyncio
|
2026-03-28 01:04:43 +01:00
|
|
|
import logging
|
2026-03-26 21:57:56 +00:00
|
|
|
import os
|
feat(WP-0004): railiance deployment & service ops
- Dockerfile (multi-stage, uv-based, slim runtime)
- .dockerignore
- docker-compose.railiance.yml (Temporal + NATS + PG, no Elasticsearch)
- GET /health endpoint (db + temporal probes, 200/503)
- .env.example (complete env var reference)
- Makefile: migrate, sync-all, dev-up/down, railiance-up/down,
start-worker, start-api, start-event-router, help targets;
extracted sync-event-types Python to scripts/sync_event_types.py
- SIGTERM graceful shutdown in worker.py and event_router.py
- docs/runbook.md: Railiance deployment section
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-15 00:04:39 +02:00
|
|
|
import signal
|
2026-03-26 21:57:56 +00:00
|
|
|
|
|
|
|
|
from temporalio.client import Client
|
2026-03-28 01:04:43 +01:00
|
|
|
from temporalio.runtime import PrometheusConfig, Runtime, TelemetryConfig
|
2026-03-26 21:57:56 +00:00
|
|
|
from temporalio.worker import Worker
|
|
|
|
|
|
|
|
|
|
from activity_core.activities import (
|
2026-05-14 23:02:33 +02:00
|
|
|
emit_tasks,
|
2026-05-19 20:13:23 +02:00
|
|
|
evaluate_instructions,
|
2026-05-14 23:02:33 +02:00
|
|
|
evaluate_rules,
|
2026-03-26 22:02:15 +00:00
|
|
|
init_session_factory,
|
2026-03-26 21:57:56 +00:00
|
|
|
load_activity_definition,
|
|
|
|
|
log_run,
|
2026-05-19 20:13:23 +02:00
|
|
|
persist_instruction_reports,
|
2026-03-26 22:30:50 +00:00
|
|
|
persist_task_instance,
|
2026-03-26 21:57:56 +00:00
|
|
|
resolve_context,
|
|
|
|
|
)
|
2026-05-14 23:02:33 +02:00
|
|
|
from activity_core.db import make_engine
|
|
|
|
|
from sqlalchemy.ext.asyncio import async_sessionmaker
|
|
|
|
|
from activity_core.sync_activity_definitions import sync as sync_activity_defs
|
2026-03-28 01:04:43 +01:00
|
|
|
from activity_core.sync_schedules import sync as sync_schedules
|
2026-03-26 21:57:56 +00:00
|
|
|
from activity_core.workflows import RunActivityWorkflow, TaskExecutorWorkflow
|
|
|
|
|
|
2026-03-28 01:04:43 +01:00
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
2026-03-26 21:57:56 +00:00
|
|
|
TEMPORAL_HOST = os.environ.get("TEMPORAL_HOST", "localhost:7233")
|
|
|
|
|
TEMPORAL_NAMESPACE = os.environ.get("TEMPORAL_NAMESPACE", "default")
|
2026-03-28 01:04:43 +01:00
|
|
|
PROMETHEUS_BIND_ADDR = os.environ.get("PROMETHEUS_BIND_ADDR", "0.0.0.0:9090")
|
2026-03-26 21:57:56 +00:00
|
|
|
|
|
|
|
|
ORCHESTRATOR_TASK_QUEUE = "orchestrator-tq"
|
|
|
|
|
TASK_EXECUTION_TASK_QUEUE = "task-execution-tq"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async def run() -> None:
|
2026-03-26 22:02:15 +00:00
|
|
|
db_url = os.environ.get("ACTCORE_DB_URL")
|
|
|
|
|
if not db_url:
|
|
|
|
|
raise RuntimeError("ACTCORE_DB_URL is required")
|
|
|
|
|
init_session_factory(db_url)
|
|
|
|
|
|
2026-03-28 01:04:43 +01:00
|
|
|
# T31: Configure the Temporal SDK runtime to emit metrics in Prometheus format.
|
|
|
|
|
runtime = Runtime(
|
|
|
|
|
telemetry=TelemetryConfig(
|
|
|
|
|
metrics=PrometheusConfig(bind_address=PROMETHEUS_BIND_ADDR)
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
client = await Client.connect(
|
|
|
|
|
TEMPORAL_HOST, namespace=TEMPORAL_NAMESPACE, runtime=runtime
|
|
|
|
|
)
|
|
|
|
|
|
2026-05-14 23:02:33 +02:00
|
|
|
# T45: Sync ActivityDefinition files into DB before schedule sync.
|
|
|
|
|
logger.info("Syncing ActivityDefinition files...")
|
|
|
|
|
try:
|
|
|
|
|
session_factory = async_sessionmaker(make_engine(db_url), expire_on_commit=False)
|
|
|
|
|
await sync_activity_defs(session_factory)
|
|
|
|
|
except Exception:
|
|
|
|
|
logger.exception("activity definition sync failed — continuing worker startup")
|
|
|
|
|
|
2026-03-28 01:04:43 +01:00
|
|
|
# T23: Sync Temporal Schedules with the DB before workers start accepting tasks.
|
|
|
|
|
logger.info("Syncing Temporal Schedules with ActivityDefinition DB...")
|
|
|
|
|
try:
|
|
|
|
|
await sync_schedules(client, db_url)
|
|
|
|
|
except Exception:
|
|
|
|
|
logger.exception("schedule sync failed — continuing worker startup")
|
2026-03-26 21:57:56 +00:00
|
|
|
|
|
|
|
|
orchestrator_worker = Worker(
|
|
|
|
|
client,
|
|
|
|
|
task_queue=ORCHESTRATOR_TASK_QUEUE,
|
|
|
|
|
workflows=[RunActivityWorkflow],
|
2026-05-19 20:13:23 +02:00
|
|
|
activities=[
|
|
|
|
|
load_activity_definition,
|
|
|
|
|
resolve_context,
|
|
|
|
|
log_run,
|
|
|
|
|
evaluate_rules,
|
|
|
|
|
evaluate_instructions,
|
|
|
|
|
persist_instruction_reports,
|
|
|
|
|
emit_tasks,
|
|
|
|
|
],
|
2026-03-26 21:57:56 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
task_worker = Worker(
|
|
|
|
|
client,
|
|
|
|
|
task_queue=TASK_EXECUTION_TASK_QUEUE,
|
|
|
|
|
workflows=[TaskExecutorWorkflow],
|
2026-03-26 22:30:50 +00:00
|
|
|
activities=[persist_task_instance],
|
2026-03-26 21:57:56 +00:00
|
|
|
)
|
|
|
|
|
|
feat(WP-0004): railiance deployment & service ops
- Dockerfile (multi-stage, uv-based, slim runtime)
- .dockerignore
- docker-compose.railiance.yml (Temporal + NATS + PG, no Elasticsearch)
- GET /health endpoint (db + temporal probes, 200/503)
- .env.example (complete env var reference)
- Makefile: migrate, sync-all, dev-up/down, railiance-up/down,
start-worker, start-api, start-event-router, help targets;
extracted sync-event-types Python to scripts/sync_event_types.py
- SIGTERM graceful shutdown in worker.py and event_router.py
- docs/runbook.md: Railiance deployment section
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-15 00:04:39 +02:00
|
|
|
loop = asyncio.get_running_loop()
|
|
|
|
|
stop = asyncio.Event()
|
|
|
|
|
loop.add_signal_handler(signal.SIGTERM, stop.set)
|
|
|
|
|
loop.add_signal_handler(signal.SIGINT, stop.set)
|
|
|
|
|
|
2026-03-26 21:57:56 +00:00
|
|
|
async with orchestrator_worker, task_worker:
|
feat(WP-0004): railiance deployment & service ops
- Dockerfile (multi-stage, uv-based, slim runtime)
- .dockerignore
- docker-compose.railiance.yml (Temporal + NATS + PG, no Elasticsearch)
- GET /health endpoint (db + temporal probes, 200/503)
- .env.example (complete env var reference)
- Makefile: migrate, sync-all, dev-up/down, railiance-up/down,
start-worker, start-api, start-event-router, help targets;
extracted sync-event-types Python to scripts/sync_event_types.py
- SIGTERM graceful shutdown in worker.py and event_router.py
- docs/runbook.md: Railiance deployment section
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-15 00:04:39 +02:00
|
|
|
logger.info(
|
|
|
|
|
"Workers running — queues: %r, %r (namespace=%r)",
|
|
|
|
|
ORCHESTRATOR_TASK_QUEUE,
|
|
|
|
|
TASK_EXECUTION_TASK_QUEUE,
|
|
|
|
|
TEMPORAL_NAMESPACE,
|
2026-03-26 21:57:56 +00:00
|
|
|
)
|
feat(WP-0004): railiance deployment & service ops
- Dockerfile (multi-stage, uv-based, slim runtime)
- .dockerignore
- docker-compose.railiance.yml (Temporal + NATS + PG, no Elasticsearch)
- GET /health endpoint (db + temporal probes, 200/503)
- .env.example (complete env var reference)
- Makefile: migrate, sync-all, dev-up/down, railiance-up/down,
start-worker, start-api, start-event-router, help targets;
extracted sync-event-types Python to scripts/sync_event_types.py
- SIGTERM graceful shutdown in worker.py and event_router.py
- docs/runbook.md: Railiance deployment section
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-15 00:04:39 +02:00
|
|
|
await stop.wait()
|
|
|
|
|
logger.info("Shutdown signal received — draining workers")
|
|
|
|
|
logger.info("Workers stopped cleanly")
|
2026-03-26 21:57:56 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
2026-03-28 01:04:43 +01:00
|
|
|
logging.basicConfig(level=logging.INFO)
|
2026-03-26 21:57:56 +00:00
|
|
|
asyncio.run(run())
|