# Review & QA gates

hlix has **two** gates, and they are not the same thing. Automated QA judges work after it exists. An approval gate stops work before it starts. Every cycle passes the first; the second is a project policy you opt into — plus a set of risk lanes that demand it whether you opted in or not.

## When this matters to you

- You want to know what happens between an agent finishing and you seeing the result.
- A cycle failed and you want to know whether it will retry on its own.
- You need a person in the loop before agents touch a production-adjacent project.
- A cycle parked for approval on a project where the gate is switched off.

## Gate one: automated QA

Work moves from build through automated QA to an evaluator verdict; a retryable failure loops back to build carrying the evaluator’s feedback, and the human approval gate — off by default — sits between approval and deploy.

Every cycle runs it. No configuration, no opt-out.

```text
intake ──▶ planner ──▶ execute ──▶ QA evaluator
                          ▲             │
                          │             ├── approved ──▶ deploy
                          │             │
                          └── optimize ─┤   (retryable)
                                        │
                                        └── terminal ──▶ fail
```

The evaluator judges the completed tasks against the plan and the quality criteria and returns a verdict. A **retryable** problem loops through the optimizer and back into evaluation — the task does not just fail, it gets another pass with the failure as input. A **terminal** problem fails the cycle with a recorded reason rather than looping forever.

The evaluator's verdict is visible on the task's evidence document, alongside the diff it judged:

```json
{"evaluator": {"verdict": "approved", "summary": "…", "headSha": "9f2c1b0…", "stale": false}}
```

`stale: true` means the branch moved after the evaluator looked. The verdict is still shown — it is just no longer about the current commit. See [Reviewing output](/running/review/).

## Gate two: human approval

Off by default. Setting `requiresApproval` on a project makes the orchestrator open a blocking `deployment_approval` request **before** launching a gated cycle, then park the run until someone decides. Approving resumes dispatch; denying fails that cycle.
**What approval actually controls:** Approval governs **dispatch and deployment**. It is not a claim that no task branch was ever created, or that a repo-less project's internal working history has not advanced. Read it as "no agents are launched on this cycle until a person says so", which is what it is.

With the flag off, an autonomous run is unchanged — the gate adds nothing to a project that has not asked for it. [Approvals](/running/approvals/) covers the request queue and how to resolve one.

## Lanes that demand a human

Some work is risky enough that a person signs off first, no matter what the project asked for.

Every cycle is classified at [Triage](/concepts/triage/) into a **lane**, derived from how well understood the change is and how costly a wrong answer would be. Four of those lanes open the same blocking approval request the project flag would, on their own:

| Lane | The classification behind it |
| --- | --- |
| `stop` | Simple but high-risk — a human should plan this before code starts |
| `design-review` | Ambiguous and high-risk — a senior owner should look at it first |
| `architecture-review` | Ambiguous and medium-risk — the shape should be agreed before code |
| `manual-lane` | Systemic and high-risk — there is no automated lane for this |

The lane check runs per cycle, and it ORs with the project setting: whichever says "gate" wins. So a project with approvals off still parks a `manual-lane` cycle, and a project with approvals on gates everything regardless of lane. The request that opens carries the lane in its `context`, so the queue tells you *why* this particular cycle stopped.

The other five lanes — `auto`, `fast-review`, `plan-gate`, `experiment`, `split-work` — do not gate. They are recorded classification you can read, filter, and audit on; `plan-gate` in particular is already satisfied by the planning pass every cycle makes before it executes.
**A missing lane never gates by itself:** A cycle with no lane — one created before it was triaged, or a lane that could not be read — is treated as not-forced. The project's own flag stays the conservative backstop: if *that* cannot be read, the gate is applied. An unknown operator toggle is treated as "on"; an unknown classification is not.

## Sending work back yourself

Neither gate replaces your judgement. When you read a diff and disagree with it, `request-revision` folds your unresolved line comments into the task's feedback and re-enters the cycle, so the worker gets your notes as instructions rather than as a rejection. That is the third loop, and it is the one you drive.

## What gets recorded

Three planes carry different promises, and the difference is the point:

| Plane | Answers | Delivery |
| --- | --- | --- |
| Audit log | Who did what, to what, when, from where, with what outcome | Guaranteed and immutable |
| Events | What is happening right now | Best-effort |
| Agent runs and logs | Why a run cost or failed what it did | Best-effort |

The audit log is the one that carries a promise. It is append-only at the database level, partitioned by month, and chained per organization with a row hash, so a tampered or missing row is detectable rather than merely unlikely. `GET /v1/api/audit-log/verify` checks that chain. Where a mutation is compliance-critical, the state change and its audit row commit in one database transaction — the record cannot exist without the change, or the change without the record.

Reading the audit log requires the workspace `owner` or `admin` role.
**The honest boundary:** This record supports review and incident investigation. It is not a claim of guarantees the data plane does not implement — the events feed and the run logs are explicitly best-effort, and a gap there is a gap, not evidence of anything.

## Where to go next

| If you want to… | Read |
| --- | --- |
| Turn the approval gate on | [Approvals](/running/approvals/) |
| See how a cycle gets its lane | [Triage](/concepts/triage/) |
| Read the diff a gate judged | [Reviewing output](/running/review/) |
| See what a cycle run does end to end | [Cycles](/running/cycles/) |

## Next steps

[Approvals](/running/approvals/)
  [Triage](/concepts/triage/)
  [Reviewing output](/running/review/)
  [Import trust model](/security/trust-model/)