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 textaccepts 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 textchoices 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 textrange: [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.92reason: "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_emailTry “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.