Quick Start
Ship a streaming, tool-using agent in five minutes. We'll build a weather agent that calls a Python function as a tool, streams Claude's response token-by-token, and exits cleanly.
Prerequisites
- Python 3.11+
cubepiinstalled (pip install cubepi)- An
ANTHROPIC_API_KEYin your environment
The full script
Save this as weather_agent.py:
weather_agent.py
import asyncio
import os
from pydantic import BaseModel
from cubepi import Agent, AgentTool, AgentToolResult, Model, TextContent
from cubepi.providers.anthropic import AnthropicProvider
class GetWeatherParams(BaseModel):
city: str
async def get_weather(tool_call_id, params: GetWeatherParams, *, signal=None, on_update=None):
# In a real app: call an HTTP weather API. Here we hard-code a reply.
return AgentToolResult(
content=[TextContent(text=f"72°F and sunny in {params.city}")]
)
async def main():
provider = AnthropicProvider(api_key=os.environ["ANTHROPIC_API_KEY"])
agent = Agent(
provider=provider,
model=Model(id="claude-sonnet-4-5-20250929", provider="anthropic"),
system_prompt="You are a concise weather assistant.",
tools=[
AgentTool(
name="get_weather",
description="Get current weather for a city",
parameters=GetWeatherParams,
execute=get_weather,
),
],
)
# Subscribe BEFORE prompt() — that's how you see streaming events.
def on_event(event, signal=None):
if event.type == "message_update" and event.stream_event.type == "text_delta":
print(event.stream_event.delta, end="", flush=True)
elif event.type == "agent_end":
print() # final newline
agent.subscribe(on_event)
await agent.prompt("What's the weather in Tokyo?")
asyncio.run(main())
Run it:
python weather_agent.py
You should see Claude stream a sentence like "The weather in Tokyo is currently 72°F and sunny…", with the tool result threaded through.
What just happened
CubePi ran a loop that looks (conceptually) like this:
agent.prompt("What's the weather in Tokyo?")enqueued aUserMessageand called the model.- The model decided to invoke
get_weather(city="Tokyo")— CubePi parsed the JSON args with your Pydantic model, called yourasync def, and fed the result back as aToolResultMessage. - The model produced a final assistant response, streamed back as
text_deltaevents. - The loop emitted
agent_endand returned.
agent.subscribe(...) registered a callback for every event the
runtime emits: agent_start, turn_start, message_start,
text_delta, tool_execution_start, tool_execution_end,
message_end, turn_end, agent_end. The script only cared about
text_delta, but you can hang any UI off the rest.
Sources of confusion (read before you debug)
- Subscribe before you prompt. Listeners only receive events
emitted after
subscribewas called. Callingpromptfirst means the early events are gone. Model(id=…, provider=…)is required. The agent usesmodel.providerto clamp thinking levels and route metadata. The id must match a model the provider supports.- The
executesignature is fixed. Keep(tool_call_id, params, *, signal, on_update)even if you don't use the keyword-only arguments. CubePi always passes them. agent.prompt()can only run one prompt at a time. While it's running, useagent.steer()to inject a follow-up, oragent.follow_up()to queue one for after the current run.
Next steps
- Core Concepts — the mental model behind
Agent,Tool,Provider,Stream,Middleware,Checkpointer. - Building Your First Agent — full walkthrough of tools, streaming, and error handling.
- Tool Use & Parallel Execution — make the agent fan out across multiple tools at once.
- Recipes → Weather Agent — a slightly beefier version of this script with real HTTP calls.