Human-in-the-loop and checkpoints
A @checkpoint in ScaiCore pauses the invocation, persists its state, and waits for an external signal to resume. ScaiFlow exposes checkpoints in three flavors, all of which compile to the same underlying IR block kind.
The three checkpoint flavors on the canvas#
checkpoint (plain)#
Bare deterministic pause — no HITL routing, no automatic publish. Use this when you want to:
- Set a runtime breakpoint while developing (toggle
breakpoint: trueon the property panel; codegen prepends a syntheticdebug_breakpointcheckpoint before the actual block). - Hold execution until an external system explicitly resumes it via
POST /v1/scaicore/checkpoints/{id}/resolve.
Config: type (defaults approval), prompt, options, timeout, on_timeout.
hitl_decision#
Lightweight @checkpoint { type = "approval" }. No preceding queue publish. Use when the agent already routed to a HITL surface (a Slack message, an in-app modal) and the human responds back via a side channel.
Config: type, timeout, on_timeout, options.
hitl_review#
The full HITL pattern. Compiles to a single @checkpoint block carrying hitl_target: {scope, queue, hitl_spec}. ScaiCore's runtime:
- Detects
hitl_targeton the checkpoint. - Auto-publishes a review request to the named ScaiQueue queue.
- Suspends the invocation, persisting state to S3.
- Subscribes to
scaiqueue.message.completedevents for the published message. - On completion, deserializes the resolution and resumes the flow with the resolution available as a variable.
There is no correlation-id threading on your side, no bridge daemon, no webhook receiver. ScaiCore owns the publish + resolve cycle entirely.
Config:
scope— ScaiQueue scope. Can be omitted to inherit fromflow.config.scaiqueue.scope.queue— ScaiQueue queue slug. Required.sections— the form to render in the reviewer's UI. Three section types:context(read-only display),input(editable fields),decision(button choices). Edited via the canvas Form Builder.assignee— optional user/group hint.timeout,on_timeout— what to do if no human responds.decision_bind— variable name for the result; defaultsdecision.
The HITL form (sections)#
"sections": [
{ "type": "context", "title": "Customer", "content": "{{customer_id}}" },
{ "type": "input",
"field": "amount",
"kind": "currency",
"default": "{{amount}}",
"extras": { "confidence": "{{confidence}}" } },
{ "type": "decision",
"field": "decision",
"options": [
{ "id": "approve", "label": "Approve", "variant": "primary" },
"reject"
] }
]
contextsections render read-only content. Use them to show the data a human needs to make the decision.inputsections render editable form fields. Thekindcontrols the widget (text,currency,textarea, …).defaultcan be a template expression filled from flow variables.decisionsections render buttons. The chosen button'sidbecomes the value of the section'sfield.
The optional extras.confidence is a documented convention — ScaiQueue's renderer surfaces it as a confidence indicator next to the input. (ScaiQueue's typed HitlSpec doesn't formally type it; it passes through under the SDK's extras dict.)
Scope inheritance#
flow.config.scaiqueue.scope is the flow-level default. Any hitl_review, queue_publish, queue_consume, or queue_escalation node that omits scope in its own config inherits the flow's scope at compile time. This includes the hitl_target.scope written into the checkpoint block.
If a node doesn't specify a queue at compile time and no flow-level scope is set, deploy will fail with a meaningful error.
Runtime breakpoints#
Set node.config.breakpoint = true on any node to make the runtime pause every invocation just before that node. Codegen prepends a synthetic checkpoint_type: "debug_breakpoint" block with node_id: "{original_id}__breakpoint" so the canvas can correlate paused checkpoints back to canvas nodes.
The canvas's Live Runs panel has a <PendingBreakpointsBar> that shows currently-paused executions. Click "Continue" to resume; the underlying call is POST /v1/scaicore/checkpoints/{id}/resolve with {"resolution": "continue"}.
debug_breakpoint checkpoints and hitl_review checkpoints share the underlying IR primitive but render different UIs — the bar filters by checkpoint_type.
Resolving a checkpoint via the API#
Any pending checkpoint can be resolved via the ScaiFlow proxy at POST /v1/scaicore/checkpoints/{checkpoint_id}/resolve:
1 2 3 4 | |
The proxy requires scaicore:manage permission (resuming has side effects). It forwards to ScaiGrid's /v1/modules/scaicore/checkpoints/{id}/resolve.
For HITL Review checkpoints, set resolution to whatever the decision section's id was (e.g. "approve", "reject"). For debug breakpoints, use "continue".