Flow schema
ScaiFlow flows conform to JSON Schema draft 2020-12 at https://scailabs.com/schemas/scaiflow/v2.json.
The schema is hand-mirrored in three places (all kept in lockstep — CI fails if they drift):
- JSON Schema —
packages/flow-schema/schema/v2.json— the canonical reference. - Pydantic —
packages/flow-schema/python/scaiflow_schema/models.py— server-side validation. - TypeScript —
packages/flow-schema/typescript/src/index.ts— canvas + client typing.
Top-level shape#
{
"$schema": "https://scailabs.com/schemas/scaiflow/v2.json",
"id": "flow_<random>", // required, opaque string
"name": "Untitled Flow", // required
"version": "1.0.0", // required, semver
"scaicore_target": "0.2", // ScaiCore language version
"metadata": { /* see below */ },
"nodes": [ /* see Node */ ],
"edges": [ /* see Edge */ ],
"variables": [], // flow-level state slots
"config": { /* see FlowConfig */ },
"tests": [] // optional test fixtures
}
metadata#
{
"author": "alice@acme.example",
"created": "2026-04-01T...", // ISO-8601
"description": "What this flow does."
}
All fields optional.
Node#
{
"id": "node_<random>", // required
"type": "llm_flexible", // required, NodeKind enum
"label": "Classify Intent", // required
"position": { "x": 400, "y": 200 }, // required
"config": {}, // per-kind shape; see Node kinds
"inputs": [ /* Port[] */ ],
"outputs": [ /* Port[] */ ]
}
NodeKind enum (closed)#
1 2 3 4 5 6 7 8 9 10 | |
See Node kinds reference for the per-kind config shape.
Port#
{ "id": "in_message",
"label": "message",
"type": "string" }
type is free-form ScaiCore type syntax: string, int, float, bool, object, enum, or a user-declared type name.
Edge#
{
"id": "edge_<random>", // required
"type": "sequential", // required, EdgeKind enum
"source": { "node": "node_a", "port": "out" }, // required
"target": { "node": "node_b", "port": "in" }, // required
"condition": "confidence > 0.7" // required when type === "conditional"
}
EdgeKind enum: sequential, conditional, data.
Variable#
{ "name": "intent",
"type": "string",
"default": null }
name and type required. Flow-level state slots — currently advisory (the compiler emits them; runtime enforcement TBD).
FlowConfig#
{
"core_identity": { /* required */
"name": "My Core",
"description": "Optional",
"models": [ /* ModelDecl[] — minItems: 1 */ ]
},
"plugins": [], // string[]; plugin names this flow uses
"capabilities": [], // string[] OR object[]; capability flags + structured bunker block
"bunker": { /* optional BunkerCapability */ },
"scaiqueue": { /* optional ScaiQueueConfig */ },
"hitl_mode": "explicit_publish", // legacy, deprecated; M3 uses single @checkpoint with hitl_target
"publish_as_model": false,
"model_visibility_group_ids": []
}
ModelDecl#
{
"role": "primary", // ^[a-z][a-z0-9_]*$, unique within models[]
"ref": "scaigrid", // provider key
"model": "openai/gpt-4o", // model identifier
"temperature": 0.5, // 0.0–2.0, optional
"modalities": ["text", "structured_output"], // default ["text"]
"system_context": "...", // optional
"fallback": [ // optional
{ "ref": "openai", "model": "gpt-3.5-turbo" }
]
}
First entry is the implicit primary default for any LLM node that doesn't specify llm_role.
ScaiQueueConfig#
{
"scope": "support", // required, slug
"queues": [
{
"slug": "review", // required
"display_name": "Review queue",
"description": "Where reviews go",
"consumer_mode": "fifo", // fifo | priority | deadline | broadcast
"max_retries": 5, // 0–100
"purpose": "review" // intake | processing | review | escalation | completed | dead-letter | custom
}
]
}
Queue/HITL nodes that omit scope inherit from flow.config.scaiqueue.scope at compile time.
BunkerCapability#
{
"lifecycle": "ephemeral", // ephemeral | session | persistent | appliance
"image": "python-3.12", // required
"cpu_millicores": 1000, // 100–64000, default 1000
"memory_mb": 1024, // 128–131072, default 1024
"disk_mb": 2048, // 512–524288, default 2048
"gpu_count": 0, // 0–8
"gpu_type": "...", // optional
"network_profile": "registry", // isolated | registry | allowlisted | unrestricted | transit
"network_allowlist": ["*.example.com"] // used when network_profile=allowlisted
}
TestCase#
{
"name": "Refund request", // required
"description": "Optional",
"input": { "message": "I want a refund" }, // required
"expect": { "intent": "complaint" }, // optional shallow == match
"expect_expressions": [] // optional free-form assertions
}
Strictness#
additionalProperties: falseat most levels — unknown keys are rejected on save.$schemaat the top level is allowed (editor pointer convention).- Pydantic + JSON Schema + TypeScript are kept in lockstep by
packages/flow-schema/python/tests/test_models.py— drift fails CI.
Validation#
1 | |
Or via the REST API: POST /v1/flows/validate — see Preview router.