# 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](/concepts/coding-workspace/).

## Prerequisites

- An imported or created project — see the [Quickstart](/getting-started/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

## Where tasks come from

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 |

## Creating a task

**Over the API:**

```bash
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.

**With the TypeScript SDK** — see [TypeScript SDK](/sdk/typescript/) for building the `hlix` client:

```ts
const task = await hlix.tasks.create({
  projectId,
  title: 'Add GET /healthz',
  description: 'Return 200 with the build SHA and cover it with a test.',
})
```

**In the dashboard:** go to **Tasks → New task**, pick the project, write the description, and submit. You land on the task detail page.

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

```bash
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.

## There is no standalone task run
**POST /v1/api/tasks/:id/execute never executes anything:** The endpoint resolves the task first, so it answers in one of two ways. A task you cannot see — wrong id, or another workspace — is a `404`, and existence is deliberately not revealed:

```json
{"error":"Task not found"}
```

A task that does exist gets a `501`, and is left untouched — no status change, no audit row:

```json
{"error":"Standalone task execution is not implemented. Run the task's cycle instead (POST /v1/api/cycles/:id/execute)."}
```

The `501` is documented in the contract rather than hidden, because a task created on its own and never executed is the single most confusing state in the product.

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

## Task statuses

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.

## Watching a task

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

**With the CLI:**

```bash
hlix tasks watch "$TASK_ID"
```

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

```text
queued
building
testing
reviewing
done
```

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

**Over the API:**

```bash
curl -N "$HLIX_BASE_URL/v1/api/tasks/$TASK_ID/stream" \
  -H "x-api-key: $HLIX_API_KEY" \
  -H "X-Organization-Id: $HLIX_WORKSPACE_ID"
```

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.

## Listing tasks

```bash
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:

```text
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.

```bash
hlix tasks list --status failed --json
```

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

## Reading one task

```bash
hlix tasks get "$TASK_ID"
```

Expected result:

```text
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.

## Task conversation

`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](/running/review/), which are anchored to a commit and a file.

```bash
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.

## Common questions

**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.

## If a task will not run

- `{"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](/running/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.

## Next steps

[Cycles](/running/cycles/)
  [Reviewing output](/running/review/)
  [Approvals](/running/approvals/)
  [Work tree](/concepts/work-tree/)