Functions
Functions run a Verb as code, a model call, an agent or a human Task, behind one signature.
Functions run a Verb as code, a model call, an Agent or a Human Task, behind the Verb's own typed signature. The caller never sees which kind ran.
Contract
The Verb is the contract and the Function is replaceable. One fn() per Verb binds the Verb's generated input and output types. Moving work from a person to an Agent is a one-line edit no caller sees.
| Kind | Runs as | Declares |
|---|---|---|
code | Deterministic code: same input, same output | code(input, |
generative | One model call that writes, extracts or classifies | instructions, model |
agentic | An Agent that plans and calls Tools over many steps | instructions, tools, model, maxSteps, budget |
human | A person working a Task in an App, Mailbox or Channel | role, sla, escalateTo, form |
| Guarantee | Detail |
|---|---|
| Typed | fn() takes the Verb's input type and must return its output type; a mismatch fails at compile time |
| Cascade | cascade lists kinds in order, and ctx.decline passes the work to the next; each hand-off is an Event |
| Recorded | The Action keeps the input, the output, the cost and Function.version |
| Bounded | tools are Verbs, so Permissions and Limits apply to each call; budget caps one Action's spend |
| Durable | ctx.step, ctx.sleep and ctx.waitForEvent survive restarts, as in any Workflow |
| Tested | The cases beside the Function pass before its Change merges |
Write a Function
The file name binds the Function to its Verb. fixwell.app settles warranty claims; it declares a Claim Noun. Code settles small claims and declines the rest to the Customer Lead Role.
// nouns/ Claim.settle.ts
import { fn } from 'startup-builder'
export default fn('Claim.settle', {
cascade: ['code', 'human'],
async code({ claim }, ctx) {
const c = await ctx.$.Claim.get({ id: claim })
if (c.amount.amount > 200) return ctx.decline('Over the automatic settlement line')
await ctx.step('refund', () => ctx.$.Order.refund({ order: c.order, amount: c.amount, reason: 'Warranty claim' }))
return { claim: c }
},
role: 'customer-lead',
sla: 'PT4H',
escalateTo: 'operations-lead',
})The human kind needs no form: one generates from the Verb's input and output.
The context
Every kind receives the same ctx.
| Member | Does |
|---|---|
ctx.$ | The client, acting as this Function's Worker, with on set to this Action |
ctx.step, ctx.sleep, ctx.waitForEvent | Durable steps and waits; a wait shows as pending with reason awaitingTime |
ctx.ask | Runs Action.ask and waits for the answer, with reason awaitingInput |
ctx.approve | Opens an Approval and waits, with reason awaitingAction |
ctx.decline | Passes to the next kind in the cascade, with a reason |
ctx.budget | Spend so far against the Function's budget and the Mandate's Limits |
ctx.emit | Streams a partial result to a caller in send |
An agentic Function
// nouns/ Claim.assess.ts
export default fn('Claim.assess', {
kind: 'agentic',
instructions: 'Decide whether the warranty covers the claim. Cite the photo and the term that decide it.',
tools: ['Claim.get', 'Order.get', 'Claim.settle'],
model: 'review',
maxSteps: 20,
budget: { amount: 2, currency: 'USD' },
})Cases
A case is { input,; the last two are optional. Code asserts on expect, and an Evaluator grades agentic and human output.
// nouns/ Claim.settle.test.ts
import { cases } from 'startup-builder'
export default cases('Claim.settle', [
{ input: { claim: 'claim_small' }, expect: { claim: { status: 'settled' } } },
{ input: { claim: 'claim_large' }, expect: { claim: { status: 'open' } }, evaluators: ['settlement-reason'] },
])Verbs
| Verb | Object | Event |
|---|---|---|
| Function.create | Function | Function.created |
| Function.get | Function | None. |
Interfaces
Example
matchbook.ai reconciles payments for agencies. Code matches by amount and date, a model reads remittance notes, an Agent chases what remains, and an accountant approves each write-off. Its Customers call one Verb, Payment.match, and see none of this.