NOUKAI

Block Runtime Contract

How each block type executes at runtime — the authoritative reference for LLM authors building flows via MCP tools.

Block Types

llm

Sends a prompt to a language model. The prompt supports template interpolation.

  • Input: pipeline_input dict from the previous block's output (or flow global input for the first block).
  • Template variables: {{fieldName}} references resolve to keys in pipeline_input. {{initial.fieldName}} references the flow's global input.
  • Output: A dict whose keys match the block's output_schema field names.

code

Executes a sandboxed Python function body for data transformation.

  • Language: Python only. No other languages are supported.
  • What you write: The function body only — the runtime wraps your code in a function definition. Do NOT write def block(...) or def _block(...) yourself.
  • Generated wrapper:
    def _block(initial, <input_schema_field_1>, <input_schema_field_2>, ...):
        <your code here>
  • Parameters:
    • initial — always present. Contains the flow's global input dict.
    • One positional parameter per field in the block's input_schema, in array order.
  • Return contract: Must return a dict. The keys should match the block's output_schema field names.
  • Call site: _block(initial=<flow_global_input>, **pipeline_input) where pipeline_input is the previous block's output dict.

Sandbox Rules

Allowed builtins: abs, bool, dict, enumerate, float, int, isinstance, len, list, max, min, range, reversed, round, set, sorted, str, sum, tuple, zip, True, False, None

Forbidden:

  • No import statements
  • No eval() or exec()
  • No file I/O (open, os, pathlib, etc.)
  • No getattr, type, vars, dir (object-model traversal)
  • No network access

Example

Given input_schema fields: [first_name, last_name, age] and output_schema fields: [full_name, is_adult]:

# This is what you write (body only):
full = first_name + " " + last_name
return {"full_name": full, "is_adult": age >= 18}

The runtime generates and executes:

def _block(initial, first_name, last_name, age):
    full = first_name + " " + last_name
    return {"full_name": full, "is_adult": age >= 18}

passthrough

Forwards input to output unchanged. Useful as a pipeline junction or for documentation.

  • Input: pipeline_input dict from the previous block.
  • Output: Same dict, forwarded unchanged.

Data Flow Between Blocks

Sequential (h-container)

Blocks in an h (horizontal/sequential) container execute left-to-right. Each block receives the previous block's output dict as its pipeline_input.

Block A → Block B → Block C
         ↑ A's output     ↑ B's output

The first block in a sequence receives either:

  • The flow's global input (if it's the root-level first block), or
  • The output from the previous sibling of its parent container.

Parallel (v-container)

Blocks in a v (vertical/parallel) container execute concurrently. All children receive the same pipeline_input (from the block/container preceding the v-container).

When all children complete, their outputs are merged into a single dict:

  • For block children: {<step_id>: <block_output>}
  • For nested container children: {<index_as_string>: <container_output>}

The downstream block receives this merged dict as its pipeline_input.

Loop

A loop node iterates over an array field in its input. The arrayField property names which key contains the array.

Per-iteration input: Each child receives {...sibling_fields, [arrayField]: single_item} where:

  • sibling_fields = all keys from the input except the array field
  • single_item = one element from the array

Example: If the loop's input is {items: ["a", "b", "c"], lang: "en"} and arrayField = "items", each iteration's child gets:

  • Iteration 0: {items: "a", lang: "en"}
  • Iteration 1: {items: "b", lang: "en"}
  • Iteration 2: {items: "c", lang: "en"}

Loop output: A list (array) of per-iteration results, in order. The downstream block receives this list.

Global Input (initial)

The flow's global input is available to every block:

  • LLM blocks: via {{initial.fieldName}} template syntax
  • Code blocks: via the initial parameter (always the first parameter)

Schema Shape

Both input_schema and output_schema follow the SchemaNode structure:

{
  "includeInPrompt": true,
  "fields": [
    {
      "id": "uuid",
      "name": "fieldName",
      "description": "What this field contains",
      "type": "string",
      "required": true,
      "defaultValue": "",
      "enumValues": [],
      "objectRef": null,
      "arrayItemType": "",
      "arrayItemRef": null,
      "examples": []
    }
  ]
}

The fields[].name values determine:

  • Code block parameter names (in array order)
  • LLM template variable names ({{name}})
  • Expected output dict keys

On this page