Skip to main content

criogen

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 criogen

Python 3.9+ · Linux (x64, ARM64) · macOS (Apple Silicon, Intel) · Windows (x64) · free to use, also commercially

Website · Docs · Quickstart · Benchmarks · npm package

Why Criogen

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:

Criogen 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 criogen 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 criogen 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. @tool builds 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=MyPydanticModel and result.output is 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 have history, rewind and delete.
  • 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. criogen.patterns has 12 ready-made designs (sequential, parallel, review loops and more), and criogen.specialists has 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. Gateway makes plain chat, stream and embedding calls with the same retries and fallbacks.

Approval before a tool runs

from criogen 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 criogen 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 criogen 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 criogen 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 criogen 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 criogen (npm).

Licence

Free to use, also commercially, under the Criogen Free Licence (see LICENSE).

Metadata

Release files for criogen 0.1.3

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Built distributions (wheels)

Table of built distributions (wheels) for criogen 0.1.3
File Actions
criogen-0.1.3-cp39-abi3-win_amd64.whl CPython 3.9 abi3 Windows x86-64 Details for criogen-0.1.3-cp39-abi3-win_amd64.whl
criogen-0.1.3-cp39-abi3-manylinux_2_28_x86_64.whl CPython 3.9 abi3 Linux glibc 2.28+ x86-64 Details for criogen-0.1.3-cp39-abi3-manylinux_2_28_x86_64.whl
criogen-0.1.3-cp39-abi3-manylinux_2_28_aarch64.whl CPython 3.9 abi3 Linux glibc 2.28+ ARM64 Details for criogen-0.1.3-cp39-abi3-manylinux_2_28_aarch64.whl
criogen-0.1.3-cp39-abi3-macosx_11_0_arm64.whl CPython 3.9 abi3 macOS 11.0+ ARM64 Details for criogen-0.1.3-cp39-abi3-macosx_11_0_arm64.whl

Total release size: 30.0 MB

Release files / criogen-0.1.3-cp39-abi3-win_amd64.whl

Download URL criogen-0.1.3-cp39-abi3-win_amd64.whl
Size 7.6 MB
Tags CPython 3.9 Windows x86-64 abi3
SHA-256 checksum
How to use checksums
b44d11cd0540ae3df95629cb0c5c9a2d773787223be642396b0e8639d5af51e0
BLAKE2b-256 checksum
How to use checksums
1af68dc86903250fae969b8876bdbe8040a1427fe52a22bb64cd0098b60e8e79
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via maturin/1.15.0

Release files / criogen-0.1.3-cp39-abi3-manylinux_2_28_x86_64.whl

Download URL criogen-0.1.3-cp39-abi3-manylinux_2_28_x86_64.whl
Size 7.9 MB
Tags CPython 3.9 Linux glibc 2.28+ x86-64 abi3
SHA-256 checksum
How to use checksums
efd1fdcaa539e4f4d246f81cf07d3752e4dd3f44aed16f9f3fc7195867251ad0
BLAKE2b-256 checksum
How to use checksums
c5183ed90950f0f9296393a8d66b1032252c45ed6aa07d28e9b5105d776b8b17
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via maturin/1.15.0

Release files / criogen-0.1.3-cp39-abi3-manylinux_2_28_aarch64.whl

Download URL criogen-0.1.3-cp39-abi3-manylinux_2_28_aarch64.whl
Size 7.4 MB
Tags CPython 3.9 Linux glibc 2.28+ ARM64 abi3
SHA-256 checksum
How to use checksums
a576fff373223393a2e0f4a772deb0549db64cff84262a2921636697235058c9
BLAKE2b-256 checksum
How to use checksums
10502ae640c764ad78f791542efe9b7fd62b5683c28e4cf0f43b9de91dd54f6b
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via maturin/1.15.0

Release files / criogen-0.1.3-cp39-abi3-macosx_11_0_arm64.whl

Download URL criogen-0.1.3-cp39-abi3-macosx_11_0_arm64.whl
Size 7.1 MB
Tags CPython 3.9 abi3 macOS 11.0+ ARM64
SHA-256 checksum
How to use checksums
b2caa8bbccf4aea25c4b527bcb12ec1b34f3d7276b2c5c7f73893098c395627d
BLAKE2b-256 checksum
How to use checksums
27bda74a917694709583573fdc73de3d716ffc2ce0028d6a9ef9fe3c19d961a6
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via maturin/1.15.0

Security for criogen 0.1.3

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.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page