Node kinds
ScaiFlow has 24 node kinds across 9 categories. Each kind maps to a specific ScaiCore construct — the compiler refuses to invent anything that doesn't exist upstream.
This page is conceptual; the node-kinds reference has the per-kind config shape, ports, and IR mapping.
Categories (visual color cue on the canvas)#
| Category | Color | Kinds |
|---|---|---|
| Entry | Green | entry_api, entry_webhook, entry_schedule |
| LLM | Blue | llm_rigid, llm_guarded, llm_flexible |
| Logic | Orange | logic_decision, logic_loop, logic_parallel |
| Tool | Purple | tool_plugin, tool_http |
| Data | Teal | data_variable_set |
| Checkpoint | Yellow | checkpoint |
| Queue / HITL | Amber | queue_publish, queue_consume, hitl_review, hitl_decision, queue_escalation |
| Compute | Indigo | compute_provision, compute_exec, compute_file_upload, compute_file_download, compute_destroy |
| Sub-flow | Grey | subflow_call |
Entry points#
How an invocation starts. Each entry kind compiles to a top-level IRTrigger declaration on IRModule.triggers — NOT to a block in the flow body. The closed set of trigger kinds is {api, webhook, schedule}.
There is no entry_chat — chat reachability is a publish-time concern, handled by flow.config.publish_as_model (see Publish as model).
entry_api— HTTP-style invocation. The deployed Core exposes it atPOST /v1/modules/scaicore/cores/{id}/invoke. Config:method,path.entry_webhook— same wire shape but typically called from an external system. Config:method,path,auth.entry_schedule— cron-driven trigger. Config:cron(5-field),timezone.
LLM blocks#
The three-tier rigidity spectrum — see Rigidity.
llm_rigid— deterministic template, no model call.llm_guarded— bounded LLM call withguard(pre) andvalidate(post) expressions.llm_flexible— goal-driven LLM call with an optional output schema.
All three pick a model from the flow's registry via llm_role.
Logic#
logic_decision— branches to one of N targets based on per-edge conditions. Compiles to a ScaiCorematchblock; the IR closed set has no separateifkind. Style isif(one or two branches) orswitch(multi-branch on a shared subject); both compile identically —switchis the convention where every branch condition references the same subject.logic_loop—for(iterate a collection) orwhile(loop until condition false;max_iterationsis mandatory).logic_parallel— fan-out branches;max_concurrent+fail_fastcontrols.
Tool calls#
tool_plugin— invoke a registered ScaiGrid plugin. Config:plugin,method,args, optionalresult_binding. Compiles to aplugin_callstatement (NOT a block — sits inside the parentrigidblock).tool_http— placeholder for direct HTTP. ScaiCore forbids direct HTTP from Core code; this compiles to aplugin_callagainst anhttpplugin name (the canonical name isn't finalized in ScaiCore yet — see Troubleshooting).
Data#
data_variable_set— bind an expression to a variable name. Compiles tokind: "assign"per the IR doc.
Checkpoint#
checkpoint— pause execution. Useful as a standalone deterministic gate or as the underlying block kind for HITL Review.
Queue / HITL#
ScaiQueue is the asynchronous message bus + human-review surface. See Queues and HITL.
queue_publish—scaiqueue.publish(scope, queue, message_type, payload).queue_consume—scaiqueue.consume(scope, queue, consumer_id).hitl_review— single@checkpointwithhitl_target: {scope, queue, hitl_spec}. Runtime auto-publishes the review request and auto-resolves on completion.hitl_decision— lightweight@checkpointof type"approval", no preceding queue publish. For cases where the agent already routed to a HITL surface.queue_escalation—scaiqueue.escalate(...).
Compute (ScaiBunker)#
Sandboxed execution environments. See Sandboxes.
compute_provision—scaibunker.create(image, lifecycle). Returns a bunker handle.compute_exec—scaibunker.exec(command, timeout_s).compute_file_upload—scaibunker.write_file(path, content).compute_file_download—scaibunker.read_file(path).compute_destroy—scaibunker.destroy().
A flow with any compute_* node auto-adds scaibunker to its plugins list and lifts a default bunker capability onto the manifest if the flow's config.bunker is unset.
Sub-flow#
subflow_call— invoke another Core. Config:target(acore://{tenant_slug}/{slug}URI),input_bindings,is_async. Compiles toIRCoreCallBlock(kind: "core_call").
Adding a node kind#
Extending the kind set isn't a casual change. You'd need to:
- Add it to the JSON Schema's
NodeKindenum (packages/flow-schema/schema/v2.json). - Add it to the Pydantic + TS enums.
- Add a category + label + color entry.
- Add a
_emit_*function inapps/compiler/scaiflow_compiler/codegen.pyand register it in_DISPATCH. - Add a default-config entry in
apps/canvas/src/canvas/nodeDefaults.ts.
Skipping any one of these will compile but break at runtime.