criogenai
The fast AI agent framework for Python. Build agents that call tools, wait for a person's approval, remember users, and survive crashes, on a Rust engine that adds almost nothing to each step. A LangChain and LangGraph alternative with one package and no dependency tree.
pip install criogenai
Python 3.9+ · Linux (x64, ARM64) · macOS (Apple Silicon, Intel) · Windows (x64) · free to use, also commercially
Website · Docs · Quickstart · Benchmarks · npm package
Why CriogenAI
The scheduling, tool-call checks, retries and saving run in compiled Rust. Your
agents, tools and workflows stay plain Python: async def functions, type hints,
Pydantic models.
Measured on one machine (AMD Ryzen 5 4600H, Windows 11, 2026-10-21), all three frameworks from Python against the same simulated model, so only framework cost is counted:
| CriogenAI | LangGraph | CrewAI | |
|---|---|---|---|
| Framework overhead per agent turn | 0.29 ms | 1.23 ms | 33.9 ms |
| 1,000 agent runs at once | 333 ms | 2.85 s | 49 s |
| Packages installed | 1 | 42 | 138 |
| Install size | 17.9 MB | 42.9 MB | 742.7 MB |
| Import time | 148 ms | 1.70 s | 9.30 s |
| Reliability tests passed with default settings | 5 of 5 | 2 of 5 | 4 of 5 |
With one agent and a real model, the model's own time dominates and the frameworks are close. The difference shows with many steps, many tool calls and many users at once. The benchmark page has every scenario and a kit to rerun them on your machine.
Quickstart
import asyncio
from criogenai import Agent, tool, ToolError
@tool
async def get_order(order_id: str) -> dict:
"""Look up an order by id."""
if order_id != "A-1":
raise ToolError(f"No order {order_id}") # the model sees this and can recover
return {"id": order_id, "status": "shipped"}
agent = Agent("openai:gpt-5-mini", tools=[get_order], storage="sqlite://support.db")
async def main():
chat = agent.thread("user-42") # a saved conversation
reply = await chat.send("Where is order A-1?")
print(reply.text)
asyncio.run(main())
Keep keys in a .env file: from criogenai import load_env; load_env() reads
OPENAI_API_KEY, ANTHROPIC_API_KEY, GEMINI_API_KEY and OPENROUTER_API_KEY. It
never prints values and never overrides a variable that is already set.
What you get
- Tools from type hints.
@toolbuilds the JSON Schema from the signature and checks every argument before your function runs. Sync tools run on a worker thread, async tools on your event loop. - Typed output.
output=MyPydanticModelandresult.outputis that model. - Human approval.
approve=["issue_refund"]pauses before the call. The request waits in the database; anyone can approve, reject or edit it later, from another process. - Durable threads. Every step is saved. After a crash,
thread.retry()continues where it stopped. Threads also havehistory,rewindanddelete. - Streaming.
thread.stream(...)yields text, tool and approval events. Leaving the loop cancels the run and still saves it. - Memory.
Memory.by_state("user_id", "users")recalls facts per user before each model call. Search by keyword, or by meaning with an embedder (OpenAI or Ollama). - Workflows. Graphs of plain functions and agents with routes, joins, loops and
pauses (
suspend) that wait for a person, for days if needed. - Multi-agent.
agent.as_tool(...)turns an agent into another agent's tool.criogenai.patternshas 12 ready-made designs (sequential, parallel, review loops and more), andcriogenai.specialistshas agent recipes you can extend. - Resilience. Retries with
Retry-After, fallback models, limits on model calls, tool calls and tokens, and tool timeouts. - Storage. SQLite or Postgres, encryption at rest (AES-256-GCM), retention rules, and a tamper-evident audit trail that records who did what, without the content.
- Observability. Hooks around model and tool calls, OpenTelemetry export and a Langfuse preset. Content is left out unless you turn it on.
- Any model. OpenAI, Anthropic, Gemini, DeepSeek, Mistral, xAI, Groq, Cerebras,
Together, Fireworks, Qwen, Kimi (Moonshot), GLM (Z.ai) and OpenRouter by name, any
OpenAI- or Anthropic-compatible server by URL, and self-hosted Ollama, vLLM or
LM Studio, so data can stay on your own servers.
Gatewaymakes plain chat, stream and embedding calls with the same retries and fallbacks.
Approval before a tool runs
from criogenai import Agent, Storage
agent = Agent(
"openai:gpt-5-mini",
tools=[issue_refund],
approve=["issue_refund"], # a person decides before it runs
storage=Storage("sqlite://support.db"),
)
chat = agent.thread("ticket-7", actor="user:ada")
reply = await chat.send("Order A-1001 arrived broken, please refund it")
if reply.needs_approval:
for request in reply.approvals:
print(request.tool, request.args)
# Hours later, in another process: the approval waited in the database.
reply = await agent.thread("ticket-7", actor="user:manager").approve()
Typed output and limits
from typing import Literal
from pydantic import BaseModel
from criogenai import Agent
class Resolution(BaseModel):
category: Literal["billing", "shipping", "other"]
refund: bool
agent = Agent(
"openai:gpt-5-mini",
instructions="You are a support agent.",
tools=[issue_refund],
output=Resolution, # result.output is a Resolution
limits={"model_calls": 10, "tool_calls": 20},
)
Streaming
stream = agent.thread("user-42").stream("Summarise my last order")
async for event in stream:
if event.type == "text":
print(event.text, end="")
elif event.type == "tool_call_started":
print(f"\n[{event.tool}…]")
reply = await stream.reply() # saved, like send()
Workflows
from criogenai import END, NodeContext, Storage, Workflow, suspend
wf = Workflow()
@wf.node
def classify(state: dict) -> dict:
return {"category": "billing" if "charge" in state["text"] else "other"}
@wf.node
def approve(state: dict, ctx: NodeContext) -> object:
if ctx.resume is None:
return suspend({"refund": state["amount"]}) # pause for a person
return {"approved": ctx.resume == "yes"}
@wf.node
def answer(state: dict) -> dict:
return {"reply": "A human will follow up."}
wf.start("classify")
wf.route("classify", lambda s: s["category"], {"billing": "approve", "other": "answer"})
wf.edge("approve", END).edge("answer", END)
app = wf.compile(storage=Storage("sqlite://app.db"))
turn = await app.thread("ticket-7").run({"text": "I was charged twice", "amount": 40})
Models
from criogenai import Agent, Anthropic, Gemini, Groq, Ollama, OpenAICompatible, Retry
Agent("openai:gpt-5-mini") # OPENAI_API_KEY
Agent(Anthropic("claude-sonnet-5-5")) # ANTHROPIC_API_KEY
Agent(Gemini("gemini-2.5-flash")) # GEMINI_API_KEY
Agent("deepseek:deepseek-chat") # DEEPSEEK_API_KEY
Agent(Groq("llama-3.3-70b-versatile")) # GROQ_API_KEY
Agent(Ollama("llama3.2")) # self-hosted
Agent(OpenAICompatible("qwen3", base_url="http://gpu-box:8000/v1")) # vLLM, LM Studio
# Rate limits, overload and outages go to the next model.
Agent("openai:gpt-5-mini", fallbacks=[Ollama("llama3.2")], model_retry=Retry(max_attempts=4))
Testing without a model
from criogenai import Agent, FakeModel, FakeReply
agent = Agent(
FakeModel([FakeReply.tool_call("get_order", {"order_id": "A-1"}), "It has shipped."]),
tools=[get_order],
)
assert agent.run_sync("Where is A-1?").text == "It has shipped."
Scripted replies, no network, the same answer every time.
Also on npm
The same engine and the same features for TypeScript and Node.js:
npm install criogenai (npm).
Licence
Free to use, also commercially, under the CriogenAI Free Licence (see LICENSE).
Metadata
Release files for criogenai 0.1.7
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| criogenai-0.1.7-cp39-abi3-win_amd64.whl | CPython 3.9 | abi3 | Windows x86-64 | Details for criogenai-0.1.7-cp39-abi3-win_amd64.whl |
Release files / criogenai-0.1.7-cp39-abi3-win_amd64.whl
| Download URL | criogenai-0.1.7-cp39-abi3-win_amd64.whl |
|---|---|
| Size | 9.0 MB |
| Tags | CPython 3.9 Windows x86-64 abi3 |
|
SHA-256 checksum How to use checksums |
3e7df0d9cf218712ab084fbb41844464ee61fe79cb617692bae02f99819328b6
|
|
BLAKE2b-256 checksum How to use checksums |
7ab332d827c915ab172434b0c2e277dfc3583616ac8e8a57991f96ba286f0298
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
maturin/1.15.0
|
Security for criogenai 0.1.7
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.