The autonomous loop and policy

Up to here, your code has orchestrated the model one call at a time. This lesson hands the wheel to the model. delegate gives a goal to the autonomous loop, which decides for itself which tools to call and when it is done. That is the part that feels like magic and the part that needs a fence around it, so this lesson is equally about policy: the safety envelope that bounds what the loop can do.

delegate

delegate goal hands the goal to the perceive-think-act loop. The model perceives the goal and the available tools, thinks about the next step, acts by calling a tool, reads the result, and repeats, looping until it produces a final answer. It runs over the agent's tools and exposed skills, the same set advertised with JSON-schema params that you saw in the tools and skills lesson.

on message(text: str) -> str {
    return delegate text
}

You can scope a delegation with a with clause, restricting the tools it may use, capping its budget, or limiting its steps. This is how you give one span exactly the capabilities it needs and nothing more.

let report = delegate "Research this angle and cite sources: {angle}"
    with { tools: [web_search, fetch_page], max_steps: 8 }

The policy block

Where with bounds a single delegation, policy is the agent-wide safety envelope. Every delegation runs inside it, and a delegation can only ever tighten these limits, never loosen them.

policy {
    max_steps: 20
    max_tool_calls: 60
    max_spend: $1.00
    allow_shell: ask
    allowed_domains: ["api.example.com"]
}
  • max_steps caps how many perceive-think-act iterations the loop may run. The default is 25.
  • max_tool_calls caps the total number of tool invocations. The default is 100.
  • max_spend caps how much money the loop may spend on model calls, as a money literal.
  • allow_shell gates shell execution. It is "never" by default, which refuses all shell; set it to ask for an interactive y/n per command, or always only for inputs you fully trust.
  • allowed_domains is the egress allowlist for HTTP. An empty list permits any domain (the egress guard still blocks loopback and private addresses); pin it to specific hosts to narrow the prompt-injection surface.

Two more defaults are worth knowing: max_delegate_depth is 4, which bounds how deeply a delegation may nest further delegations, and an unattended serve run carries a spend cap of $5.00 so a long-running agent cannot quietly run up a bill. The gate is enforced by the runtime, not the model. A confused or prompt-injected model cannot talk its way past allow_shell.

budget

budget(spend, steps, tool_calls) { ... } opens a tighter, nested policy scope around a block. It can only tighten an outer limit, never raise it, so wrapping work in a budget is always safe. Use it to cap an open-ended request so it cannot run away.

on message(text: str) -> str {
    return budget(spend: $0.10, steps: 6) {
        delegate text
    }
}

As you saw in the concurrency lesson, a budget around concurrent work tightens the shared cap for the whole scope, and every branch draws from that one pool.

Reliability

Models are not deterministic and tools fail, so Orchard gives you a few constructs to make a flow reliable.

retry(n) { ... } until (v) => ... re-runs a block until a predicate over its value holds, up to n attempts. It is the right tool when the model might produce something you can check but not on the first try.

let label = retry(3) {
    gen as Severity "Be decisive: {issue}"
} until (r) => r != Severity.low or attempts == 3

try/catch handles a Throw the ordinary way. This is where you catch the error a delegate or an await re-raised, and decide whether to recover, retry, or report.

try {
    return delegate text
} catch (e) {
    return "I could not complete that: {e.message}"
}

halt "reason" stops the agent deliberately with a stated reason. Reach for it when the agent has hit a condition it should not proceed past, rather than throwing an error you would only catch and re-raise.

Note. Think of the three layers as nested fences. policy is the agent's outer wall, budget tightens it for a block, and a with clause tightens it for a single delegation. None of them can loosen what encloses it, so the tightest one always wins.

Next. Your agent is now bounded and reliable. The last step is getting it out into the world: continue to Running and deploying for the CLI in depth, the compiled IR, and serving an agent.