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

Blocks

A @flow is a sequence of blocks. Each block is one execution unit; the runtime dispatches on a kind discriminator and the executor knows how to drive each kind. There are fourteen of them, but you'll write @rigid and @flexible for most everything. The other twelve cover control flow, external calls, and lifecycle concerns. The single most useful distinction to internalize is rigid vs flexible: rigid is deterministic and replayable, flexible is LLM-driven and not.

Computation blocks#

These produce a value (or update memory) without changing control flow.

@rigid — a deterministic sequence of statements. Variable assignments, memory reads and writes, plugin calls, conditionals, simple math. The verifier rejects an LLM call inside @rigid (error E405) and rejects non-deterministic operations (E400) because rigid blocks must be replayable from a serialized scope snapshot during checkpoint resume.

@flexible — an LLM call with a goal, optional inputs, an output type, and per-block constraints. The compiler turns the output: declaration into a structured-output schema; the runtime hands it to the model provider. The block's "result" is whatever the model returns, type-checked against the schema.

scaicore
1
2
3
4
5
result = @flexible {
    goal = "Summarize the document in three bullet points"
    input = { text: doc.body }
    output: Summary
}

@guarded@flexible plus a list of post-conditions that must hold on the model's output. If any check fails, an optional on_validation_failure body runs; otherwise the runtime raises GuardViolationError. Use this when the model's structured output isn't enough — when you also need invariants like "all citations resolve" or "no claim is unsupported".

@model_call — non-text model invocations: TTS, STT, embeddings, image generation, audio generation. Same shape as @flexible (input bindings, output type) but a modality selects the call type instead of producing text.

Control flow#

These compose other blocks. Each takes a body or a list of branches.

@parallel — execute branches concurrently. Optional max_concurrent and fail_fast. Results are returned as a list in branch order. A @checkpoint inside a @parallel branch is rejected by the verifier (E403) — suspend/resume across parallel branches isn't supported.

@foreach — iterate a collection, run the body for each element. Optionally yield expressions to build a result list.

@match — pattern matching with arms. Patterns can be literals, enum symbols, type checks, bindings, or wildcards. Guards on arms are supported.

@while — bounded loop. The max_iterations is mandatory; the runtime raises WhileExceededError if you hit it without breaking.

@try_catch — error handling. try_body runs first; on a ScaiCoreRuntimeError, catch clauses are tried in order against the error's class name (or *).

External calls#

These cross a boundary — to another Core, or to async results gathered separately.

@core_call — invoke another Core via the host's CoreDispatcher. Sync by default; pass is_async = true to fire-and-forget (returning an AsyncFlowRef). Pair async calls with @await_responses to collect their results.

@await_responses — wait on a set of AsyncFlowRef values. A strategy (all, any, at_least(n), majority) controls when the await is satisfied. Timeouts and partial completion are first-class.

Lifecycle#

These don't compute a value — they shape how execution proceeds.

@checkpoint — pause execution and surface a request to a human (or a queue). When the host returns a resolution, the flow resumes with the response bound into scope. Supports presentation, options, on_response match arms, and (since v1.0.0) a hitl_target for native routing into ScaiQueue.

@budget — wraps a body in resource limits: max duration, LLM calls, plugin calls, memory writes, retries. On excess, either fail or warn.

@debug — a body of statements that compile away unless you pass --debug to scaicore compile. Use for inline logging during development.

When to pick which#

A short heuristic for the three you'll reach for most:

  • Mostly deterministic work? @rigid. Memory reads, plugin calls, arithmetic, branching, building output structures.
  • LLM call with a structured output? @flexible. It enforces the shape and routes through your @llm config.
  • LLM call where the output must satisfy hard rules? @guarded. Cheaper than re-prompting, more reliable than hoping.

Reach for control-flow blocks when sequencing doesn't capture what you mean. Reach for lifecycle blocks when you need to bound, suspend, or instrument execution — not as a first move.

Updated 2026-05-18 01:26:17 View source (.md) rev 4