Bridle
Guide

Agents

Wrap a Python function whose body uses primitives. Validate I/O, set the model and budget, get a traceable unit.

@agent marks a Python function as a Bridle program. The body uses primitives; the decorator gives the function a typed input, a typed output, a default model, an optional token budget, and a name on the trace.

A decorated function returns a Call rather than its body's value — the body runs when the call is resolved, and inner primitives pick up the agent's settings.

Signature

from bridle import agent

@agent(
    *,
    input: type | None = None,
    output: type | None = None,
    model: str | None = None,
    token_budget: int | None = None,
    name: str | None = None,
)
def fn(...) -> ...:
    ...

All parameters are optional. The minimum viable agent is @agent() over a function whose body uses primitives.

Set the input and output

from pydantic import BaseModel

class Query(BaseModel):
    topic: str
    depth: Literal["shallow", "deep"] = "shallow"

class Brief(BaseModel):
    headline: str
    body: str

@agent(input=Query, output=Brief, model="claude-sonnet-4-6")
def brief_writer(q: Query) -> Brief:
    ...

input validates the first positional argument. If the caller passes a dict, Bridle coerces it through the schema:

brief_writer({"topic": "Mars", "depth": "deep"})

output validates the body's return value. A return that does not match raises ValidationError at the agent boundary.

Set a default model

model="claude-sonnet-4-6" becomes the default for every step inside the body. See model resolution for how it interacts with with_model and bridle.configure.

@agent(input=str, output=Brief, model="claude-opus-4-7")
def deep_brief(topic: str) -> Brief:
    return step("write a comprehensive brief", schema=Brief, context=topic)

Inner agents inherit. If deep_brief calls another @agent that does not declare its own model=, the inner one uses opus too.

Set a token budget

token_budget enforces a soft per-agent limit. Token usage accumulates after each model response; when the next step would breach the budget, TokenBudgetExceededError raises:

@agent(input=Query, output=Brief, model="claude-sonnet-4-6", token_budget=200_000)
def brief_writer(q: Query) -> Brief:
    ...

The budget applies to this agent's subtree. Nested agents share the parent's accumulator unless they declare their own budget — declaring one resets usage for that subtree.

Calling agents

A decorated function returns a Call:

call = brief_writer(Query(topic="Mars weather"))
brief: Brief = bridle.resolve(call)

Inside another agent's body, you can use the call directly — the dispatcher resolves it for you when the result is read or returned:

@agent(input=str, output=Brief)
def double_check(topic: str) -> Brief:
    first = brief_writer(topic)              # a Call
    if branch("is the brief strong enough?", context=first):
        return first                          # resolved automatically on return
    return brief_writer(f"{topic} — sharper")

What you get

Five things, automatic:

  1. Input/output validation. Pydantic at both boundaries.
  2. Model and budget context. Inner primitives pick them up via contextvars — no threading required.
  3. Traceable unit. A call_kind: "agent" event opens at start and closes at end. Inner steps nest underneath.
  4. Composable invocation. Returns a Call, so you can wrap with cache, retry, with_model, etc.
  5. Recursion. An agent can call itself. The brief writer in the recipes does this to gather more evidence.

Pitfalls

Mutating arguments. Treat agent inputs as immutable. The decorator coerces them through the input schema; the validated value is what reaches your body. Mutating it has no effect on later validation but makes the trace misleading.

Returning a Call you do not want resolved automatically. The agent dispatcher calls resolve() on whatever the body returns. If you mean to hand a deferred call to a wrapper, build the wrapper inside the body and return the wrapped call — the dispatcher will resolve the wrapper, which in turn drives the inner work.

Heavy logic outside primitives. It's tempting to do parsing, network I/O, or analytics inside the body. Fine in moderation — but anything not in a primitive does not appear in the trace. Push real work into a @tool or a step.

Forgetting name= on lookalike agents. When you have multiple agents with the same __name__ (different modules, same file name), set name= so the trace can tell them apart.

A complete example

import bridle
from bridle import agent, branch, step
from bridle.models.anthropic import install
from pydantic import BaseModel
from typing import Literal


class Query(BaseModel):
    topic: str
    depth: Literal["shallow", "deep"] = "shallow"


class Brief(BaseModel):
    headline: str
    body: str


@agent(input=Query, output=Brief, model="claude-sonnet-4-6", token_budget=200_000)
def brief_writer(q: Query) -> Brief:
    plan = step("draft a plan", schema=str, context=q)

    if not branch("is the plan strong?", context=plan):
        return brief_writer(Query(topic=q.topic, depth="deep"))

    return step("write the brief", schema=Brief, context=(q, plan))


install()
brief = bridle.resolve(brief_writer({"topic": "Mars weather"}))
print(brief.headline)

Next

On this page