Your first agent
A tool, an agent, a reply — in about ten lines.
A tool is a decorated async function. An agent is a prompt, a model and a list of tools. There is no third concept.
from agentino import Agent, tool
@tool
async def get_weather(city: str) -> str:
"""Look up current weather for a city."""
return f"It's 22°C in {city}."
agent = Agent(instructions="You're a helpful assistant.", tools=[get_weather])
print(await agent.run("What's the weather in Lisbon?"))
# → "It's 22°C in Lisbon. Want a forecast?"
That is the whole API. The framework handles the model round-trip, tool dispatch, retries and pulling the final text out of the response.
What the decorator reads
@tool builds the schema the model sees from the function itself — nothing is
declared twice:
- the function name becomes the tool name
- the docstring becomes the description, and its
Args:section describes each parameter - the type hints become the JSON schema
So the function above is offered to the model as get_weather(city: string)
with the description "Look up current weather for a city." Rename the
function and the tool renames itself.
What happens on .run()
- Your instructions and the user's message go to the model along with the tool schemas.
- If the model asks for a tool, the runtime calls it, appends the result, and goes back to the model.
- That repeats until the model answers in prose or
max_turnsis reached. - You get the final text.
Failures inside a tool come back to the model as an error result rather than raising, so one broken lookup does not end the turn.
Synchronous callers
The core is async all the way down, and there is deliberately no sync wrapper — one would either block an event loop that is already running or hide the fact that a turn takes seconds. From synchronous code, drive it yourself:
import asyncio
print(asyncio.run(agent.run("What's the weather in Lisbon?")))
Runner is the higher-level entry point when you are working from a config
file rather than an Agent you built in Python:
from agentino import Runner, load_config
runner = Runner(load_config("agents.yml"))
print(await runner.one_shot("Review PR #42"))
load_agents is the same loader when you want the agents themselves rather
than a runner around them.