Implement-tasks loop
Implement every authored task under one slug through sequential Loop actions or a conductor with bounded workers.
What you build
An implementation run over a set of task files. The default mode imports every task under
.compozy/tasks/<slug>, then implements them one at a time in dependency order. The orchestrated
mode gives the same task graph to a conductor that starts and stops one bounded worker per task.
Use it when you have authored tasks under .compozy/tasks/<slug> and want them implemented.
The artifact
This is the complete definition, exactly as it ships in the CompozyOS repository at
extensions/spec-cycle/loops/implement-tasks/loop.yaml. It runs against a current release.
apiVersion: compozy.loop/v1
kind: Loop
meta:
name: implement-tasks
description: Implement pending CompozyOS task files directly or through a task conductor.
catalog:
use_when: "You have authored tasks under .compozy/tasks/<slug> and want them implemented in dependency order, either one Loop action per task or one conductor delegating dedicated workers."
keywords: [tasks, implement, orchestrate, engineering]
category: Engineering
concurrency: forbid
inputs:
slug:
type: string
required: true
mode:
type: string
enum: [per-task, orchestrated]
default: per-task
implementer:
type: agent
default: code_implementer
orchestrator:
type: agent
default: orchestrator
auto_commit:
type: boolean
default: false
orchestrator_runtime:
type: runtime
default: {}
backend_runtime:
type: runtime
default: {}
frontend_runtime:
type: runtime
default: {}
default_runtime:
type: runtime
default: {}
contract:
goal: >
Implement every authored task under .compozy/tasks/{{ .inputs.slug }} in dependency order.
definition_of_done: >
Every loaded task completed implementation, task-level validation, tracking updates, and any
worker session created by the conductor has stopped.
iteration_cap: 50
no_progress:
window: 3
budget:
tokens: 0
wall_clock_sec: 0
on_exceeded: halt
terminal_states: [done, no-op, blocked, failed, exhausted, stalled]
graph:
nodes:
- id: slug_input
class: source
kind: input
input_ref: slug
- id: select_mode
class: control
kind: route
routes:
- when: "inputs.mode == 'orchestrated'"
to: stage_orchestrated
default: select_category
- id: load_tasks
class: action
kind: ext__spec_cycle__import_tasks
params:
pattern: ".compozy/tasks/{{ .inputs.slug }}/task_*.md"
produces:
tasks: array
- id: implement
class: control
kind: fan-out
collection: "{{ .nodes.load_tasks.output.tasks }}"
batch_size: 1
max_parallel: 1
max_fan_out: 64
- id: select_category
class: control
kind: route
routes:
- when: "item.type == 'backend'"
to: execute_backend
- when: "item.type == 'frontend'"
to: execute_frontend
default: execute_default
- id: stage_orchestrated
class: action
kind: transform
params:
map:
task_id:
value: "{{ .item.id }}"
status:
value: staged
produces:
task_id: string
status: string
- id: execute_backend
class: action
kind: run-agent
params:
agent: "{{ .inputs.implementer }}"
runtime: "{{ .inputs.backend_runtime }}"
prompt: &implement_prompt |
Kickoff directive:
Begin work on {{ .item.title }} immediately. This run is the operator's authorization
to implement exactly this pending task — do NOT ask for confirmation, do NOT wait for
further instructions, and do NOT reply with a greeting before starting.
Required skills:
- cy-workflow-memory: use before editing code; the memory paths are listed below.
- cy-execute-task: the end-to-end execution workflow for this task.
- cy-final-verify: required before any completion claim or automatic commit; use it to
identify and run the repository's real verification commands.
Task context:
Task file: {{ .item.path }}
Task id: {{ .item.id }}
Slug: {{ .inputs.slug }}
{{ if .item.blocks }}Depends on: {{ join ", " .item.blocks }}{{ end }}
Workflow memory:
- Memory directory: .compozy/tasks/{{ .inputs.slug }}/memory
- Shared memory: .compozy/tasks/{{ .inputs.slug }}/memory/MEMORY.md
- Task memory: .compozy/tasks/{{ .inputs.slug }}/memory/{{ .item.id }}.md
- Read both memory files before implementation and update them before finishing.
- Keep task-local decisions, learnings, touched surfaces, and corrections in the task
memory file; promote only durable cross-task context into shared memory.
Scope and tracking:
- Read repository AGENTS.md/CLAUDE.md and surface-specific instructions before editing.
- Open and read {{ .item.path }} before implementation. Treat that file plus
.compozy/tasks/{{ .inputs.slug }}/_spec.md and _tasks.md, when present, as the
source of truth.
- Keep scope tight to this task; record meaningful follow-up work instead of expanding
scope silently.
- Preserve unrelated worktree changes.
- Fix production code for real; do not weaken tests or add compatibility shims.
Verification:
- Run focused checks for each changed surface.
- Execute every explicit Validation, Test Plan, or Testing item from the task file.
- Report exact commands and outcomes in the structured output.
Tracking and commits:
- Write `status: completed` and update task checkboxes in {{ .item.path }} only after implementation,
verification evidence, and self-review are complete.
- Update .compozy/tasks/{{ .inputs.slug }}/_tasks.md only when this task is complete.
- Keep tracking-only files out of automatic commits.
{{ if .inputs.auto_commit -}}
- Create exactly one commit for this task after clean verification, self-review, and
tracking updates. Do not push.
{{ else -}}
- Leave changes uncommitted for manual review. Do not push.
{{ end }}
Closing directive:
You have the full brief above — start work on {{ .item.title }} now instead of
summarizing the plan back. Return `status`, `summary`, and `files_changed`. Use
only `completed` after all required implementation and verification work succeeds.
output_schema: &implement_output
type: object
required: [status, summary]
properties:
status:
enum: [completed]
summary:
type: string
files_changed:
type: array
session: &implement_session
isolated: true
timeout: 45m
retry: &implement_retry
max_attempts: 2
- id: execute_frontend
class: action
kind: run-agent
params:
agent: "{{ .inputs.implementer }}"
runtime: "{{ .inputs.frontend_runtime }}"
prompt: *implement_prompt
output_schema: *implement_output
session: *implement_session
timeout: 45m
retry: *implement_retry
- id: execute_default
class: action
kind: run-agent
params:
agent: "{{ .inputs.implementer }}"
runtime: "{{ .inputs.default_runtime }}"
prompt: *implement_prompt
output_schema: *implement_output
session: *implement_session
timeout: 45m
retry: *implement_retry
- id: collect
class: control
kind: collect
- id: select_delivery
class: control
kind: route
routes:
- when: "inputs.mode == 'orchestrated' && size(nodes.load_tasks.output.tasks) > 0"
to: orchestrate
default: per_task_done
- id: per_task_done
class: action
kind: transform
params:
map:
status:
value: completed
produces:
status: string
- id: orchestrate
class: action
kind: goal
session:
mode: continuous
params:
agent: "{{ .inputs.orchestrator }}"
runtime: "{{ .inputs.orchestrator_runtime }}"
objective: |
Activate the `cy-orchestrate-tasks` skill and follow it strictly for the spec
`.compozy/tasks/{{ .inputs.slug }}`.
Selected implementer Agent: `{{ .inputs.implementer }}`.
Conduct only. Read the task graph and spawn one bounded worker session using the selected
implementer Agent per task, in dependency order. `code_implementer` is only the input
default; never substitute it for a different selected Agent. For each task: dispatch the
briefing, wait for the turn to end, re-read the task frontmatter as proof, and stop the
worker before advancing. Implementation belongs to the workers — leave every code edit
to them.
Pass these category runtime inputs to each spawned worker. Use the backend runtime only
for exact task type `backend`, the frontend runtime only for exact task type `frontend`,
and the default runtime for every other task type. Omit runtime flags whose values are
absent:
- backend: {{ json .inputs.backend_runtime }}
- frontend: {{ json .inputs.frontend_runtime }}
- default: {{ json .inputs.default_runtime }}
Return `status`, `summary`, and `tasks`, naming each task with the worker session id that
executed it. Task files must use `status: completed` after verification. Existing completion
aliases `complete`, `done`, and `finished` are also recognized by the task importer and
judge; do not dispatch those tasks again. Only `pending` and `in_progress` need work;
an unknown status requires correction before dispatch.
The Goal JSON result is separate from task frontmatter: return `{"status":"complete"}`
only after the task importer reports no pending tasks and no conductor-created worker
is starting, active, or stopping. If any worker cannot be stopped, return `blocked`.
judge:
- id: tasks_completed
type: extension
tool: ext__spec_cycle__import_tasks
inputs:
pattern: ".compozy/tasks/{{ .inputs.slug }}/task_*.md"
- id: workers_stopped
type: command
check: >-
slug={{ .inputs.slug | shellQuote }};
for state in starting active stopping; do
sessions="$("$COMPOZY_BIN" session list --type spawned --state "$state" --query "orchestrate-${slug}-" --limit 1 -o jsonl)" || exit 1;
if printf '%s\n' "$sessions" | grep -q '"id"[[:space:]]*:'; then exit 1; fi;
done
max_turns: 12
output_schema:
type: object
required: [status]
properties:
status:
enum: [complete, blocked]
summary:
type: string
tasks:
type: array
edges:
- from: slug_input
to: load_tasks
- from: load_tasks
to: implement
- from: implement
to: select_mode
- from: select_mode
to: select_category
- from: select_mode
to: stage_orchestrated
- from: select_category
to: execute_backend
- from: select_category
to: execute_frontend
- from: select_category
to: execute_default
- from: stage_orchestrated
to: collect
- from: execute_backend
to: collect
- from: execute_frontend
to: collect
- from: execute_default
to: collect
- from: collect
to: select_delivery
- from: select_delivery
to: per_task_done
- from: select_delivery
to: orchestrate
start:
- kind: manual
- kind: cli
- kind: http
- kind: uds
- kind: native_tool
- kind: scheduleRun it
slug is the one required input. mode defaults to per-task; choose orchestrated when you want
the bundled orchestrator Agent to conduct dedicated workers. implementer selects the worker
Agent in both modes and defaults to code_implementer. The four optional runtime inputs select the
conductor, backend workers, frontend workers, and every other worker. Empty runtime inputs fall
through to Agent and config defaults, while a task file's own runtime fields win.
# check the definition against compozy.loop/v1
compozy loop validate loop.yaml
# publish it to your runtime
compozy loop create loop.yaml
# rehearse a generation without side effects, then run it
compozy loop run --name implement-tasks --input slug=<slug> --dry-run
compozy loop run --name implement-tasks --input slug=<slug>
# conduct one bounded worker per task with per-category runtime choices
compozy loop run --name implement-tasks \
--input slug=<slug> \
--input mode=orchestrated \
--input implementer=<agent> \
--input 'orchestrator_runtime={"provider":"codex","model":"gpt-5.6-sol","reasoning":"high"}' \
--input 'backend_runtime={"provider":"codex","model":"gpt-5.6-sol"}' \
--input 'frontend_runtime={"provider":"claude","model":"sonnet"}'The definition ships inside the bundled spec-cycle extension, so the Loop is already published in
your runtime. Validate and create are for when you copy the file out and adapt it.
Its start surfaces are manual, CLI, HTTP, UDS, native tool, and schedule — note that unlike the review Loop it declares no webhook start, so a signed HTTP delivery cannot begin the run.
How it works
Read the slug — `slug_input`
A source node binds the required slug, then select_mode chooses the per-task or orchestrated
branch. Existing runs omit mode and keep the per-task path.
Import the task files — `load_tasks`
An extension action parses .compozy/tasks/<slug>/task_*.md into ordered pending task
payloads, each carrying its id, title, path, body reference, and the tasks it depends on.
Implement one task at a time — `implement` to `select_category`
A fan-out routes each task by exact frontmatter type. backend and frontend select their named
runtime input; every other type selects default_runtime. Each isolated task session opens the
referenced task file and receives workflow memory, verification expectations, and commit policy.
Conduct bounded workers — `orchestrate`
In orchestrated mode, one continuous Goal session follows cy-orchestrate-tasks. It starts a
worker with the selected implementer Agent per task, applies the category runtime, waits for
proof on disk, and stops the worker on every path. Omitting implementer selects
code_implementer. The command judge accepts only completed task frontmatter and zero surviving
conductor workers.
Collect the results — `collect`
The terminal collect node joins the sequential task results. When every task action succeeds,
the daemon closes the Loop as done without adding a review, command, or approval gate.
The daemon enforces the exit
iteration_cap: 50, a no-progress window of 3 generations, concurrency: forbid, and
on_exceeded: halt on the budget remain in force. Every run ends in a named terminal state.
Next steps
Review-and-fix loop
The smaller sibling: review one task with an agent and remediate every finding until a round comes back clean.
Goals and stop conditions
How a Loop contract states done, what the daemon checks each generation, and which outcomes it can record.
Guardrails
How iteration, no-progress, budgets, concurrency, and named terminal outcomes bound a Loop run.
The spec-cycle extension
The implementer and reviewer agents, the task-import tool, and the skills these Loops load.
Task completion and reruns
Write status: completed after implementation and verification. The task importer also recognizes
complete, done, and finished, ignoring case and surrounding whitespace. YAML quoting and
inline comments do not change the meaning. Only pending and in_progress are dispatched;
unknown or missing statuses produce validation errors before any task worker starts.
The completion judge calls ext__spec_cycle__import_tasks, the same parser used to load the task
graph. Its additive passed result is true when count is zero. A separate command judge verifies
that spawned workers have stopped. Rerunning an already-finished task set starts no task workers.
Files are read without rewriting existing status values, and no database or configuration migration
is needed. The Goal JSON result retains complete|blocked; workers write completed in task files.
Review-and-fix loop
Review a named task with an agent, write inspectable findings, and preserve anything that cannot be resolved until a review round comes back clean.
Morning briefing job
A cron job that wakes an agent every weekday morning, has it read the state of your workspace, and leaves a written briefing waiting for you.