---
audience: engineers
summary: "The fourteen block kinds in ScaiCore, grouped by purpose \u2014 computation,\
  \ control flow, external calls, and lifecycle."
title: Blocks
path: concepts/blocks
status: published
---

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