Skip to content

Tasks

A task is the atomic unit of execution: one worker agent, one git worktree, one branch, one outcome. Tasks are what fan out in parallel inside a project’s Coding Workspace.

  • An imported or created project — see the Quickstart
  • An API key and workspace ID, or a signed-in dashboard session
  • Agency role (owner, admin, or member) to create a task; client collaborators can read and comment but not create

Most tasks are not written by hand. A project run (POST /v1/api/projects/:id/start) plans a roadmap, the roadmap becomes cycles, and each cycle’s planner decomposes it into tasks. Creating a task directly is the exception — useful when you already know the unit of work and want it attached to a specific cycle.

Origin How Executes?
Project run POST /v1/api/projects/:id/start with a brief Yes — the orchestrator dispatches it
Cycle planner Automatic, during a cycle run Yes
POST /v1/api/cycles/:id/tasks You add it to an existing cycle Yes, on the next cycle run
POST /v1/api/tasks You create it standalone No — see below

Over the API:

Terminal window
curl -sS -X POST "$HLIX_BASE_URL/v1/api/tasks" \
-H "x-api-key: $HLIX_API_KEY" \
-H "X-Organization-Id: $HLIX_WORKSPACE_ID" \
-H 'content-type: application/json' \
-d '{"projectId":"'"$PROJECT_ID"'","title":"Add GET /healthz","description":"Return 200 with the build SHA and cover it with a test."}'

projectId (uuid) and description (1–5000 characters) are required; title (1–200 characters) is optional. A 201 returns the created task row.

To attach the task to a specific cycle instead — which is what makes it runnable — post it to the cycle:

Terminal window
curl -sS -X POST "$HLIX_BASE_URL/v1/api/cycles/$CYCLE_ID/tasks" \
-H "x-api-key: $HLIX_API_KEY" \
-H "X-Organization-Id: $HLIX_WORKSPACE_ID" \
-H 'content-type: application/json' \
-d '{"title":"Add GET /healthz","description":"…","dependsOn":[]}'

dependsOn is an array of task UUIDs that must finish first.

Execution always enters through a cycle. Either run the cycle directly (Cycles) or start a project run and let the orchestrator dispatch it.

Thirteen values, from the task_status database enum. A new task starts at queued.

Status Meaning
queued Created, waiting for a wave to pick it up
blocked Held by an unmet dependency or an unresolved request
building A worker agent is editing the worktree
testing Automated checks are running
reviewing The QA evaluator is judging the result
approved The evaluator approved the change
merging The task branch is being merged into the project’s default branch
resolving A merge conflict is being worked
merged The branch landed
pr_created A pull request was opened on the connected repository
ci_passing Provider CI reported success
done Terminal — the task finished
failed Terminal — the task failed

PATCH /v1/api/tasks/:id accepts title, description, and status — but not resolving, which only the merge path sets.

GET /v1/api/tasks/:id/stream is a Server-Sent Events stream of status changes.

With the CLI:

Terminal window
hlix tasks watch "$TASK_ID"

Expected result — one line per status change, exiting at a terminal status:

queued
building
testing
reviewing
done

--json prints one envelope per line (JSONL) so a consumer can read frames as they arrive.

Four properties of this stream are worth knowing before you build on it:

  • Frames are unnamed events. Each data: payload is the full task row, emitted on the first frame and on every status change.
  • There is no done event. The stream simply closes once the status reaches done, failed, pr_created, or ci_passing. Read the status field; do not wait for a terminal event name. (merged is deliberately not a closing status — a merged task usually continues to done.)
  • keepalive is a named event with an empty payload, roughly every 30 seconds while nothing changes.
  • A missing task is a frame, not a 404. The stream opens 200 and sends {"error":"Task not found"} before closing. The status code cannot tell you the task is gone.

The stream is bounded: it polls every 2 seconds for at most 600 iterations, so it ends after roughly 20 minutes whether or not the task finished. Long tasks need a reconnect.

Terminal window
hlix tasks list

Inside a folder bound to a project, this lists that project’s tasks; anywhere else it lists the whole workspace. Expected result:

ID STATUS TITLE
------------------------------------ -------- ------------------
b4a1f0d2-3c77-4e1a-9a2b-1d5e6f7c8a90 building Add GET /healthz
  • --project <id> (string, default the bound project): restrict to one project.
  • --status <status> (string): restrict to one of the statuses above.
Terminal window
hlix tasks list --status failed --json

With no matches it prints No tasks. and exits 0 — an empty result is not a failure.

Terminal window
hlix tasks get "$TASK_ID"

Expected result:

id b4a1f0d2-3c77-4e1a-9a2b-1d5e6f7c8a90
status done
title Add GET /healthz
project 6f1c2a9e-8f0b-4a7d-9d33-2f0b1c7e5a41
description Return 200 with the build SHA and cover it with a test.

hlix tasks get --json passes the API response through unchanged — the human table picks columns, the JSON envelope never drops a field.

GET and POST /v1/api/tasks/:id/comments are the task’s conversation — messages between you and the agents working it. They are not the same surface as line-level review comments, which are anchored to a commit and a file.

Terminal window
curl -sS -X POST "$HLIX_BASE_URL/v1/api/tasks/$TASK_ID/comments" \
-H "x-api-key: $HLIX_API_KEY" \
-H "X-Organization-Id: $HLIX_WORKSPACE_ID" \
-H 'content-type: application/json' \
-d '{"body":"Use the existing logger rather than console."}'

body is required (1–10 000 characters). An optional messageId (uuid) makes the post idempotent: replaying the same id with the same body returns the stored message instead of duplicating it. A view-only client collaborator may comment.

Why did my standalone task never start? Because a task created on its own is a record, not a run. Execution comes from a cycle — the orchestrator dispatches a wave, and tasks in that wave run. A task created through POST /v1/api/tasks waits until something dispatches it.

Can I cancel a running task? Not from the CLI, which has no execution commands at all. PATCH /v1/api/tasks/:id accepts a status change, with one exception: it will not set resolving, which only the merge path may write.

Why did hlix tasks watch exit while the task was still going? The stream is bounded — it polls every 2 seconds for at most 600 iterations, so it ends after roughly 20 minutes regardless. A clean exit at a non-terminal status means “reconnect”, not “finished”. Read the status field, not the exit code.

Where are the logs? There is no log stream. The log endpoint is not part of the published contract, so no client can reach it, and tasks watch streams task status — a different thing, named accordingly.

Two tasks touched the same file. What happens? Each task works in its own git worktree, so they never share a checkout. The collision surfaces at merge time as a genuine conflict and fails that task only; its siblings are unaffected.

  • {"error":"Standalone task execution is not implemented. Run the task's cycle instead (POST /v1/api/cycles/:id/execute)."} — expected, and a 501. Execute the task’s cycle instead.
  • {"error":"Task not found"} with a 404 from /execute — the task resolved to nothing in this workspace. Check the id and X-Organization-Id before assuming the endpoint is at fault.
  • Task stays queued forever — it is not attached to a cycle, or its cycle has not been executed. Check cycleId on the task row.
  • Task stays blocked — an unmet dependsOn, or a pending request is holding it. See Approvals.
  • {"error":"Task not found"} as a stream frame — the id is wrong, or it belongs to another workspace. Check X-Organization-Id.
  • {"error":"Project administration is restricted to the agency"} — you are authenticated as a client collaborator. Clients read and comment; they do not create tasks.
  • The watch command exits after ~20 minutes with the task still running — the stream’s own bound, not a failure. Reconnect.