Skip to content

Implement-tasks loop

Implement every authored task under one slug through sequential Loop actions or a conductor with bounded workers.

ShippedFor people running agent work6 pages in this section

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.

extensions/spec-cycle/loops/implement-tasks/loop.yaml
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: schedule

Run 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

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.

On this page