--- name: orchestrate-agent-task-loop description: Execute dependency-ready PLAN and CODE_REVIEW task loops with workspace write claims, a bundled default agent/model catalog, runtime catalog overrides, deterministic target failover, and persistent recovery state. --- # Orchestrate Agent Task Loop ## Final-channel gate Do not end the caller turn through `final` until either: - every in-scope task has a verified archived `complete.log`, every generated work log is archived, no task or execution remains active, and the dispatcher exits `0`; or - the user explicitly asks to stop the current run. Use `commentary` for non-terminal status, blockers, questions, recovery notices, and partial completion. If the user asked for a persistent run, successful completion alone does not release this gate. ## Purpose Monitor the file-backed workflow under `agent-task/` and converge ready PLAN implementation, optional self-check, official review, follow-up PLAN, and archive completion. The dispatcher owns deterministic scheduling, recovery, target transitions, and runtime evidence. Child agents own implementation and review judgments within their assigned artifact. ## Inputs - `workspace`: trusted repository root containing `agent-task/`; defaults to the current directory. - `execution_catalog`: optional runtime agent/model catalog override, supplied with `--execution-catalog` or `AGENT_TASK_EXECUTION_CATALOG`; otherwise use `assets/default-execution-catalog.json`. - `task_group`: optional `agent-task/` scope. - `dry_run`: inspect routes, dependencies, claims, and catalog validity without launching an agent. - `max_parallel`: workspace-wide active task-stage limit; defaults to `3`; `0` means unlimited. - `retry_blocked`: retry eligible blocked tasks without changing their catalog route history. - `accept_catalog_revision`: explicit operator acceptance that replaces only a stale, inactive, incomplete worker selection in the required `task_group`; dry-run previews the reset and live execution records it in audit history. `--validate-plan` validates one PLAN without launching orchestration and therefore does not require an execution catalog. ## Preconditions - Read the current plan and code-review contracts routed by `agent-ops/skills/common/router.md`. - Use the bundled default catalog unless the runtime or project layer supplies an override. - Run `--dry-run` before the first live execution. - Never bypass the physical-workspace dispatcher lock. - Keep automatic approval inside the current workspace and the PLAN's declared write set. ## Runtime catalog contract The bundled catalog is `assets/default-execution-catalog.json`. Catalog resolution order is explicit argument, `AGENT_TASK_EXECUTION_CATALOG`, then the bundled default. Every catalog root contains exactly `schema_version`, `targets`, and `routes`. It must cover `worker` and `review`, and each stage must define every `local-G01` through `local-G10` and `cloud-G01` through `cloud-G10` route. Each target has: - an opaque `agent` identity; - an opaque `model` identity; - optional `reasoning_effort`, stored as a separate opaque catalog value rather than embedded in dispatcher code or a literal command argument; - `execution_class`: `local_model` or `cloud_model`; - optional `selfcheck_required` boolean; - `runtime.command`: a non-empty argv template executed without a shell; - optional `runtime.resume_command`, `preflight_command`, `environment`, `session_path`, `native_session_monitor`, `terminal_success`, and `auxiliary_logs`; - optional `runtime.output_format`: `text` or `jsonl`. Command templates may use only `{agent}`, `{model}`, `{reasoning_effort}`, `{target_id}`, `{workspace}`, `{attempt_dir}`, `{session_id}`, `{resume_session}`, `{resume_session_dir}`, and `{prompt}`. A target with `reasoning_effort` must use `{reasoning_effort}` in its command and resume command when present; a target without the field cannot use that placeholder. `native_session_monitor=true` requires both `resume_command` and `session_path`. `terminal_success=agent_end` requires JSONL output and accepts only a non-retrying final `agent_end` whose last assistant message has `stopReason=stop`. `terminal_success=turn_completed` requires JSONL output and accepts only final `turn.completed`; `turn.failed` or a missing terminal event fails closed. When either declared success event is observed with exit 0, earlier recovered transport diagnostics do not turn the attempt into a failure. The catalog must not embed repository secrets; environment values should refer only to runtime-provided non-secret configuration. Each route owns its ordered `candidates` plus optional `rule_id`, `policy_priority`, and `reason_codes`. A route may use catalog-owned `windows` instead of a fixed candidate list; every window supplies an IANA timezone, start/end time, and candidates. Exactly one window must match. The bundled review routes vary model and reasoning effort by routed grade instead of fixing every review to one target: G01-G04 use Terra/high, G05-G08 use Sol/high, and G09-G10 use Sol/xhigh. Runtime or project catalog overrides may replace this default tiering. Before work starts, the dispatcher: 1. resolves and validates the entire catalog; 2. verifies exact route coverage and every target reference; 3. verifies each target command is executable; 4. runs an optional target `preflight_command` for live execution; 5. records the catalog source and SHA-256 revision in the decision. A persisted decision is valid only while the resolved catalog revision and selected target snapshot still match. Catalog changes fail closed instead of silently changing an active work unit. After an intentional catalog update, `--accept-catalog-revision` may reset only an inactive worker decision that has not completed; it requires `--task-group`, refuses live attempts and review decisions, clears failures charged to the obsolete worker selection, and preserves an audit record of the prior decision. Use it with `--dry-run` first. ## Selection and failover - Initial execution selects the first candidate in the resolved route. - Resume pins the persisted target and route revision. - The dispatcher never queries quota before admission and never accepts a quota snapshot as selector input. - Classify actual terminal output after an attempt. `provider-quota`, `context-limit`, `model-unavailable`, `provider-stream-disconnect`, and `provider-connection` may advance to the next unused route candidate. - In particular, a confirmed quota/rate-limit error advances directly to the next candidate. A plain mention of quota in source text, model prose, or non-terminal output is not sufficient evidence. - `generic-error` does not imply quota. Persist its count per work-unit, stage, and selected target. Retry the same target for the first two generic failures; on the third, move to the next unused candidate immediately. If no candidate remains, block at `3/3` instead of spending the 10-failure stage budget. Process termination, work-log failure, and review-control failure do not change the selected target. - Never use a hidden promotion table or provider-specific fallback. If no next catalog candidate exists, keep recovery within the stage budget or block the task with evidence. - Transfer logical context using the prior locator, normalized output, raw stream, workspace, and PLAN. Use native resume only when both targets opt into the same catalog-declared native-session mechanism and the session belongs to the current workspace. ## Scheduling and write claims - Admit every dependency-ready task whose canonical PLAN write set does not collide with another active claim. - Require exactly one non-empty `Modified Files Summary` or supported legacy heading. Reject broad, malformed, directory, outside-workspace, or missing paths. - Atomically claim canonical paths before worker, self-check, or review execution. Keep a task's claim across retries and follow-up PLANs; release it only after verified archive completion. - Treat explicit predecessors as unfinished while matching live execution evidence exists, even if a `complete.log` is already visible. - Apply `max_parallel` across the physical workspace, independent of task-group filtering. Do not count internal helper coroutines as agent slots. - A blocker delays only that task and its dependency closure. Continue draining independent work. ## Prompt and child boundary Prefix worker and review prompts with the dispatcher-child boundary that prohibits starting or monitoring another orchestration loop. A child may use `dispatch.py --validate-plan` only when its plan or review finalization requires it. Prompts must include absolute artifact paths and instruct the child to follow the repository's language and output rules. Do not hardcode a programming language, human language, agent, model, or provider in common prompts. Never ask a child to create, edit, or summarize `WORK_LOG.md`; that file is dispatcher-owned. ## Self-check Run self-check only when the selected catalog target declares `selfcheck_required=true`. The completing decision, not a fixed agent identity or execution class, determines the requirement. Treat worker exit `0` as transport completion only. Before marking the worker done, require at least one claimed file or implementation-evidence change and a complete implementation-owned checklist (or concrete blocker evidence). Classify a no-op or incomplete-evidence exit as `generic-error`, apply the same per-target three-error budget, and return persisted `worker_done` state to the worker stage while that contract remains incomplete. Apply that bounded three-attempt target budget to `session-stall` as well, so a repeatedly silent candidate advances instead of consuming the ten-attempt stage recovery budget. Accept self-check completion only when `## Implementation Checklist` or its supported legacy heading contains at least one checkbox and every checkbox has a non-empty value. Run one full pass, then resume the latest successful native context for at most 10 unchecked-item retries when the target supports native resume. Block instead of silently starting a new context when a required persisted context is unavailable. ## Runtime evidence and recovery - Store each attempt under the dispatcher state directory with `locator.json`, `stream.log`, `normalized-output.log`, and `heartbeat.log`. - Record the target id, opaque agent/model identity, execution class, runtime contract, catalog evidence, process identity, workspace identity, timestamps, result, and exact failure evidence. - Treat stderr as terminal diagnostic evidence. For JSONL, recognize generic terminal event fields such as error/fatal type or severity, rejected/failed status with an error code, explicit error flags, and a non-retrying `agent_end` whose last assistant message ends with `error` or `aborted`. - Determine liveness from PID/start-token/process-marker evidence and actual stream or native-session progress. Heartbeat mtime is never agent progress. For Codex JSONL, an unmatched `item.started` `command_execution` is an active tool interval: suspend the model-response silence timer until its matching `item.completed`, then restore normal stall detection. - Never start a duplicate attempt while owned live evidence remains. - Keep a 10-consecutive-failure budget per task stage. Reset only that stage's budget after success. - Preserve failed attempt logs. Delete successful attempt logs only after verified archive completion and no live evidence. ## Work log - Keep one dispatcher-owned `WORK_LOG.md` per task group. - Append chronological `START` and `FINISH` rows with KST (`Asia/Seoul`) time, task artifact, plan loop, role, attempt, selected agent/model display, result, and locator. Use the PLAN artifact for worker and self-check rows; use the CODE_REVIEW artifact only for official review rows. - Archive the group log as the next `work_log_N.log` only after every observed task in the group is verified complete and idle. - Work-log write or archive failure is a retryable control-plane failure and prevents exit `0`. ## Invocation ```bash python3 agent-ops/skills/common/orchestrate-agent-task-loop/scripts/dispatch.py \ --workspace /absolute/repository \ --dry-run ``` Remove `--dry-run` to start execution. Add `--execution-catalog ` only to override the bundled default. Add `--task-group `, `--max-parallel `, or `--retry-blocked` only when requested by the workflow. After an intentional catalog replacement invalidates a persisted incomplete worker decision, preview and accept it explicitly: ```bash python3 agent-ops/skills/common/orchestrate-agent-task-loop/scripts/dispatch.py \ --workspace /absolute/repository \ --task-group \ --accept-catalog-revision \ --dry-run ``` Remove `--dry-run` only after the preview shows the intended task scope. Launch the live dispatcher as one persistent foreground process. Do not wrap it in an arbitrary timeout and do not start a second dispatcher after a normal tool yield. Wait on the same execution handle until an attention event or terminal exit. ## Completion checklist - [ ] The resolved catalog was fully validated, preflighted, and revision-pinned. - [ ] No hidden route outside the resolved catalog or quota probe was used. - [ ] Runtime quota errors moved only to the next catalog candidate. - [ ] Dependencies, write claims, and workspace concurrency were enforced. - [ ] Required self-check and official review stages completed. - [ ] Every observed task has a verified archived `complete.log`. - [ ] Work logs and successful attempt cleanup were reconciled. - [ ] No active, waiting, pending, or blocked in-scope task remains. - [ ] Dispatcher exited `0` before successful final response.