Gates and review
Steps do the work; gates decide whether it was good enough. Most gates are automatic and invisible — you only meet them when one rejects something. A few are human, and those are why a run can end in review rather than completed.
Steps do, gates judge
A gate is not a step. It runs no model and produces no output of its own. It inspects what the previous step produced and declares which step id it unlocks — that is the whole mechanism.
{
"id": "gate-text",
"name": "OCR text gate",
"type": "quality",
"inputs": ["audited"],
"outputs": ["lettering-checked"],
"unlocks": ["step-4"]
}| Gate type | Who decides | Effect |
|---|---|---|
quality | The engine, automatically | Passes silently, or fails the step and triggers escalation. |
approval | A person | Parks the run in review until someone decides. |
hitl | A person | Same as approval; the two names are interchangeable in practice. |
approval or hitl gate, its runs go straight from running to completed. Most public workflows are in that category — they are gated on quality, not on a human.When an automatic gate rejects
A failed quality gate does not necessarily fail the run. The engine has an escalation ladder: it rewinds the cursor, rebinds the role that produced the bad output to a different tool, and re-runs that segment. Only when the ladder is exhausted does the run fail.
escalatedToRung and escalatedToTool. Work already paid for is never re-bought.When it does fail, you get the gate’s actual reasoning rather than a code. This is a real failure from a text gate catching lettering the image model invented:
{
"status": "failed",
"error": "step 'step-4': OCR text gate rejected the render — verdict: {
\"foundText\": \"BRIEF, STRATEGY, CREATIVE\",
\"pass\": false,
\"reason\": \"The image contains multiple legible text elements, which
violates the 'none' text policy requiring zero readable text.\"
}"
}The QA record
Whatever happens, the checks that ran are recorded on the artifact. Each carries a status of pass, fail, or pending.
"qa": {
"status": "pending_review",
"checks": [
{ "id": "text-gate", "label": "Text gate", "status": "pass", "detail": "" }
]
}qa.checks, nothing verified that property — which is different from it having been verified and passed. When you care, look for the check rather than the absence of a failure.The review queue
Runs sitting in review make up the queue. It is a view, not a separate table — which is why there is nothing to keep in sync.
| Endpoint | Does |
|---|---|
GET /v1/queue | Everything waiting on a person. |
GET /v1/queue/count | Just the number, for a badge. |
POST /v1/queue/{run_id}/decision | Decide one run. |
POST /v1/queue/decisions | Decide many at once. |
curl -X POST https://api.esy.com/v1/queue/run-a1b2c3d4/decision \
-H "Authorization: Bearer $ESY_API_KEY" \
-H "content-type: application/json" \
-d '{ "decision": "approve", "note": "Colours match the pack." }'decision is approve, reject, or request_changes, and each maps to the matching terminal run status. Every decision is written to a durable ledger with who decided, when, and any note — so review is auditable rather than a state flip.
What approval will refuse
Two rules protect the queue from rubber-stamping:
- You cannot approve over a failed check. If any entry in
qa.checksisfail, approval is rejected. Fix the work or re-run it. - You cannot approve while a check is still pending. A verification that has not finished is not a verification.
Typed holds
Sometimes a run is held not because the work is wrong but because something is missing — most often a classification the model could not confidently assign. That is a typed hold, and it declares exactly which fields a reviewer must supply.
curl -X POST https://api.esy.com/v1/queue/run-a1b2c3d4/decision \
-H "Authorization: Bearer $ESY_API_KEY" \
-H "content-type: application/json" \
-d '{
"decision": "approve",
"patch": { "category": "woodland-animals" }
}'The patch must match the hold exactly — no more fields, no fewer. Supplying extra keys is refused, and so is the placeholder "uncategorized": a hold exists precisely because that answer is not acceptable.
- Gates judge and unlock; they never execute work. Only
approval/hitlgates involve a person. - A failed quality gate first tries to recover by rewinding and rebinding, and records every escalation.
- Approval is refused over a failed or pending check, and a typed hold must be patched exactly.
