CubePi vs CrewAI
CrewAI organises your agent system around role-playing metaphors — crews, agents with roles and backstories, and tasks that agents are assigned to. CubePi starts from a different premise: an agent is a plain async while-loop that calls the model, runs tools, and repeats.
If the role-based abstraction is solving a problem you do not have, CubePi removes that layer. Here is how the two compare.
Side-by-side
| CrewAI | CubePi | |
|---|---|---|
| Abstraction | Crews of role-playing agents with roles, goals, and backstories | A plain async while-loop — readable top to bottom in five minutes |
| Multi-agent | First-class: hierarchical or sequential process types | Subagent middleware — spawn child agents, await results, compose freely |
| Async | Synchronous by default; async support added later | Async-first — every entry point is async |
| Streaming | Limited streaming support | async for event in stream — one pattern, eleven event types |
| Checkpointing | No built-in persistence layer | Append-only — O(1) DB I/O; backends: memory, SQLite, Postgres, MySQL |
| Dependencies | langchain-core + many transitive deps | 3 core deps: pydantic, anthropic, openai |
| Tools | Tool classes with schema boilerplate | Decorate any async function with @tool — schema auto-derived |
| Observability | Basic logging; no native OTel | Native OpenTelemetry — GenAI semconv, OTLP / JSONL exporters |
| Testing | Requires live API calls for most tests | FauxProvider — deterministic streaming, no API keys needed |
A tool-using agent
from crewai import Agent, Task, Crew, Process
from crewai.tools import BaseTool
from pydantic import BaseModel
class WeatherInput(BaseModel):
city: str
class WeatherTool(BaseTool):
name: str = "get_weather"
description: str = "Get current weather for a city."
args_schema: type[BaseModel] = WeatherInput
def _run(self, city: str) -> str:
return f"72F and sunny in {city}"
weather_tool = WeatherTool()
agent = Agent(
role="Weather Assistant",
goal="Answer weather questions accurately.",
backstory="You are a helpful weather assistant.",
tools=[weather_tool],
llm="claude-sonnet-4-6",
)
task = Task(
description="What's the weather in {city}?",
expected_output="A weather report for the city.",
agent=agent,
)
crew = Crew(agents=[agent], tasks=[task], process=Process.sequential)
result = crew.kickoff(inputs={"city": "Tokyo"})
from cubepi import Agent, tool
from cubepi.providers.anthropic import AnthropicProvider
@tool
async def get_weather(city: str) -> str:
"Get current weather for a city."
return f"72F and sunny in {city}"
provider = AnthropicProvider(provider_id="anthropic", api_key="...")
agent = Agent(
model=provider.model("claude-sonnet-4-6"),
tools=[get_weather],
system_prompt="You are a helpful weather assistant.",
)
await agent.prompt("What's the weather in Tokyo?")
Roles, goals, and backstories — or just a system prompt
CrewAI's "role", "goal", and "backstory" fields are a structured way to write a system prompt. When you have one agent doing one job, the role metaphor adds ceremony without adding capability. In CubePi, `system_prompt` is a string you own completely — no framework vocabulary to learn, no structure to maintain.
For genuine multi-agent scenarios, CubePi provides a `SubagentsMiddleware` that spawns child agents and wires their results back to the parent — without the crew/process/task object model.
Persistence that does not require you to wire it yourself
CrewAI does not ship a built-in checkpointing layer. If you want conversations to survive restarts you add your own database logic. CubePi's append-only checkpointing ships out of the box — `SQLiteCheckpointer`, `PostgresCheckpointer`, and `MySQLCheckpointer` each write only the new messages from each turn, so write cost stays O(1) regardless of how long the thread grows.
When CrewAI is the better fit
CrewAI is a reasonable choice if your workflow maps naturally onto the crew metaphor — a team of specialist agents each assigned a discrete task in a pipeline. If you are building a single conversational agent, a stateful assistant, or anything that needs production-grade persistence or OTel observability, CubePi is the leaner path.