Skip to content

Approvals

An approval is a blocking pending request that stops a cycle from being dispatched until a person decides. Projects are autonomous by default. Two things can open one: a project policy you turn on, and a risk lane severe enough to demand one on its own. Neither is hidden — both name themselves on the request.

  • A project you can administer (agency owner, admin, or member)
  • A cycle that has not yet been dispatched

Approval is the project-level requiresApproval flag, default off.

In the dashboard: it is the approval gate switch, in two places for the same flag — Project settings → Agent → Behavior, and inline on a cycle’s Request Queue tab for the operator already looking at a run.

With the flag set, the project orchestrator opens a blocking deployment_approval request before launching a gated cycle and parks the run. Approving resumes dispatch; denying fails that cycle. With the flag off, an autonomous run is byte-for-byte unchanged — the gate adds no step it does not need to.

Set the flag once for the workspace and every project created afterwards starts with it:

Terminal window
curl -sS -X PUT "$HLIX_BASE_URL/v1/api/settings/org/project-defaults" \
-H "x-api-key: $HLIX_API_KEY" \
-H "X-Organization-Id: $HLIX_WORKSPACE_ID" \
-H 'content-type: application/json' \
-d '{"requiresApproval":true}'

Three things about it are worth being precise on:

  • It applies at creation only. A project’s own requiresApproval is concrete from the moment the row exists, so changing the workspace default never reaches back and rewrites projects that already exist. Change those individually.
  • An explicit choice wins. Creating a project with requiresApproval in the body uses that value; the default fills in only when the request says nothing.
  • null clears it, returning the workspace to “no default”; omitting a field leaves it as it was. GET on the same path reads the current values.

Writing this setting requires owner or admin. It is not in the published contract, so there is no generated SDK method for it.

A cycle can park for approval on a project that never turned the gate on. Triage classifies every cycle into a risk lane, and four of those lanes — stop, design-review, architecture-review, and manual-lane — open the same blocking request on their own.

The two conditions OR together: the project’s flag, or the cycle’s lane. Whichever says “gate” wins, and there is no way to opt a lane-gated cycle out short of re-triaging it.

The request’s context names the cycle’s lane whenever it has one:

{"projectId":"","gate":"cycle_dispatch","lane":"architecture-review"}

Read it as the classification, and infer from it. One of the four lanes above means this cycle would have parked whatever the project’s setting says. Any other lane — or no lane key at all — means the project’s own policy is what stopped it. Review & QA gates explains what each lane is classifying.

Terminal window
curl -sS "$HLIX_BASE_URL/v1/api/pending-requests?status=pending" \
-H "x-api-key: $HLIX_API_KEY" \
-H "X-Organization-Id: $HLIX_WORKSPACE_ID"

status, cycleId, and taskId are the supported query filters. A per-cycle read is also available at GET /v1/api/cycles/:id/pending-requests.

Each row carries requestType, blocking, requiredActor, title, description, context, status, deadline, and — once decided — decision, decidedBy, decidedAt, and resultingAction.

In the dashboard, Approvals lists every pending request across the workspace.

Eight types share one queue. Only the first one resumes a parked run.

Type Raised when Resolving it
deployment_approval A gated cycle is about to be dispatched Approve resumes dispatch; deny fails the cycle
tool_permission An agent asks to use a gated tool The decision is replayed onto the live agent session
plan_approval A plan wants sign-off Recorded only
code_review A change wants human review Recorded only
conflict_resolution A merge conflict needs a decision Recorded only
budget_exceeded A cost ceiling was hit Recorded only
requirement_clarification An agent needs an answer from you or the client Recorded only
cycle_proposal A cycle is proposed for adoption Recorded only

“Recorded only” is deliberate and honest: those types capture the decision and its audit trail, but do not themselves resume a workflow.

There is no separate approve or deny endpoint. Both are the status field on one call.

Terminal window
curl -sS -X POST "$HLIX_BASE_URL/v1/api/pending-requests/$REQUEST_ID/resolve" \
-H "x-api-key: $HLIX_API_KEY" \
-H "X-Organization-Id: $HLIX_WORKSPACE_ID" \
-H 'content-type: application/json' \
-d '{"status":"approved","decision":"Ship it — the migration is reversible."}'

status is approved or denied and is required; decision is optional free text up to 5000 characters. The response is the updated request row with status, decision, decidedBy, and decidedAt written. Every decision emits an event and writes a guaranteed audit row.

To withdraw a request instead of deciding it:

Terminal window
curl -sS -X POST "$HLIX_BASE_URL/v1/api/pending-requests/$REQUEST_ID/cancel" \
-H "x-api-key: $HLIX_API_KEY" \
-H "X-Organization-Id: $HLIX_WORKSPACE_ID"

Cancelling a deployment_approval un-parks the cycle as a deny. A cancelled gate does not leave a run waiting forever.

POST /v1/api/pending-requests/client-question opens a non-blocking requirement_clarification addressed to the client collaborator:

Terminal window
curl -sS -X POST "$HLIX_BASE_URL/v1/api/pending-requests/client-question" \
-H "x-api-key: $HLIX_API_KEY" \
-H "X-Organization-Id: $HLIX_WORKSPACE_ID" \
-H 'content-type: application/json' \
-d '{"cycleId":"'"$CYCLE_ID"'","title":"Invoice numbering","question":"Should invoice numbers restart each fiscal year?"}'

The body accepts exactly cycleId, title, and questionrequestType and requiredActor are set by the server and rejected if you send them. Only the agency can ask; the named client can answer.

  • {"error":"Request already approved"} — someone decided it first. Re-read the row before acting.
  • {"error":"Not found"} — wrong id, another workspace, or you are a client collaborator and the request was not addressed to you. Existence is deliberately not revealed.
  • The cycle stays parked after approval — confirm the request was deployment_approval and carried a cycleId. Only that combination resumes a run.
  • Approving does nothing for a plan_approval or code_review row — expected. Those types record the decision without resuming a workflow.
  • A run parked and no request appears — check that requiresApproval is set on the project you are actually running.
  • A cycle parked although requiresApproval is false — read the request’s context.lane. A forced lane gates on its own. See Triage.
  • You set the workspace default and existing projects did not change — by design; it applies to projects created after it. Set those projects individually.