Cycles
A cycle is a meaningful chunk of a project — a feature, a release, a phase — and it is the unit that actually executes. Tasks fan out inside a cycle run; they do not run on their own.
Prerequisites
Section titled “Prerequisites”- A project with at least one cycle, usually planned by a project run
- Agency role (
owner,admin,member); a client collaborator with steer rights can execute but not edit - Model access configured for the workspace
Proposing a cycle
Section titled “Proposing a cycle”curl -sS -X POST "$HLIX_BASE_URL/v1/api/cycles" \ -H "x-api-key: $HLIX_API_KEY" \ -H "X-Organization-Id: $HLIX_WORKSPACE_ID" \ -H 'content-type: application/json' \ -d '{"projectId":"'"$PROJECT_ID"'","title":"Health and readiness","description":"Add liveness and readiness endpoints with tests and docs."}'The response shape — adopted is true only when the orchestrator folded the proposal into the roadmap on this pass:
{"proposed":true,"adopted":false}projectId, title (1–200), and description (1–10 000) are required.
An adopted proposal becomes a cycle at status triage, and the Triage Agent decides whether it goes any further. That is true of every intake path — chat, the API, Linear, or the project’s founding brief — so a cycle in your list at triage has been created but not yet admitted.
Executing a cycle
Section titled “Executing a cycle”curl -sS -X POST "$HLIX_BASE_URL/v1/api/cycles/$CYCLE_ID/execute" \ -H "x-api-key: $HLIX_API_KEY" \ -H "X-Organization-Id: $HLIX_WORKSPACE_ID"Expected result:
{"message":"Cycle execution started","threadId":"<workspace-id>:<cycle-id>"}The optional ?mode= query takes sandbox (default) or desktop.
Execution is gated on status: only draft, planning, failed, and interviewing cycles can be executed. A cycle still in triage is not executable — Triage admits it first. Re-executing a failed or interviewing cycle resets it to draft and returns every failed or blocked task to queued — a retry is a genuine retry, not a second parallel run. That reset is itself a recorded transition, attributed to you.
The response means the run was queued, not finished. If queuing itself fails you get a 502 and nothing was started:
{"error":"The cycle run could not be queued, so it was not started. Nothing is running — try again."}Cycle statuses
Section titled “Cycle statuses”| Status | Meaning |
|---|---|
triage |
Created and awaiting the Triage Agent’s decision |
draft |
Admitted, not yet running |
interviewing |
The orchestrator is clarifying requirements with you |
planning |
Decomposing the cycle into tasks |
executing |
Tasks are running |
e2e |
End-to-end verification |
pr_created |
Terminal — the work landed as a pull request |
failed |
Terminal — the cycle failed |
canceled |
Terminal — Triage refused the request |
Stage history
Section titled “Stage history”Every status move is a governed transition: it is recorded on the cycle, attributed, and stamped with the platform rules in force at the time. GET /v1/api/cycles/:id returns the whole record as stageHistory, oldest first, alongside a revision counter.
{ "from": "triage", "to": "draft", "at": "2026-08-12T09:41:07.220Z", "actor": { "type": "agent", "id": "triage-agent" }, "ruleSetVersion": "hlix-web-v1", "reason": "Schema change is understood and reversible."}| Field | What it answers |
|---|---|
from / to |
Which move this was |
at |
When |
actor.type |
Who decided: human, agent, or rule |
actor.id |
Which one — a user id, or a stable agent/rule identifier such as triage-agent, orchestrator, gate-denied |
ruleSetVersion |
Which version of the platform rules governed the move |
reason |
Free text, when the mover supplied one |
actor.type is the distinction that matters. human is a person acting through the dashboard or the API. agent is an LLM-driven decision — the Triage Agent forwarding a request, the orchestrator submitting a plan. rule is deterministic platform logic with no model involved, such as a denied approval failing the cycle.
revision is a compare-and-swap counter, incremented once per transition. Two writers racing the same cycle cannot both win: the loser is rejected with the row’s real current revision rather than silently overwriting the winner.
Priority and lane
Section titled “Priority and lane”Triage writes two more fields onto every cycle it forwards.
priority— an integer derived from the urgency tier Triage judged:300urgent,200high,100normal,0low. Ready cycles are dispatched highest first.lane— the risk lane the platform rules resolved from the complexity and risk judgement. Four lanes force a human approval before dispatch even when the project has not opted in.
Both are on the cycle row and in the published contract. Triage covers how each is derived.
To list a project’s cycles in dispatch order rather than creation order:
curl -sS "$HLIX_BASE_URL/v1/api/cycles?projectId=$PROJECT_ID&orderBy=priority" \ -H "x-api-key: $HLIX_API_KEY" \ -H "X-Organization-Id: $HLIX_WORKSPACE_ID"orderBy accepts createdAt (the default, newest first) or priority (highest first, ties keeping creation order). Any other value is a 400. The dashboard’s cycles grid offers the same two sorts.
Watching a cycle
Section titled “Watching a cycle”GET /v1/api/cycles/:id/stream streams the cycle’s event timeline rather than a status field, so you see the run’s narrative as it happens.
curl -N "$HLIX_BASE_URL/v1/api/cycles/$CYCLE_ID/stream" \ -H "x-api-key: $HLIX_API_KEY" \ -H "X-Organization-Id: $HLIX_WORKSPACE_ID"- Unnamed events carry one timeline event row each (
entityType,eventType,payload,actorType,traceId,timestamp). event: donefires with{"status":"pr_created"}or{"status":"failed"}when the cycle reaches a terminal status, then the stream closes. Unlike the task stream, this one does announce its ending.event: keepalivewith an empty payload arrives roughly every 30 seconds.- The stream is bounded at 600 polls of 2 seconds — about 20 minutes. Reconnect for longer runs.
For a non-streaming read of the same material use GET /v1/api/cycles/:id/timeline (cycle plus child-task events) or GET /v1/api/cycles/:id/events (cycle events only).
Reading a cycle
Section titled “Reading a cycle”GET /v1/api/cycles/:id returns the cycle row plus its tasks — each with a dependsOn array — and previewUrl and linearIssueUrl when they exist.
curl -sS "$HLIX_BASE_URL/v1/api/cycles/$CYCLE_ID" \ -H "x-api-key: $HLIX_API_KEY" \ -H "X-Organization-Id: $HLIX_WORKSPACE_ID"GET /v1/api/cycles?projectId=… lists them.
Managing the task set
Section titled “Managing the task set”| Operation | Endpoint | Constraint |
|---|---|---|
| Add a task | POST /v1/api/cycles/:id/tasks |
title and description required; dependsOn optional |
| Edit a task | PATCH /v1/api/cycles/:id/tasks/:taskId |
title, description, sortOrder |
| Remove a task | DELETE /v1/api/cycles/:id/tasks/:taskId |
Only queued or blocked tasks |
| Edit the cycle | PATCH /v1/api/cycles/:id |
title, description, targetBranch |
| Delete the cycle | DELETE /v1/api/cycles/:id |
Only draft or failed cycles |
Deleting a running cycle is refused rather than racing the engine:
{"error":"Can only delete cycles in draft or failed status"}Talking to a cycle
Section titled “Talking to a cycle”POST /v1/api/cycles/:id/messages sends a message into the cycle thread (content required; agentId addresses one worker’s 1:1 thread; messageId makes it idempotent). While the cycle is interviewing, a message with no agentId can advance the lifecycle — the response carries lifecycleAdvanced and lifecycleReason.
POST /v1/api/cycles/:id/messages/stream is the streaming form. Its SSE events are named, unlike the task stream:
| Event | Payload |
|---|---|
user_message |
{"id":…,"content":…} — your message, as stored |
token |
A raw text chunk, not JSON-wrapped |
tool_start |
{"name":…,"input":…} |
tool_end |
{"name":…,"output":…} |
complete |
{"id":…,"content":…} — the final assistant message |
error |
{"message":…} — then the stream returns |
Artifacts and cost
Section titled “Artifacts and cost”GET /v1/api/cycles/:id/artifactslists what the run produced.GET /v1/api/cycles/:id/artifacts/:filenamereturns{"url":…,"key":…}, or the raw bytes with?download=1.GET /v1/api/cycles/:id/usagereturns{"totals":{…},"byRole":[…]}with token counts and cost per agent role.
If a cycle will not execute
Section titled “If a cycle will not execute”{"error":"Cycle cannot be executed in \"executing\" status"}— it is already running. Watch it instead of starting a second run.{"error":"Cycle not found"}— wrong id, or another workspace. CheckX-Organization-Id.{"error":"Project not found for cycle"}— the cycle outlived its project; it cannot be run.{"error":"The cycle run could not be queued, so it was not started. Nothing is running — try again."}— a502. Nothing was half-started; retry is safe.{"error":"Can only delete tasks in queued or blocked status"}— the task is past the point where removing it is safe.{"proposed":true,"adopted":false}and no cycle appears — the orchestrator did not adopt the proposal. Re-read the roadmap before proposing again.{"error":"Cycle cannot be executed in \"triage\" status"}— the Triage Agent has not admitted it yet. There is no way to execute past that decision.- The cycle reads
canceled— Triage refused the request. The reason is on the laststageHistoryentry. - The cycle parks and nothing happens — the project has approval required, or its lane demands one regardless. See Approvals.