Control Flow & Concurrency
These steps shape the flow of a script: iterate over collections (each),
branch on rules or LLM judgment (decide), gate the flow (check), run lanes
in parallel (together), stream items through stages (pipe), loop until a
condition (repeat), and pause (wait). Each gets a worked example below.
each — iterate over a collection
each runs its body once per item and collects the per-iteration outputs into a
list. Bind the result with ->.
each result in raw_results.matches
do "Classify relevance" with classify
text = result.snippet
category = topic
-> classifiedresultis a loop-scoped binding — visible only inside the body.classifiedis a list, one entry per iteration.
Add an index with a comma:
each item, index in chunks
do "Process item {index}" with transform
data = item
-> processedYou can bound the loop’s concurrency with parallel <n> (run up to n items at
once) and sleep <ms> (inter-batch delay):
each vec, i in doc_vectors parallel 4
let similarity = cosine_similarity(query_vector, vec)
emit "Score"
doc_index = i
similarity = similarity
-> raw_scoresdecide — branch the flow
decide routes execution down exactly one branch. There are two strategies:
rule-based (with rules) and LLM-based (with llm).
Rule-based: decide ... with rules
Each when tests a condition and names a branch; otherwise is the fallthrough.
The named branches follow, each "Name": introducing its body. (Adapted from
examples/test_encrypt_pattern.scriptum.)
decide "Encrypt output?" with rules
when encrypt_output == true and encryption_key != "" -> "Encrypt"
otherwise -> "No Encryption"
"Encrypt":
do "Encrypt data" with encrypt_data
plaintext = clinical_data.text
key = encryption_key
-> encrypted
"No Encryption":
do "Passthrough" with echo
message = "plaintext"
-> encryptedRules shine for content routing — dispatch on a MIME type, a status, or a flag:
decide "Route by Type" with rules
when contains(content_type, "pdf") -> "PDF"
when contains(content_type, "html") -> "HTML"
when contains(content_type, "audio") -> "Audio"
otherwise -> "Text"
"PDF":
do "Extract PDF" with extract_pdf { url = resolved_url }
-> extracted
"HTML":
do "Extract HTML" with extract_html { url = resolved_url }
-> extracted
"Audio":
do "Transcribe" with transcribe { model = transcribe_model, audio = resolved_url }
-> extracted
"Text":
do "Read text" with fetch_url { url = resolved_url }
-> extractedLLM-based: decide ... with llm
Here the branch is chosen by the managed LLM. Give it a prompt (a bare string), then list the candidate branches by name; the LLM picks one.
decide "Choose architecture approach" with llm
"Given the synthesis:\n{synthesis.text}\n\nWhich architecture best fits this project?"
"Microservices":
do "Design microservices" with llm
prompt = "Design a microservices architecture for '{project_name}'."
-> arch_design
"Monolith First":
do "Design monolith" with llm
prompt = "Design a modular monolith for '{project_name}'."
-> arch_design
"Serverless":
do "Design serverless" with llm
prompt = "Design a serverless architecture for '{project_name}'."
-> arch_designBranch bindings
Because exactly one branch runs, branches may bind the same name (-> arch_design
above). If any step after the decide references that binding, the compiler
requires every branch to produce it — otherwise it is a compile error.
check — gate the flow
check evaluates a condition and routes to on pass / on fail. The condition
follows the label on the same line. Actions are continue, goto "Label",
retry N, or fail "message".
check "Is reachable?" probe.status < 400
on pass: continue
on fail: fail "URL returned error status"check "Found relevant articles?" count(articles) > 0 and articles[0].relevance > 0.7
on pass: continue
on fail: goto "Escalate to human"A check can also defer to the LLM with ask llm "prompt" instead of a boolean
expression:
check "Research quality"
ask llm "Is this research comprehensive and well-sourced? Research: {research.text}"
on pass: continue
on fail: goto "Request revision guidance"together — parallel lanes
together runs several lanes concurrently and waits for all of them. Each lane
binds its own result; the outer binding collects them.
together "Research from multiple angles"
do "Technical angle" with web_search
query = "{topic} technical deep dive"
-> technical
do "Market angle" with web_search
query = "{topic} market analysis 2026"
-> market
do "Academic angle" with web_search
query = "{topic} research papers"
-> academic
-> all_researchLanes can hold any steps, including agent calls — fan out independent agents
and merge their findings afterward:
together "Diarize and classify"
do "Speaker diarization" with infer
model = diarize_model
input = { "type": "audio_url", "audio_url": resolved_url }
-> speakers
do "Audio classification" with infer
model = audio_class_model
input = { "type": "audio_url", "audio_url": resolved_url }
-> cls_rawpipe — stream items through stages
pipe item from source consumes a source (an array or a stream) and runs its
body per item. Bind with -> to collect all items, or ->> to forward
the items as a stream into the next stage. (Adapted from
examples/stream_pipeline.scriptum.)
do "Find Rust files" with file_glob
pattern = "crates/scriptum-compiler/src/*.rs"
-> glob_result
-- array source → stream forward
pipe file from glob_result.matches
do "Tag file" with echo
message = file
->> tagged
-- stream source → collect
pipe item from tagged
do "Echo item" with echo
message = item.message
-> collectedpipe accepts batch <n> (group items into batches) and parallel <n> (run
batches concurrently) — useful for high-throughput embedding:
pipe chunk from chunks.chunks batch 20 parallel 3
do "Contextualize Chunk" with llm
prompt = "Document:\n{extracted_text}\n\nChunk:\n{chunk}\n\nEnriched chunk:"
max_tokens = contextualize_max_tokens
-> enriched_chunksSee Streaming for yield inside a pipe and the
->> operator in depth.
repeat — loop until a condition
repeat runs its body until an until condition becomes true. The until
clause goes at the end of the body, and an optional max N caps the iteration
count. Combine with let to maintain loop state. (Adapted from
examples/repeat_test.scriptum.)
let counter = 0
repeat "Count to five"
let counter = counter + 1
do "Log iteration" with echo
message = "iteration {counter}"
-> log
until counter >= 5
-> repeat_resultWith a safety cap:
let x = 0
repeat "Capped loop"
let x = x + 1
do "Log x" with echo
message = "x = {x}"
-> x_log
until x > 9999
max 3
-> capped_resultwait — pause execution
wait pauses the thread for a duration, or until a condition is met (with an
optional timeout).
wait "Brief pause"
duration = "100ms"wait "Until ready"
condition = ready == true
timeout = "5s"Durations are written as strings: "100ms", "2s", "24h". A paused thread is
durable — it can resume hours later exactly where it stopped.
Next: the agent and human-input primitives in Agents & Human Input.