# Task: Worker Daemon ## Goal Implement a standalone **Worker Daemon** that repeatedly obtains one task from the central coordinator, runs it locally, and submits a result manifest. The worker must not access the database directly. The target flow is: ```text Worker Daemon -> coordinator: claim task coordinator -> Worker Daemon: task metadata + input location Worker Daemon -> local SciMesh Core / CV runner: execute Worker Daemon -> coordinator: upload result artifact, then submit result manifest ``` The architecture sketch uses video chunks and CV, while the current SciMesh repository contains local molecular workloads. Therefore the daemon must use a small runner adapter: the first adapter may invoke a SciMesh CLI workload, and future adapters may run a CV/video chunk processor. Do not put workload logic inside the daemon. ## Deliverables 1. A Python module/package for the daemon and a console command, for example `scimesh-worker`. 2. Configuration via environment variables and CLI overrides: - `SCIMESH_COORDINATOR_URL` (required); - `SCIMESH_WORKER_NAME` (optional; defaults to the hostname); - `SCIMESH_WORKER_ID` (optional legacy/test override; production identity is returned by registration); - working directory for downloaded inputs and generated outputs; - poll interval and request timeout; - optional bearer token. 3. A `Runner` protocol and one `SciMeshRunner` implementation. The protocol must make a future `VideoRunner` possible without changing daemon control flow. 4. Structured logs containing `worker_id`, `task_id`, attempt number, state, elapsed time, and error type. 5. Unit tests using a mocked HTTP coordinator and a fake runner. ## Coordinator contract Use JSON over HTTPS. Claiming a task changes its state, so use `POST`, even if the initial diagram labels the endpoint as `GET /get_task`. `docs/api-contract.md` is the authoritative API schema. This document explains the daemon workflow and must not introduce a different request or response shape. ### Register worker At daemon startup, register the worker capabilities before claiming tasks: ```http POST /workers/register Content-Type: application/json { "name": "lab-worker-01", "capabilities": ["similarity-search"], "cpu_count": 8, "memory_mb": 16384 } ``` The `worker_id` returned by this endpoint is used for the daemon lifetime. ### Claim a task ```http POST /tasks/claim Content-Type: application/json { "worker_id": "", "capabilities": ["similarity-search"], "max_concurrency": 1 } ``` When no task is available, the coordinator returns `204 No Content`. When a task is available, it returns `200 OK`: ```json { "task_id": "0d2d5a53-4c7e-467e-93d2-45ed2dc18e46", "attempt": 1, "lease_expires_at": "2026-07-21T12:05:00Z", "workload": "similarity-search", "input": { "uri": "https://coordinator.example/tasks/0d2d/input", "sha256": "..." }, "parameters": { "query_smiles": "CCO", "top_k": 20 } } ``` `input.uri` may initially point to a coordinator download endpoint. Keep input retrieval behind an `ArtifactClient` abstraction so it can later be replaced by object storage without changing the daemon state machine. ### Submit a result ```http POST /tasks/{task_id}/result Content-Type: application/json { "worker_id": "worker-01", "attempt": 1, "result": { "artifact_id": "0d2d5a53-4c7e-467e-93d2-45ed2dc18e46", "uri": "https://coordinator.example/tasks/0d2d/result.csv", "sha256": "...", "content_type": "text/csv" }, "metrics": { "elapsed_seconds": 12.4, "scanned_rows": 10000, "valid_molecules": 9876, "invalid_smiles": 124, "matches_emitted": 20 } } ``` The `result.uri` must be the durable URI returned by the artifact upload endpoint below; a worker-local `file://` or `worker://` path is invalid. ### Upload a result artifact ```http PUT /tasks/{task_id}/artifacts/{filename} Content-Type: text/csv X-Worker-ID: worker-01 X-Task-Attempt: 1 ``` The coordinator streams the artifact to its configured storage and responds: ```json { "artifact_id": "0d2d5a53-4c7e-467e-93d2-45ed2dc18e46", "uri": "https://coordinator.example/tasks/0d2d/artifacts/result.csv", "sha256": "...", "size_bytes": 1234 } ``` For a failed execution, send a short, sanitized `error_code` and `error_message` to `POST /tasks/{task_id}/failure`. Never send a Python traceback, access token, or local path outside the worker directory. ## Required state machine ```text idle -> claiming -> downloading -> running -> uploading -> submitting -> idle | | | | +------------> failed <--------------------+ ``` - Poll only after a `204` response or a transient failure; use exponential backoff with jitter and an upper bound. - Verify the input checksum before running. - Create one isolated task directory: `///`. - Invoke the runner with an explicit argument list, never `shell=True`. - Upload the produced result artifact before submitting its manifest. - Version 1 produces exactly one CSV partial result. Multi-artifact manifests require an explicit future API-contract change. - Do not mark a task completed until every submitted artifact has a durable coordinator-provided URI. - A timeout, network error, or rejected submission must leave the local task directory available for diagnostics until a configurable cleanup period. - Treat a duplicate successful submission as success when the coordinator returns an idempotent response for the same `task_id` and `attempt`. ## Runner interface The daemon owns task orchestration; the runner owns only local execution. ```python class Runner(Protocol): def run(self, task: ClaimedTask, task_dir: Path) -> RunResult: """Run one task and return output artifacts plus safe metrics.""" ``` `SciMeshRunner` maps an allowlisted workload and validated parameters to the local SciMesh reference functions. A planned `similarity-search` task contains a resolved `query_smiles` (never `query_id`) and writes one exact local top-k partial CSV plus the metrics above. Legacy single-shard tasks may still use the CLI compatibility path: ```text scimesh similarity-search --query-id ... --output /result.csv ``` Do not accept an arbitrary command from the coordinator. Maintain an allowlist of registered workload names and validate every parameter before invocation. ## Acceptance criteria - With a fake coordinator, the daemon claims one task, downloads a fixture, invokes the fake runner once, and submits its CSV manifest. - A `204` response does not create a task directory and waits before the next poll. - A bad input checksum prevents runner execution and reports a failed task. - A transient claim/submit failure retries with bounded backoff. - Two workers cannot both complete the same leased attempt; the daemon handles a lease/submission conflict without corrupting local results. - The daemon has no database driver or SQL queries. ## Out of scope - FastAPI coordinator implementation; - database schema and migrations; - video segmentation, CV inference, and trajectory stitching; - multiprocessing, distributed scheduling, and autoscaling.