Blueprint Engine Overview

The blueprint engine is the part of the RUAL core that loads a blueprint, compiles it once per node into an in-memory cache and runs it: flow pins decide which block runs next, and data pins are computed just before a block needs them. The pages in this section explain each step with a real example and its real output.

At runtime a blueprint is a set of action documents

Every block on the canvas is stored as one action: a JSON document with a type, its props, and two pin lists, in_pins and out_pins. A wire between two blocks is a pin entry {action_guid, in_id, out_id, type}, and every wire is stored on both ends: the producer lists it in its out_pins, the consumer in its in_pins.

  • Flow pins (type flow) decide when a block runs. See How a Flow Executes.
  • Data pins (every other type: value, number, condition, object, httpconnection and so on) decide what a block receives. A data in-pin has one producer and is resolved only when a block that needs it is about to run.
  • Positions are canvas coordinates stored as [Y, X]: the first number is the top edge, the second the left edge. The engine never reads them.

This is a real seven-action blueprint from the platform's test fixtures: two custom functions that each return whether the current date falls in this year or this week. Three of its actions, trimmed (guids shortened, editor geometry and timestamps left out). The condition block's out_pins entry and the return block's second in_pins entry are the same wire, seen from each end.

Stored actions (trimmed)
[
  {
    "_meta": {"guid": "a167158e…"},
    "type": "trigger_custom_function",
    "event": "condition_date_equals_this_year",
    "position": [51551.01, 50613.01],
    "function_return_pins": [
      {"id": "true", "state": "true", "type": "condition", "optional": false, "required": true, "description": null}
    ],
    "in_pins": [],
    "out_pins": [
      {"out_id": "flow", "action_guid": "8fa17fcf…", "in_id": "flow", "type": "flow"}
    ]
  },
  {
    "_meta": {"guid": "a88cd43c…"},
    "type": "condition_date_equals_this_year",
    "position": [51775.01, 51216.01],
    "in_pins": [
      {"in_id": "date_today", "action_guid": "5e1a0770…", "out_id": "date", "type": "date"}
    ],
    "out_pins": [
      {"out_id": "condition", "action_guid": "8fa17fcf…", "in_id": "true", "type": "condition"}
    ]
  },
  {
    "_meta": {"guid": "8fa17fcf…"},
    "type": "function_return",
    "position": [51594.01, 52007.01],
    "custom_pins": [
      {"id": "true", "state": "true", "type": "condition", "optional": false, "required": true, "description": null}
    ],
    "in_pins": [
      {"in_id": "flow", "action_guid": "a167158e…", "out_id": "flow", "type": "flow"},
      {"in_id": "true", "action_guid": "a88cd43c…", "out_id": "condition", "type": "condition"}
    ],
    "out_pins": []
  }
]

Exported with the bpl command line, the same seven actions become 312 characters of Blueprint Language instead of 5,933 characters of JSON. The date block is shared by both functions, so it becomes a top-level let; the return pin is called true, a reserved word, so it is written in backticks.

Export
$ bpl export internal/blueprint/testdata/bp/20b5dd47….json -o dates.blueprint
7 actions, 6 wires: 312 source chars / 5933 JSON chars = 5.3%, round-trips exactly
dates.blueprint
blueprint ""

let currentdate = now()

fn condition_date_equals_this_year() -> (`true`: condition) {
  function_return(`true`: condition_date_equals_this_year(currentdate))
}

fn condition_date_equals_this_week() -> (`true`: condition) {
  function_return(`true`: condition_date_equals_this_week(currentdate))
}

From a save to a run in five steps

A blueprint is saved in the Studio, compiled on each node the first time it is needed, kept in that node's memory, and run from there until the next save invalidates it.

Step What happens Where in rual-core
1. Save The Studio writes the changed action documents one at a time. Each write asks for an invalidation of the blueprint's compile; the first write arms one waiter and the rest of the save is covered by it. internal/stores/blueprint_invalidation.go
2. Invalidate The waiter sleeps 300 ms so the rest of the save can arrive, waits until the entity's writes are searchable, and fires once: on the writing node directly, and on the Redis channel blueprintactions-reset-precompiled-{database} with the payload {entity}:{blueprint guid}, which every node subscribes to. Each node marks its compile stale; nothing is deleted. internal/blueprint/engine.go, InvalidateBlueprintCache
3. Compile The next run on a node reloads the blueprint's actions, sorts each action's pins and compiles every action once into a compiledAction: its type, its dispatch prefix, and its in-pins as a typed list indexed by pin id, each with the value key of the producer it reads worked out in advance. cachedBlueprintActions, compileActionMap
4. Cache The actions and their compiled views are kept in the node's blueprintCache under {entity}:{blueprint guid}, with no expiry. When a reload fails, the previous compile keeps serving and the failure is logged. cachedBlueprint, staleOrError
5. Run A starter block begins a run. The engine follows flow pins one block at a time, depth first, and computes each block's data inputs just before it runs. RunFlow, followTruethyFlow, fillValues

Runs never see a half-saved blueprint

Because an invalidation only marks a compile stale and the waiter fires after the whole save has landed, runs move from the complete old version to the complete new version. A save of twenty blocks does not produce twenty recompiles of a blueprint that is partly saved, and a reload that fails leaves the old version running instead of no version at all.

A cold run loads what it reaches first

  1. A run starts from one action and loads the blocks it reaches one at a time, each through the shared action cache (memory, then Redis, then the search index).
  2. After four such misses in one run, it loads the whole blueprint at once: it waits until the entity's recent action writes are searchable, lists the blueprint's action guids, and re-reads every action so a save that just happened is visible.
  3. If that list does not contain the action the run started from, the list is provably incomplete, and the engine refuses to cache it instead of caching a smaller blueprint.

A cache miss is counted: the Precompiled cache miss chart on the cluster's usage page shows how often nodes had to load and compile a blueprint.

What each page covers

How Blocks Are Registered The catalog entry that describes a block, what the catalog API returns for it, and how the executor picks the code that runs it. How a Flow Executes Flow pins in order, depth first; data pins pulled on demand; starters, routing blocks, concurrency and what a simulation answers. Functions and Custom Pins Custom functions, calls across blueprints and custom pins, from source to the stored JSON. Blueprint Language The text form of a blueprint: syntax, a real export, the lock, checks and positions. Editing Blueprints with an AI Agent Export, edit, dry run, stage and save, with the request and response of every step. The Studio Code View The Canvas and Code switch, Check, Stage to canvas, Place unpositioned and Positions. Engine Performance What keeps a run fast, and what a measured investigation of the engine found.

Frequently asked

What does the RUAL blueprint engine do?

It is the part of the RUAL core that runs blueprints. Each node compiles a blueprint once into an in-memory cache, and every run starts at a starter block, follows flow pins one block at a time and computes data pins just before a block needs them. A save invalidates the compile on every node, and the next run compiles it again.

What happens when I save a blueprint in RUAL?

The Studio writes the changed blocks, and the core collects those writes for 300 milliseconds, waits until they are searchable and then sends one invalidation to every node over Redis. Each node marks its compiled copy stale and recompiles on the next run, so runs switch from the complete old version to the complete new version and never see a half-saved blueprint.

Does the RUAL engine cache blueprint results between runs?

No. The engine caches a blueprint's compiled structure per node, not the values blocks produce. Every run computes its own values, and within one run a data block is computed at most once however many blocks read it.