Skip to content

Inputs and Outputs

Every machine has a contract: what data it takes in (accepts) and what data it returns (responds with). The runtime validates incoming data against this contract before any step runs. If a required field is missing or a constraint is violated, execution is rejected immediately. No steps execute, no tokens are spent, no ledger entries are created.

Declaring inputs

accepts
name as text, is required
email as text, is required, format: "email"
age as integer, min: 13
role as text, default: "member", choices: ["member", "admin", "moderator"]

Each field has three parts: a name, a type (after as), and optional modifiers.

Types

TypeDescription
text / stringText value
numberNumeric (integer or decimal)
integerInteger only
decimalDecimal/float
booleanTrue or false
listArray of values
list of <type>Typed list (e.g., list of text)
mapKey-value object
anyUntyped

Modifiers

is required makes a field mandatory. default: <value> provides a fallback when the field is not supplied.

accepts
query as text, is required
limit as number, default: 10
include_archived as boolean, default: false

Validation constraints

Constraints catch bad data before it reaches your steps:

accepts
username as text, is required, min_length: 3, max_length: 20, pattern: "^[a-zA-Z0-9_]+$"
score as number, range: [0.0, 1.0]
priority as text, choices: ["low", "medium", "high", "urgent"]
tags as list, subset: ["bug", "feature", "docs"]
ConstraintApplies toMeaning
min: / max:numbersValue bounds
min_length: / max_length:textString length bounds
format:textNamed format (e.g., "email")
pattern:textRegex match
choices:textAllowed values
range: [lo, hi]numbersValue within range

Declaring outputs

responds with
summary as text
confidence as number
tags as list of text

The runtime validates that the machine’s final output matches this contract. Extra fields are stripped. Missing fields cause an error.

Source mapping with from

You can map output fields directly to step results instead of writing a final compute step:

responds with
answer as text, from search
sources as list, from search
cached as boolean, from state.was_cached, default false

Without from, output fields are populated from the last step’s return value by matching field names.

Accessing inputs in steps

Inside any step, use input.<field> to access input values:

compute greet
{greeting: "Hello, " + input.name + "!"}

Inside ask steps, interpolate inputs into the task string:

ask classify, using: "anthropic:claude-sonnet-4-6"
with task "Classify this text: ${input.text}\nMax categories: ${input.limit}"

A complete example

machine user_profile_validator
accepts
email as text, is required, format: "email"
username as text, is required, min_length: 3, max_length: 30
age as integer, is required, min: 13, max: 150
bio as text, max_length: 500
interests as list of text
responds with
valid as boolean
profile_summary as text
warnings as list of text
implements
compute validate_and_summarize
let warnings = []
let bio_text = input.bio || "No bio provided"
let interest_count = input.interests ? input.interests.length : 0
{
valid: true,
profile_summary: input.username + " (" + input.email + "), age " + input.age + ", " + interest_count + " interests",
warnings: interest_count == 0 ? ["No interests listed"] : []
}

The accepts section handles type checking and constraint validation. By the time compute runs, you know the data is clean.

Try it

Create a machine that accepts an email address (with format validation), a message body (with a max_length of 1000), and a priority field (with choices). Have it use a compute step to build a formatted output. Try invoking it with invalid data and observe the validation error.

Next steps