An agent does work through procedures. Orchard has three kinds, and the difference between them is not stylistic: it is what the procedure is allowed to do, who can call it, and whether the model ever sees it. A fn is a pure helper. A tool is a deterministic effect the model can call. A skill is a procedure that itself calls the model. Pick the right one and the checker keeps you honest about the rest.
Three kinds of procedure
The three differ along one axis: how much they are allowed to touch.
fn name(p: T) -> Ris a pure helper. It may not usegen,delegate, tools, orstate. It is plain computation over its arguments, and the checker proves it. Afnis not advertised to the model; it is something your own code calls.tool name(p: T) -> Ris a deterministic effect: an HTTP request, a shell command, or a computation. Its body may usehttpandshell, but nevergen,delegate, orstate. It is model-callable, which means the autonomous loop can invoke it by name.skill name(p: T) -> Ris a model-using procedure. It may callgen, run other tools, and orchestrate them. It is exposed to the loop asskill_<name>, so the model can callskill_brieffor a skill namedbrief.
The rule of thumb: if it calls the model, it is a skill; if it has a side effect but no model call, it is a tool; if it is neither, it is a fn. The checker rejects a fn that reaches for gen, and a tool that does the same, so the boundaries are enforced rather than suggested.
Built-in packs with use
You do not write every tool yourself. use <pack> at the top of an agent pulls in a set of ready-made tools the model can call. The calculator pack is the simplest: it adds a single calculate tool that does exact arithmetic, which is worth having because models are unreliable at math and this is not.
use calculator // a built-in pack: gives you calculateThe built-in packs are:
httpfor outbound HTTP requests, subject to the policy's domain allowlist and the egress guard.webfor keyless web search and readable page fetching (it addsweb_searchandfetch_page).shellfor running shell commands throughrun_command, gated by the policy'sallow_shellsetting (off by default).browserfor driving a real headless Chrome over the DevTools Protocol:browser_open,browser_read(the rendered page, after JavaScript runs),browser_click,browser_type,browser_eval, andbrowser_screenshot. Grant it with one line,use browser; it launches Chrome with a throwaway profile and shuts it down when the run ends.mcpfor connecting to a Model Context Protocol server and exposing its tools to the loop.
A pack tool is dispatched exactly like one you declared yourself: same validation of arguments, same redaction of secrets in the output, same accounting against the budget.
Declaring a tool and a skill
Here is a tool and a skill side by side. word_count is a pure computation, so it is a tool with no effect at all. brief calls a pack tool and then calls the model, so it is a skill.
use calculator // a built-in pack: calculate
tool word_count(text: str) -> int {
return text.split(" ").length
}
skill brief() -> str {
let w = calculate(expression: "6 * 7")
return gen "The answer is {w.result}."
}Both are callable from your own code: word_count(text: msg) and brief(). Both are also advertised to the model during a delegate loop, the tool as word_count and the skill as skill_brief. A skill that mixes one constrained model call with deterministic routing around it is the central move of the language; you will see more of it in the typed generation lesson.
Annotations
Annotations tune how a procedure is presented and run. They go on the line above the declaration.
@description("Count the words in a piece of text.")
@timeout(30s)
tool word_count(text: str) -> int {
return text.split(" ").length
}@description("...")sets the human-readable description the model sees when deciding whether to call the tool. A clear description is the difference between the model using a tool correctly and ignoring it.@expose(false)keeps a procedure callable from your code but hides it from the model, so it never appears in the loop's tool list.@timeout(30s)bounds how long the procedure may run before it is cancelled. The argument is a duration literal.@unsafemarks a procedure whose effects are not safe to retry or run without care, so the runtime treats it accordingly.
How tools reach the model
When a delegate loop starts, the runtime builds the tool list the model will see. Each model-callable tool and exposed skill becomes one entry. The runtime lowers the procedure's parameter types into a JSON schema: a str parameter becomes a string field, an int becomes an integer, a record type becomes an object with typed properties. A money parameter lowers to a "$d.dd" string and a duration to a string like "30m". The description from @description rides along.
When the model decides to call a tool, it emits JSON for the arguments. The runtime validates that JSON against the schema before the body runs, so a malformed call becomes a structured tool error the model can read and correct, not a crash. The same path handles pack tools, your declared tools, and exposed skills identically, which is why a skill the model calls behaves no differently from a built-in.
tool body may use shell and http but never gen, delegate, or state. If you find yourself wanting a model call inside a tool, what you actually want is a skill. The checker will tell you so.Next. A procedure that only computes is forgettable; most agents need to remember things across turns and runs. Continue to Memory and state.
