File Structure & Declarations
Every .scriptum file has the same skeleton: an optional band of annotation
comments, the script header, a set of declaration blocks, and then the body of
steps. The declaration blocks must appear in a fixed order. This page covers the
header, the four declaration blocks, comments, the -- @ annotations, and how
all of it becomes the ScriptContract.
The skeleton
-- @accepts_content_type ... (optional annotations, before `script`)
script "Name" (required)
Free-text description... (optional)
version "0.1.0" (optional)
enum Name = A | B | C (optional, zero or more)
input ... (optional)
output ... (optional)
stream ... (optional)
import tools ... (optional)
env ... (optional)
<body steps> (the workflow)The order matters. The parser expects the declaration blocks in exactly this
sequence: input β output β stream β tools β env. Placing env
before the tools block, for example, causes the parser to skip the misplaced
block. When in doubt, follow the order above.
The script header
script "Research Agent"
Research a topic from multiple angles, evaluate findings,
and produce a structured summary with sources.
version "1.0.0"- The string after
scriptis the scriptβs name and primary identifier. - Any plain-text lines after the name (before
versionor the first declaration block) become the description. This is syntax, not a comment β it is the scriptβs documentation and is preserved in the contract. versiontakes a semantic version. It is optional but recommended; published versions are immutable snapshots.
The input block
Inputs are the scriptβs typed parameters. Each line is
name : type [constraints] [= default] [-- comment].
input
topic : text -- The subject to research
depth : number = 3 -- How many cycles (default: 3)
verbose : boolean = false -- Show detailed output
tags : list<text> -- Optional category tags- A field without a default is required; the runtime rejects a thread that does not supply it.
- A field with a default is optional.
- The trailing
-- commentis captured as the fieldβs documentation. - Inputs use the full type system β primitives,
list<T>, object shapes, enums, and constraints.
The output block
Outputs declare the shape the script returns. The compiler checks that your
emit steps actually produce these fields with matching types.
output
summary : text -- Final research summary
sources : list -- All sources usedOutputs can carry constraints too, and list<object> outputs can declare an
inline object schema with indented child fields:
output
status : text
artifact_id : text
total_objects : integer min(0)The stream block
The stream block declares the shape of values pushed by yield during
execution. It is the streaming counterpart to output: where output describes
the terminal result, stream describes the per-event payloads a consumer
receives in real time over WatchThread.
output
transcript : text
summary : text
stream
transcript : text
meta : objectSee Streaming for how yield and pipe produce
these values.
The tools block
The tools block lists every tool the script may call. Each tool is resolved
against the ToolCatalog at compile time; a name that does not resolve is a
compile error. There are two equivalent spellings β import tools (preferred)
and the legacy bare tools.
import tools
file_read from native -- a native built-in
echo from native
http_request from mcp("ignite") -- an Ignite-backed toolA few forms you will see in real scripts:
name from nativeβ a built-in Rust tool that always exists, no config.name from mcp("server")β a tool discovered from an Ignite source.* from nativeβ wildcard import of every native tool.- Namespaced names β fully qualified Ignite tools can be imported and aliased:
import tools
* from native
ignite::public::read_data_source as read_source
ignite::public::chunk_text as chunk_text
vector_store_insert from nativeThe as alias lets you refer to a long namespaced tool by a short local name
in the body (do "Read Source" with read_source).
You do not list the internal model capabilities (llm, embed, rerank,
transcribe, infer) in the tools block β they are handled directly by the
engine. See Core Action Steps.
The env block
The env block declares the environment variables and secrets the script needs.
Values are resolved at runtime from the tenant environment store; they never
live in the script source.
env
SLACK_TOKEN from "SLACK_TOKEN"
SLACK_CHANNEL_ID from "SLACK_CHANNEL_ID"
LLM_MODEL = "gpt-4o"Three forms appear in practice:
NAME from "SOURCE_KEY"β alias mapping: read the tenant store keySOURCE_KEYand expose it to the script asNAME.NAME = "value"β a default value, used when the store has no entry.NAME : text from "SOURCE_KEY" = "fallback"β env vars can carry an explicit type and constraints, identical to input fields. Untyped declarations default totext.
env
MILVUS_ENDPOINT : text length(0, 2048) = ""
EMBED_MODEL : TextEmbedModel = "jina-embeddings-v4"Inside the body, read an env value with the env(NAME) function or by
interpolation:
do "Embed" with embed
model = env(EMBED_MODEL)
texts = chunks
-> embed_resultThe send primitive reads channel credentials (Slack token, SMTP settings)
from these env vars automatically.
Comments
Comments use -- and run to the end of the line. There are no block comments.
-- This is a single-line comment.
-- Multi-line notes are written as consecutive single-line comments.
do "Search" with web_search -- inline comments are fine too
query = topic
-> resultsNote that --- (three or more dashes) is also treated as an ordinary comment β
it is not a section separator. Section separators use ### (see below).
Sections with ###
### lines create labeled phases. They render as headers in the visual view and
become section nodes in the IR; they have no effect on data flow.
### Phase 1: Discovery ###
do "Search" with web_search
query = topic
-> results
### Phase 2: Analysis ###
each result in results.matches
do "Classify" with classify
text = result.snippet
-> categoriesThe -- @ annotations
Annotation comments start with -- @ and appear before the script
keyword. The compiler recognizes two that shape how the platform routes data to
the script:
-- @accepts_content_type text/plain, text/html, application/pdf
-- @accepts_extension txt, md, html, pdf
script "text_embedding_index"
version "0.1.0"@accepts_content_typeβ a comma-separated list of MIME types this script accepts as input data. Globs likeimage/*are allowed. Consumers (UI dispatch, K3 routing) match these patterns against actual files to decide which script handles an incoming object.@accepts_extensionβ a comma-separated list of file extensions (a leading dot is optional; matching is case-insensitive). The canonical wire form is lowercase without the dot, so.PDFandpdfare equivalent.
Multiple annotation lines stack, and values are deduplicated. Other @-tags you
may see in template files (@tags, @labels, @description) are metadata read
by the template tooling and are otherwise ignored by the language compiler.
The ScriptContract
Taken together, the declaration blocks define the ScriptContract β the typed public interface of the script:
- inputs β required and optional parameters, with types and constraints.
- outputs β the terminal result shape.
- stream β the per-event payload shape (if the script yields).
- tools β the resolved capabilities the script depends on.
- env β the secrets/configuration it expects from the tenant store.
- accepted content types / extensions β the data this script is willing to process.
The contract is what the runtime validates against when a thread is created: it checks that supplied inputs satisfy the input types and constraints, and that required env vars are available. Because the contract is derived entirely from the source, editing a declaration block changes the contract β and the compiler re-checks the whole script against it.
Next: dig into the type system that powers these declarations in The Type System.