Skip to main content

The SQLite of durable agent execution — crash-recoverable AI agents with exactly-once semantics. Zero dependencies.

Project description

delite

The SQLite of durable agent execution. Crash-recoverable AI agents with exactly-once semantics.

Install

Binary

brew install benelser/tap/delite          # macOS
cargo install delite-core                  # Rust toolchain
pip install delite-core                    # pip
npm install @bjelser/delite-core           # npm
curl -sSL https://raw.githubusercontent.com/benelser/durable/main/install.sh | sh

Python SDK

pip install durable                        # pip
uv add durable                             # uv
poetry add durable                         # poetry

TypeScript SDK (coming soon)

npm install durable                        # npm
bun add durable                            # bun

Set your LLM provider key:

export OPENAI_API_KEY=sk-...
# or
export ANTHROPIC_API_KEY=sk-ant-...

Quick Start

from durable import Agent, tool
from durable.providers import OpenAI

@tool("get_weather", description="Get weather for a location")
def get_weather(location: str) -> dict:
    return {"temp": 72, "conditions": "sunny", "location": location}

with Agent("./data") as agent:
    agent.add_tool(get_weather)
    agent.set_llm(OpenAI())
    response = agent.run("What's the weather in San Francisco?")
    print(response)

What It Does

Every LLM call and tool execution is a durable step. Results are persisted to an append-only event log before the next step begins. If the process crashes — between the payment charge and the confirmation email, between retry 3 and retry 4, between hour 1 and hour 47 — execution resumes exactly where it left off.

Without delite:
  charge_payment()  →  crash  →  restart  →  charge_payment() again
  Result: customer charged twice ($299.94)

With delite:
  charge_payment()  →  crash  →  restart  →  cached result returned
  Result: customer charged once ($149.97)

Crash Recovery

# First run — everything executes and is persisted
response = agent.run("Process order #123")
execution_id = response.execution_id

# Process crashes. On restart, pass the same execution_id:
response = agent.run("Process order #123", execution_id=execution_id)
# All completed steps return cached results. No re-execution.

Human-in-the-Loop

Mark any tool as requiring human approval before execution:

@tool("transfer_funds", description="Transfer money", requires_confirmation=True)
def transfer_funds(from_acct: str, to_acct: str, amount: float) -> dict:
    return {"status": "transferred", "amount": amount}

response = agent.run("Transfer $5000 from checking to savings")
# response.is_suspended == True
# The tool has NOT executed. Human reviews and approves:
agent.approve(response.execution_id, response.suspend_reason.confirmation_id)
response = agent.resume(response.execution_id)
# Now the transfer executes

Contracts

Contracts are checks that run before a tool executes. They are code, not prompt engineering — the LLM cannot circumvent them.

@agent.contract("max-charge")
def check_charge(step_name, args):
    if "charge" in step_name and args.get("amount", 0) > 1000:
        raise ValueError("Charges over $1000 need VP approval")

response = agent.run("Charge $5000")
# response.is_suspended == True — the tool never executed

Budget Limits

from durable import Budget

agent.budget = Budget(max_dollars=2.00, max_llm_calls=10)
response = agent.run("Research this topic thoroughly")

if response.is_suspended:
    print(f"Budget exhausted: {response.suspend_reason}")
    # All completed work is preserved. Increase budget and resume.

Multi-Agent Runtime

One runtime, N agents, running as durable threads inside your process:

from durable import Runtime, Agent

rt = Runtime("./data")
researcher = Agent("./data", runtime=rt, agent_id="researcher", ...)
writer = Agent("./data", runtime=rt, agent_id="writer", ...)

# Non-blocking spawn
rt.go(researcher, "Research the topic")
rt.go(writer, "Write the report")

# Lifecycle callbacks
@rt.on_complete
def done(agent_id, exec_id, response):
    print(f"{agent_id} finished")

@rt.on_suspend
def paused(agent_id, exec_id, reason):
    send_slack_notification(f"Approval needed: {reason}")

# Signals trigger auto-resume
rt.signal(exec_id, confirmation_id, True)

Idempotency Keys

Every tool callback includes a unique idempotency key. Forward it to payment providers to prevent double-charges:

from durable.agent import current_idempotency_key

@tool("charge", description="Charge payment")
def charge(customer_id: str, amount: float) -> dict:
    stripe.PaymentIntent.create(
        amount=int(amount * 100),
        customer=customer_id,
        idempotency_key=current_idempotency_key(),
    )
    return {"status": "charged"}

LLM Providers

Provider Python Env Variable
OpenAI from durable.providers import OpenAI OPENAI_API_KEY
Anthropic from durable.providers import Anthropic ANTHROPIC_API_KEY
Custom Any callable (messages, tools, model) -> dict

Streaming

for chunk in agent.stream("Tell me a story"):
    print(chunk, end="", flush=True)

CLI

Inspect any execution after it runs:

delite status --data-dir ./data                    # list all executions
delite inspect <execution-id> --data-dir ./data    # detailed view
delite steps <execution-id> --data-dir ./data      # step timeline
delite events <execution-id> --data-dir ./data     # raw event log
delite export <execution-id> --data-dir ./data     # JSON export
delite health --data-dir ./data                    # storage health

How delite Compares

vs LangGraph

LangGraph has checkpointing (save/restore graph state via SQLite, Postgres, etc). This gives you conversation resume and human-in-the-loop interrupts.

What it does NOT give you: exactly-once tool execution. LangGraph checkpoints at node boundaries, not within nodes. If a tool executes and the process crashes before the next checkpoint, the tool re-executes on resume.

delite persists every step individually before the next begins. Prompt and tool drift are detected on resume.

vs Temporal

Temporal is the gold standard: multi-machine clusters, workflow versioning, visibility UI, battle-tested in production. It requires a Temporal Server cluster, separate worker processes, and an infrastructure team.

delite is for teams that want those guarantees without operating distributed infrastructure. One process, one binary, files on disk. If you outgrow single-machine, Temporal is the right next step.

Architecture

┌─────────────────────────────────────┐
│  Your code (Python, TypeScript)     │
├─────────────────────────────────────┤
│  SDK (zero dependencies)            │
├─────────────────────────────────────┤
│  Rust Engine (invisible subprocess) │
├─────────────────────────────────────┤
│  Event Log (append-only files)      │
└─────────────────────────────────────┘

Single-process. The Rust engine runs as an invisible subprocess. The LLM API is the bottleneck, not the runtime — a single machine handles more concurrent agents than most teams can afford in API costs.

Zero Dependencies

The Rust engine uses only the standard library. The Python SDK uses only stdlib (json, subprocess, urllib, threading). No transitive dependency hell.

License

MIT

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distributions

No source distribution files available for this release.See tutorial on generating distribution archives.

Built Distributions

If you're not sure about the file name format, learn more about wheel file names.

delite_core-0.2.4-py3-none-win_amd64.whl (570.9 kB view details)

Uploaded Python 3Windows x86-64

delite_core-0.2.4-py3-none-musllinux_1_2_x86_64.whl (706.4 kB view details)

Uploaded Python 3musllinux: musl 1.2+ x86-64

delite_core-0.2.4-py3-none-manylinux_2_28_aarch64.whl (604.5 kB view details)

Uploaded Python 3manylinux: glibc 2.28+ ARM64

delite_core-0.2.4-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (659.6 kB view details)

Uploaded Python 3manylinux: glibc 2.17+ x86-64

delite_core-0.2.4-py3-none-macosx_11_0_arm64.whl (598.0 kB view details)

Uploaded Python 3macOS 11.0+ ARM64

delite_core-0.2.4-py3-none-macosx_10_12_x86_64.whl (640.6 kB view details)

Uploaded Python 3macOS 10.12+ x86-64

File details

Details for the file delite_core-0.2.4-py3-none-win_amd64.whl.

File metadata

  • Download URL: delite_core-0.2.4-py3-none-win_amd64.whl
  • Upload date:
  • Size: 570.9 kB
  • Tags: Python 3, Windows x86-64
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for delite_core-0.2.4-py3-none-win_amd64.whl
Algorithm Hash digest
SHA256 a69b3e077439a15fa515745fca66b9e58b72f8f04284818a7afea3a4e44e36cf
MD5 a3fdef3bff8b53af3c8305bc36b9e524
BLAKE2b-256 6f6853f8082670820160bb1e2a210d65b21e1328b99bec2236615603fb0bc3b6

See more details on using hashes here.

Provenance

The following attestation bundles were made for delite_core-0.2.4-py3-none-win_amd64.whl:

Publisher: release.yml on benelser/durable

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file delite_core-0.2.4-py3-none-musllinux_1_2_x86_64.whl.

File metadata

File hashes

Hashes for delite_core-0.2.4-py3-none-musllinux_1_2_x86_64.whl
Algorithm Hash digest
SHA256 445a97f04b3b6f8aa7dce424a4647c89d8910b9af7c57684bca24fdbcecac094
MD5 0a4f48f565fc6c9c7d2bf20877fc14e9
BLAKE2b-256 a424884fac155904ca6beff59048d98eb24b2a13e8c96a02b92ed1885b7b8248

See more details on using hashes here.

Provenance

The following attestation bundles were made for delite_core-0.2.4-py3-none-musllinux_1_2_x86_64.whl:

Publisher: release.yml on benelser/durable

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file delite_core-0.2.4-py3-none-manylinux_2_28_aarch64.whl.

File metadata

File hashes

Hashes for delite_core-0.2.4-py3-none-manylinux_2_28_aarch64.whl
Algorithm Hash digest
SHA256 17a8b8b151da790e4f366fb64b9a2d0fa8d23217fcc4581d884a38b7e4fd749f
MD5 59cb57ece41ee08f7208633a654919cf
BLAKE2b-256 e69ae522b760399b2921af04280dd99fff4fed7506648a9a5f79c4bbbe429ea3

See more details on using hashes here.

Provenance

The following attestation bundles were made for delite_core-0.2.4-py3-none-manylinux_2_28_aarch64.whl:

Publisher: release.yml on benelser/durable

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file delite_core-0.2.4-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.

File metadata

File hashes

Hashes for delite_core-0.2.4-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 cc2b07a491e8574ec4538920392a11ec6d682439604434761cc60cf7d1f91cec
MD5 bf35f01fe1abc705be490cfa465006d5
BLAKE2b-256 9faafe7a88bd934a89008e03ab0a7538a75b50150d67a0e82e9af722a1f5b87e

See more details on using hashes here.

Provenance

The following attestation bundles were made for delite_core-0.2.4-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl:

Publisher: release.yml on benelser/durable

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file delite_core-0.2.4-py3-none-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for delite_core-0.2.4-py3-none-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 8e56a288bf8df1f2d0a756b6b4be2fbb979c306490c4e570637b8989949e7bd6
MD5 7359d4e05a9afd1bd1bdf61990c08c56
BLAKE2b-256 27197f271aa61ed9e80b31de8dc565e0197aae326e67ddf79c642d91787de319

See more details on using hashes here.

Provenance

The following attestation bundles were made for delite_core-0.2.4-py3-none-macosx_11_0_arm64.whl:

Publisher: release.yml on benelser/durable

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file delite_core-0.2.4-py3-none-macosx_10_12_x86_64.whl.

File metadata

File hashes

Hashes for delite_core-0.2.4-py3-none-macosx_10_12_x86_64.whl
Algorithm Hash digest
SHA256 8943746d60c8bf21bf50f9e92b55df08b1861019d1b3f4a1d4e45b4433db216d
MD5 94d7291b84a4d822c1bbb11e127eae85
BLAKE2b-256 bbdcfb9f4c95efd0e099cb7a7ec4273443ad523099b90515927085d2922bacc3

See more details on using hashes here.

Provenance

The following attestation bundles were made for delite_core-0.2.4-py3-none-macosx_10_12_x86_64.whl:

Publisher: release.yml on benelser/durable

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page