Skip to content

Lesson 2: Inputs, Outputs, and Structure

Your email classifier from Lesson 1 works, but it has no contract. Anyone can pass it anything, and the output is whatever the AI feels like returning. That is fine for prototyping. Not fine for a system that runs 150 times a day.

This lesson adds structure: what goes in, what comes out, and what values are allowed.

Adding a Contract

Wrap your machine with accepts and responds with:

machine classify_email
accepts
subject as text, is required
sender as text, is required
body as text
responds with
priority as text
reason as text
implements
ask classify, using: "anthropic:claude-haiku-4"
with task "Is this email urgent, routine, or ignorable?\n\nFrom: ${input.sender}\nSubject: ${input.subject}\nBody: ${input.body}"
returns
priority as text
reason as text

accepts says: this machine requires a subject and sender (both text). Body is optional. If someone tries to run it without a subject, it fails immediately with a clear error instead of sending a broken prompt to the AI.

responds with says: this machine returns a priority and a reason. Anything that uses this machine downstream knows exactly what it will get back.

This is a contract. Other machines, APIs, and people can rely on it.

Constraining the AI’s Output

Right now the AI can return any text for priority. “Kinda urgent” or “medium-high” or ”!!!” are all valid. That is not useful for routing.

Add constraints to the returns block:

returns
priority as text, is required, choices: ["urgent", "today", "later", "ignore"]
reason as text

choices restricts the AI to exactly four values. If it returns something else, the runtime rejects it and retries. This is not prompt engineering. It is structural enforcement.

Adding Confidence

Classification is only useful if you know how sure the AI is. Add a confidence score:

returns
priority as text, is required, choices: ["urgent", "today", "later", "ignore"]
confidence as number, is required, range: [0.0, 1.0]
reason as text

range: [0.0, 1.0] means the confidence must be between 0 and 1. The AI cannot return 150% confidence.

Now when you run it, you see:

priority: "urgent"
confidence: 0.92
reason: "Production outage reported by ops team"

0.92 is high confidence. Act on it. If confidence were 0.55, you would want a human to look.

Compute: Free, Instant Logic

You can add logic that runs after the AI, with no API cost:

compute check_confidence
{
priority: classify.priority,
confidence: classify.confidence,
requires_human: classify.confidence < 0.7,
reason: classify.reason
}

compute steps are pure logic. No AI. No API call. This one checks if confidence is below 0.7 and flags it. It runs instantly and costs nothing.

The pattern: AI does the judgment. Compute does the logic.

Run It

/run classify_email

Try “URGENT: Server down” from ops. Look at the confidence. Then try “Quick question” from a colleague. Compare the confidence scores.

Notice the cost in the trace. The ask step costs $0.0003. The compute step costs $0.

What Comes Next

You have a classifier that returns structured data with confidence scores. Next lesson: using those values to make decisions. Urgent and confident? Do one thing. Uncertain? Do another. One machine, multiple paths.

Next: Decisions and Routing →