Skip to content

Agents

Agent[Deps, Output] is the typed unit of work — a model, instructions, tools, and an output contract. It is generic over a dependency type (Deps, for dependency injection) and an output type (Output, for validated results).

from yaab import Agent

agent = Agent(
    name="assistant",
    model="openai/gpt-4o",              # str (LiteLLM id) or a ModelProvider
    instructions="You are helpful.",     # str or Callable[[RunContext], str]
    tools=[...],                          # functions or Tool objects
    deps_type=MyDeps,                     # DI payload type
    output_type=MyModel,                  # str (default) or a Pydantic model / type
    guardrails=[...],                     # optional per-agent scanners
    skills=[...],                         # reusable bundles (see prompts-skills.md)
    registry_id="assistant",             # link to the governance registry
    max_steps=8,                          # tool-loop budget
    output_retries=2,                     # reflection/retry on output validation
)

Running

result = await agent.run("prompt", deps=..., session_id="s1", identity="alice")
result = agent.run_sync("prompt")                      # sync wrapper
async for token in agent.stream("prompt"): ...         # token streaming
async for event in agent._get_runner().run_stream(agent, "prompt"): ...

identity flows into the run context and the audit log; session_id enables durable, multi-turn conversations (see State).

Dynamic instructions

Instructions can be a callable that builds the system prompt from the run context — useful for injecting per-request facts:

def instructions(ctx) -> str:
    return f"You are helping {ctx.deps.user_name}. Be brief."

agent = Agent("a", model="openai/gpt-4o", instructions=instructions, deps_type=Deps)

Registering tools after construction

agent = Agent("a", model="openai/gpt-4o")

@agent.tool
def now() -> str:
    """Return the current time."""
    return "2026-01-01T00:00:00Z"

Agent as a tool

Any agent (or workflow agent) can be exposed as a tool to another, enabling hierarchical delegation:

specialist = Agent("specialist", model="openai/gpt-4o", instructions="You are a tax expert.")
generalist = Agent("generalist", model="openai/gpt-4o", tools=[specialist.as_tool()])

Per-agent callbacks

before_agent / after_agent fire around an agent's own loop — so in a composition (Sequential, Parallel, Swarm, …) each child gets its own pair when it runs, not once at the top. Both may be sync or async.

from yaab import Agent
from yaab.testing import TestModel

events: list[str] = []
agent = Agent(
    "a",
    model=TestModel("hi"),
    before_agent=lambda ag, prompt: events.append(f"before:{ag.name}:{prompt}"),
    after_agent=lambda ag, result: events.append(f"after:{ag.name}:{result.output}"),
)
# before_agent(agent, prompt) runs first; after_agent(agent, result) runs last.

Filtering context to what's relevant

A context_strategy rewrites the message history before each model call. RelevanceFilter keeps only the turns relevant to the latest message (system messages and the latest user turn are always kept), alongside the built-in truncate/summarize strategies.

from yaab import Agent, RelevanceFilter

agent = Agent(
    "a",
    model="openai/gpt-4o",
    context_strategy=RelevanceFilter(min_score=0.15),   # drop low-relevance history
)

The default scorer is keyword overlap; inject any (query, text) -> float (e.g. an embedding similarity) for semantic relevance:

from yaab import RelevanceFilter

def embed_score(query: str, text: str) -> float:
    ...                       # your similarity in [0, 1]

strategy = RelevanceFilter(min_score=0.2, scorer=embed_score)

Declarative agents (YAML / dict)

agent_from_yaml / agent_from_dict build an agent from a spec. output_type resolves by name — the built-in scalars str / int / float / bool need no registration; any Pydantic model is referenced by the name it was registered under, so a declarative agent can emit a typed object, not just text.

from pydantic import BaseModel
from yaab import agent_from_dict, register_component
from yaab.testing import TestModel

class Ticket(BaseModel):
    title: str
    priority: int

register_component("output_type", "Ticket", lambda: Ticket)   # resolve the name

agent = agent_from_dict({
    "name": "tk",
    "model": TestModel('{"title": "Reset password", "priority": 1}'),
    "output_type": "Ticket",        # -> agent.output_type is Ticket
})

Callbacks and plugins wire by registered name too — callbacks: {before_agent: …, after_agent: …} resolves callback components onto the agent's hooks, and plugins: [name] resolves plugin components onto its Runner. An unknown name is a clear load-time error:

from yaab import agent_from_dict, register_component
from yaab.testing import TestModel

register_component("callback", "audit", lambda: (lambda ag, prompt: None))
agent = agent_from_dict({
    "name": "a",
    "model": TestModel("hi"),
    "callbacks": {"before_agent": "audit"},
})

The Runner

Agent.run delegates to a Runner, which owns the services, the plugin chain, and (optionally) governance. Construct one explicitly to share configuration:

from yaab import Runner
from yaab.sessions import SQLiteSessionService

runner = Runner(session_service=SQLiteSessionService("sessions.db"))
result = await runner.run(agent, "hi", session_id="s1")

See Models, Tools, and Governance for the pieces an agent composes.