# ClaudeDo.Worker ASP.NET Core hosted service that executes tasks via Claude CLI in isolated environments. ## Architecture - **Program.cs** — loads config, inits schema, registers DI, configures SignalR on `/hub`, binds to `127.0.0.1:47821` - **QueueService** — `BackgroundService` with two execution slots: - Queue slot: FIFO sequential processing of "agent"-tagged queued tasks - Override slot: immediate execution via `RunNow(taskId)` - Wake signaling via `SemaphoreSlim`, backstop timer (30s default) - **StaleTaskRecovery** — startup-only service, flips orphaned "running" tasks to "failed" ## Task Execution Pipeline `TaskRunner` orchestrates: 1. Load task + list metadata from DB; resolve config from `list_config` + task-level overrides (model, system_prompt, agent_path) 2. Create worktree (if `WorkingDir` set) or sandbox directory 3. Mark task "running", broadcast `TaskStarted` 4. Build CLI args via `ClaudeArgsBuilder`; invoke `ClaudeProcess` with task prompt 5. Stream NDJSON output through `StreamAnalyzer`; lines forwarded to log file and SignalR (`TaskMessage`) 6. On success: auto-commit changes (worktree only), store run record, mark "done" 7. On failure: retry once if session ID available (`--resume`), then mark "failed" ## Key Components - **ClaudeProcess** — spawns `claude -p --output-format stream-json --verbose --permission-mode auto` (or whatever permission mode the app settings specify). Writes prompt to stdin, reads NDJSON from stdout. Supports CancellationToken (kills process tree). - **ClaudeArgsBuilder** — dynamically constructs CLI args; supports `--model`, `--append-system-prompt`, `--agents`, `--json-schema`, `--resume` - **StreamAnalyzer** — parses rich NDJSON output; extracts session_id, token counts, turn counts, result text, structured output. Replaces MessageParser. - **TaskResetService** — discards a failed task's worktree and resets the task row to Manual; preserves run history. - **WorktreeManager** — creates worktrees at `claudedo/{taskId[:8]}` branches, commits changes with semantic messages, updates DB with head commit and diff stats - **CommitMessageBuilder** — formats `{commitType}(slug): title\n\ndescription\n\nClaudeDo-Task: taskId` - **AgentFileService** — manages `~/.todo-app/agents/*.md` agent definition files; exposes list/refresh via SignalR - **LogWriter** — async StreamWriter wrapper, auto-creates parent dirs ## Execution History Each CLI invocation is recorded in the `task_runs` table via `TaskRunRepository`: - Fields: `session_id`, input/output/cache token counts, turn count, `result` text, structured output JSON - Enables auto-retry on failure (resume last session) and multi-turn follow-up via `ContinueAsync` ## Multi-Turn / Continue `TaskRunner.ContinueAsync` sends a follow-up prompt to an existing Claude session using `--resume ` with the stored session ID from the last run. ## SignalR Hub **WorkerHub** methods: `Ping`, `GetActive`, `RunNow`, `CancelTask`, `WakeQueue`, `ContinueTask`, `ResetTask`, `GetAgents`, `RefreshAgents`, `GetAppSettings`, `UpdateAppSettings`, `CleanupFinishedWorktrees`, `ResetAllWorktrees`, `MergeTask`, `GetMergeTargets`, `UpdateList`, `UpdateListConfig`, `GetListConfig`, `UpdateTaskAgentSettings` **HubBroadcaster** events: `TaskStarted`, `TaskFinished`, `TaskMessage`, `WorktreeUpdated`, `TaskUpdated`, `RunCreated`, `ListUpdated` ## Config Loaded from `~/.todo-app/worker.config.json`: - `db_path`, `sandbox_root`, `log_root` - `worktree_root_strategy` ("sibling" | "central"), `central_worktree_root` - `queue_backstop_interval_ms` (default 30000) - `signalr_port` (default 47821) - `claude_bin` (path to claude CLI) Per-list config (`list_config` in DB) provides defaults for `model`, `system_prompt`, `agent_path`; tasks can override each individually. ## Notes - The worker runs standalone — start it separately from the UI - Only listens on loopback (127.0.0.1) - ClaudeProcess uses `--permission-mode auto` by default; legacy "bypassPermissions" settings are mapped to `auto` at dispatch time. `acceptEdits`, `plan`, and `default` pass through unchanged. - Worktree branches follow `claudedo/{id}` naming convention