Skip to main content

CubePi vs LangGraph

CubePi and LangGraph both build tool-using LLM agents in Python, but they start from opposite mental models. LangGraph asks you to express your agent as a state graph — nodes, edges, and typed channels you wire together. CubePi models the same agent as a plain async while-loop you can read top to bottom.

If you find yourself drawing graphs to express what is fundamentally a linear "call the model → run tools → repeat" loop, CubePi is the leaner alternative. Here is how the two compare.

Side-by-side

LangGraphCubePi
AbstractionState graph: nodes + edges + channels you wire manuallyA plain async while-loop — run_agent_loop reads top to bottom
Control flowadd_edge / add_conditional_edges reify the loop as a graphThe tool-call → re-prompt loop IS the runtime; you do not reify it
StreamingCallback-based with multiple stream_mode flagsasync for event in stream — one pattern, eleven event types
CheckpointingFull snapshot per step; serializes the entire message listAppend-only — O(1) DB I/O regardless of conversation length
Dependencieslangchain-core, langgraph-sdk, and transitive deps3 core deps: pydantic, anthropic, openai
ToolsTools are graph nodes (ToolNode) with manual wiringDeclare tools as functions; the framework routes and parallelizes
AsyncSplit invoke / ainvoke surfaceAsync-first — every entry point is async
ObservabilityLangSmith / Langfuse integrationNative OpenTelemetry — GenAI semconv, OTLP / JSONL out of the box

A tool-using agent

# LangGraph
from langchain_anthropic import ChatAnthropic
from langgraph.graph import StateGraph, END
from langgraph.prebuilt import ToolNode
from langchain_core.tools import tool


@tool
def get_weather(city: str) -> str:
"""Get current weather for a city."""
return f"72F and sunny in {city}"


llm = ChatAnthropic(model="claude-sonnet-4-5").bind_tools([get_weather])

class State(TypedDict):
messages: list

def call_model(state):
return {"messages": [llm.invoke(state["messages"])]}

def should_continue(state):
return "tools" if state["messages"][-1].tool_calls else END

graph = StateGraph(State)
graph.add_node("llm", call_model)
graph.add_node("tools", ToolNode([get_weather]))
graph.add_edge("__start__", "llm")
graph.add_conditional_edges("llm", should_continue)
graph.add_edge("tools", "llm")
app = graph.compile()
# CubePi
from pydantic import BaseModel
from cubepi import Agent, AgentTool, AgentToolResult, 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):
return AgentToolResult(content=[TextContent(text=f"72F and sunny in {params.city}")])


agent = Agent(
model=AnthropicProvider(provider_id="anthropic", api_key="...").model("claude-sonnet-4-5"),
tools=[AgentTool(
name="get_weather",
description="Get current weather for a city.",
parameters=GetWeatherParams,
execute=get_weather,
)],
)
await agent.prompt("Weather in Tokyo?")

Why the loop instead of a graph

A LangGraph agent never branches at runtime the way a general graph suggests — the "graph" is almost always the same shape: call the model, and if it asked for tools, run them and call the model again. CubePi makes that shape the runtime. There is no StateGraph, no END sentinel, no should_continue function, no ToolNode registry, and no State TypedDict to keep in sync.

Flow control that does need to vary — stop early, summarize, gate a tool behind human approval — lives in typed middleware hooks instead of conditional edges, so it is imperative and testable in isolation.

Persistence that does not grow with the conversation

LangGraph checkpointers snapshot the full state at every step, so write cost grows linearly with conversation length. CubePi checkpointing is append-only: each turn writes O(1) regardless of how long the thread is, and messages stay JSONB-queryable. The same MemorySaver / SqliteSaver / PostgresSaver idea maps onto MemoryCheckpointer / SQLiteCheckpointer / PostgresCheckpointer.

When LangGraph is the better fit

LangGraph is the better choice if you genuinely need arbitrary multi-agent supervisor graphs, visual graph rendering, or time-travel/fork at arbitrary checkpoints today — CubePi keeps its flow linear by design and emits vendor-neutral OpenTelemetry rather than shipping its own trace UI. If your agent is fundamentally a loop, CubePi removes the graph machinery you were not really using.