Platform
ScaiWave ScaiGrid ScaiCore ScaiBot ScaiDrive ScaiKey Models Tools & Services
Solutions
Organisations Developers Internet Service Providers Managed Service Providers AI-in-a-Box
Resources
Support Documentation Blog Downloads
Company
About Research Careers Investment Opportunities Contact
Log in

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: true on the property panel; codegen prepends a synthetic debug_breakpoint checkpoint 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:

  1. Detects hitl_target on the checkpoint.
  2. Auto-publishes a review request to the named ScaiQueue queue.
  3. Suspends the invocation, persisting state to S3.
  4. Subscribes to scaiqueue.message.completed events for the published message.
  5. 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 from flow.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; defaults decision.

The HITL form (sections)#

jsonc
"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"
    ] }
]
  • context sections render read-only content. Use them to show the data a human needs to make the decision.
  • input sections render editable form fields. The kind controls the widget (text, currency, textarea, …). default can be a template expression filled from flow variables.
  • decision sections render buttons. The chosen button's id becomes the value of the section's field.

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:

bash
1
2
3
4
curl -X POST "https://scaiflow.example/api/v1/scaicore/checkpoints/${CHECKPOINT_ID}/resolve" \
  -H "Authorization: Bearer ${TOKEN}" \
  -H "Content-Type: application/json" \
  -d '{"resolution": "approve", "comment": "Within team budget."}'

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".

Updated 2026-05-18 16:05:18 View source (.md) rev 3