# Worker (`apps/worker`) Minimal first-party agent runtime for convoy automation. Consumes orchestrator jobs from Redis, resolves models via the workspace registry, runs a tool loop against a git worktree, and records `claim_task` / `complete_task` + `convoy_events.role_complete`. ## Run locally ```bash # Dry-run (no Redis consumer — exits after banner) WORKER_DRY_RUN=1 pnpm --filter @tasks/worker dev # Live consumer (requires REDIS_URL + DATABASE_URL) pnpm --filter @tasks/worker dev ``` Pair with the orchestrator: ```bash ORCHESTRATOR_DRY_RUN=0 REDIS_URL=... pnpm --filter @tasks/orchestrator dev WORKER_DRY_RUN=0 REDIS_URL=... pnpm --filter @tasks/worker dev ``` ## Architecture - **Queue** — `BLPOP` on `echodo:orchestrator:jobs` (same key as `apps/orchestrator`) - **Session** — `WorkerSession` interface in `@tasks/shared/worker`; v1 implementation uses Vercel AI SDK `generateText` + tools - **Tools** — `read_file`, `write_file`, `grep` (ripgrep), `run_command` (allowlist per harness: code harness `pnpm lint`, `pnpm type-check`, `pnpm test`, `git status`, `git diff`; doc harness `[]` — no `run_command`, only `read`/`write`/`grep`) - **Worktree** — one git worktree per brief job under `WORKER_WORKTREE_BASE` when `harness.needsWorktree === true` (code tasks). Doc tasks (`harness: doc-writer`) set `needsWorktree: false` → worker skips `ensureWorktree` and runs straight against `monorepoRoot` (`docs/`/`plans/` writes without a branch). See `apps/worker/src/harness.ts` and `apps/worker/src/git/worktree.ts` `needsWorktree` param. - **Lifecycle** — DB writes mirroring MCP `claim_task` / `complete_task`; `role_complete` convoy event on finish (includes `metadata.harnessAlias`) - **Harnesses** — `resolveHarnessForJob` maps `job.harnessAlias` or `roleId` to `{ alias, needsWorktree, allowedCommands }`; `allowedCommands` is threaded to `runCommandTool` via `MinimalSession`. Brief 3 will promote this to DB-backed `harness_providers` / `role_harness_routes`. ## Env | Variable | Default | Purpose | |----------|---------|---------| | `DATABASE_URL` | required | Convoy, backlog, model registry | | `REDIS_URL` | optional | Job queue; missing ⇒ dry-run | | `WORKER_DRY_RUN` | `false` | Skip dequeue loop | | `WORKER_BLPOP_TIMEOUT_S` | `5` | Blocking pop timeout | | `WORKER_MAX_TOOL_STEPS` | `12` | Max tool rounds per session | | `WORKER_CHECKPOINT_EVERY_N_STEPS` | `1` | Persist a checkpoint every N tool rounds | | `WORKER_REPO_ROOT` | monorepo root | Repo for role prompts + worktree base | | `WORKER_WORKTREE_BASE` | `/tmp/echodo-worker-worktrees` | Worktree parent directory | ## Checkpoints & crash recovery After each tool round (or every `WORKER_CHECKPOINT_EVERY_N_STEPS` rounds), the worker writes a row to `worker_checkpoints` (workspace-scoped): bounded `tool_history`, `files_touched` as **paths + content hashes only** (never API keys or full file bodies), plus `branch` / `worktree_path`. On re-dispatch with `resumeFromCheckpoint: true` (orchestrator stall detector), the worker: 1. Loads the latest checkpoint for the open `agent_runs` row (filtered by `workspace_id`) 2. Reuses the saved worktree when still present — no re-clone 3. Continues the tool loop with prior steps summarized in the system prompt Mission control convoy detail shows checkpoint count and last checkpoint time. Provider API keys are read from env vars named in `model_providers.api_key_env_var` (same as web settings).