Skip to content

Recipe: parallel cycle delivery

The scenario: a cycle decomposes into eight tasks. You want them running at once, not queued — and you want to know, before you start, what happens when two of them edit the same file.

The short answer: they cannot corrupt each other, and a genuine collision fails exactly one task. This recipe explains why, and what to do about the failure.

  • An imported project with at least one cycle
  • Model access configured for the workspace — see Connect your agents

Three properties, and they are structural rather than best-effort:

Property What it means
A worktree per task Every task gets its own git working tree off the project’s shared clone. Two tasks never share a checkout.
One sandbox per project All of those worktrees live inside the project’s single Coding Workspace — no per-task cold boot, no per-task clone.
Per-task outcomes The wave collects results independently. One task failing does not cancel its siblings.

Merging is where parallelism actually meets: task branches merge into the default branch under a per-repository lock, so merges serialise even though the work did not.

  1. Let the planner decompose. Start the project run with an outcome, not a task list — the roadmap becomes cycles, and each cycle’s planner produces the wave.

    Terminal window
    curl -sS -X POST "$HLIX_BASE_URL/v1/api/projects/$PROJECT_ID/start" \
    -H "x-api-key: $HLIX_API_KEY" \
    -H "X-Organization-Id: $HLIX_WORKSPACE_ID" \
    -H 'content-type: application/json' \
    -d '{"brief":"Add tenant-scoped rate limiting across the public API, with tests and updated docs."}'

    Expected result: {"started":true} — the run was handed to the engine, not finished.

  2. Watch the wave, not one task. Filter the task list by status to see the shape of the wave:

    Terminal window
    hlix tasks list --status building
    ID STATUS TITLE
    ------------------------------------ -------- ------------------------
    b4a1f0d2-3c77-4e1a-9a2b-1d5e6f7c8a90 building Add rate-limit middleware
    c7d2e1a3-4b88-4f2b-8c3d-2e6f7a8b9c01 building Cover limits with tests
  3. Follow one task when you care about it.

    Terminal window
    hlix tasks watch "$TASK_ID"

    The stream is bounded — roughly 20 minutes — and closing is the only terminal signal. A clean exit at a non-terminal status means reconnect, not finished.

  4. Review per task, not per cycle. Each task carries its own evidence, and a cycle is approved by its tasks passing:

    Terminal window
    hlix tasks review "$TASK_ID"
  5. Handle the one that failed to merge. If a task ends failed with a merge conflict, its siblings are unaffected and already merged. Re-dispatch that task; its base is now the merged result, so the conflict that existed against the old base usually does not exist against the new one.

  • Brief for outcomes, not files. “Add rate limiting to the public API” decomposes into non-overlapping work; “edit server.ts and limits.ts” invites two tasks into the same file.
  • Expect the reviewer role to touch broadly. A QA or Security Engineer task often reads widely. That is fine — reads never collide.
  • Do not scale a wave to hide a slow harness. Parallelism is bounded by your provider’s rate limits; twelve tasks against a throttled key finish no sooner than four.
  • Watch the merge lock, not the CPU. Merges serialise per repository. In a multi-repository project each repository’s lock is independent, so two repositories merge concurrently.
  • Tasks stay queued and never start. The dispatch refused a harness. See If a task will not start — a preference naming an unavailable coder fails loudly rather than substituting one.
  • One task failed, the rest are done. Expected on a genuine conflict. Read that task’s error, re-dispatch it.
  • Every task failed identically. Not a collision — a shared cause, usually a missing provider key or a project secret that never made it into the environment. See If a secret does not reach the workspace.
  • hlix tasks watch exits while work continues. The stream is bounded. Reconnect; read the status field rather than the exit code.
  • The cycle will not execute at all. See If a cycle will not execute.