Scriptum Language
Scriptum is a purpose-built domain-specific language for orchestrating AI
workflows. A Scriptum program lives in a .scriptum file and reads top to
bottom like a recipe: every step starts with a verb (do, each, decide,
together, ask, agent, emit), and indentation β not braces β defines
structure.
The design goal is that a non-developer can read a script and understand the flow, while the compiler still has the full type graph of the program before a single step runs. Inference (LLM chat, embeddings, reranking, transcription, classification) is handled by Ignite, and structured data lands in K3 through native tools. You write the recipe; Scriptum runs it in the cloud as a durable, resumable thread.
If you are new to the platform, read Core Concepts first for the script / draft / version / thread vocabulary, and the API Reference for the gRPC surface that creates and watches threads. This section is the deep dive into the language itself.
The pipeline: .scriptum β compile β IR β thread
A script never executes as raw text. It goes through a fixed pipeline:
1. Parse .scriptum source β AST
2. Resolve tools section β ToolCatalog (native + Ignite)
3. Validate types, bindings, β typed, checked program
field access, contract
4. Codegen each construct β YAML IR (one node per step)
5. Execute IR runs in the cloud β Thread (durable, resumable)Steps 1β4 are the compiler (scriptum-compiler). Because every tool
declares its input and output schema, the compiler knows the type of every
binding before anything runs β typos, missing inputs, and bad field access are
caught at compile time, not in production. Step 5 is the runtime: the compiled
IR is scheduled as a thread that persists its state after every step, so it can
pause for human input and resume exactly where it left off.
Each language construct maps to a single IR primitive. You will see these names in the YAML IR and in step records:
| You write | IR primitive |
|---|---|
do "X" with tool | execute |
each x in list | iterate |
decide "Q" | decide (or branch for rules) |
check "C" | evaluate |
together | parallel |
pipe x from src | pipe |
repeat | repeat |
plan "X" with llm | plan |
run script | run |
wait | wait |
ask "X" | ask |
agent "X" with llm | agent |
let name = expr | let |
emit | emit |
yield | yield |
send "X" via "ch" | send |
A complete annotated example
Here is a small, end-to-end script β input block, tools, two action steps, and
an output β walked through line by line. It probes a URL, checks that it is
reachable, and emits the result. (Adapted from examples/url_probe.scriptum.)
script "URL Probe"
version "0.1.0"
input
url: text -- URL to probe
output
status: number
content_type: text
result: object
import tools
get_url_content_type from native
fetch_url from native
do "Probe URL" with get_url_content_type
url = url
-> probe
check "Is reachable?" probe.status < 400
on pass: continue
on fail: fail "URL returned error status"
do "Fetch content" with fetch_url
url = url
-> fetched
emit "Result"
status = probe.status
content_type = probe.content_type ?? "unknown"
result = fetchedLine by line:
script "URL Probe"β every file starts with thescriptkeyword and a human-readable name. The name is the primary identifier (it shows up in the thread UI), not a comment.version "0.1.0"β an optional semantic version for the script.inputblock β declares the scriptβs parameters.url: textsays this script takes one required text input namedurl. The-- URL to probetrailing comment becomes the fieldβs documentation. Inputs, outputs, and tool declarations together form the ScriptContract β the typed interface the runtime validates calls against.outputblock β declares what the script returns: anumber, atext, and anobject. The compiler checks that youremitactually produces these.import toolsβ lists the tools this script may call.get_url_content_typeandfetch_urlare resolved from the catalog (here, bothfrom native). A tool not in this block is a compile error if you try todo ... withit.do "Probe URL" with get_url_content_typeβ the core action step. It calls the tool with one named input (url = url, mapping the script input into the toolβsurlargument) and binds the whole result object toprobevia-> probe.check "Is reachable?" probe.status < 400β a gate. The condition uses a field accessed off the previous stepβs binding.on pass: continueproceeds;on fail: fail "..."stops the thread with a message.do "Fetch content" with fetch_urlβ a second action;probeis in scope because bindings are visible to every step that comes after them.emit "Result"β declares the scriptβs output. Noteprobe.content_type ?? "unknown": the??operator supplies a default if the field is null. This is what fulfils theoutputcontract.
Where to go next
- File Structure & Declarations β the
input,output,stream,envblocks, comments,-- @annotations, and the ScriptContract. - The Type System β primitives,
list<T>, object shapes, enums, and constraints. - Expressions & References β bindings, field access, literals, interpolation, operators, and built-in functions.
- Steps β the body of a script, grouped into four pages:
- Core Action Steps β
do,let, native/K3 tools, and the internal model capabilities (llm,embed,rerank,transcribe,infer). - Control Flow & Concurrency β
each,decide,check,together,pipe,repeat,wait. - Agents & Human Input β
agent,ask,plan,run. - Streaming β
yield, the->>operator,send, andpipestaging.
- Core Action Steps β
- Error Handling β
on error,on thread error, and the default pause-and-resume. - Inline Python β
do β¦ with pythonand its dependency-resolution caveat.