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.5-py3-none-win_amd64.whl (572.3 kB view details)

Uploaded Python 3Windows x86-64

delite_core-0.2.5-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.5-py3-none-manylinux_2_28_aarch64.whl (604.5 kB view details)

Uploaded Python 3manylinux: glibc 2.28+ ARM64

delite_core-0.2.5-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (661.2 kB view details)

Uploaded Python 3manylinux: glibc 2.17+ x86-64

delite_core-0.2.5-py3-none-macosx_11_0_arm64.whl (598.1 kB view details)

Uploaded Python 3macOS 11.0+ ARM64

delite_core-0.2.5-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.5-py3-none-win_amd64.whl.

File metadata

  • Download URL: delite_core-0.2.5-py3-none-win_amd64.whl
  • Upload date:
  • Size: 572.3 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.5-py3-none-win_amd64.whl
Algorithm Hash digest
SHA256 f1ec37f592a338294f211f3744a9e5cbf98aa09549084edb19e6deb0c0afbc8d
MD5 daf93f2d559a994be824588db6618643
BLAKE2b-256 812c2d2ac8c94b64d2373c12ba94b5f0f2a0bc13d2747b4df73ddd3b4258fe68

See more details on using hashes here.

Provenance

The following attestation bundles were made for delite_core-0.2.5-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.5-py3-none-musllinux_1_2_x86_64.whl.

File metadata

File hashes

Hashes for delite_core-0.2.5-py3-none-musllinux_1_2_x86_64.whl
Algorithm Hash digest
SHA256 dc2f29132704596cf2df0412b970cb514b0acbfe161a8649a9e811e823b5ef90
MD5 bb44baaa72903ea2fd02f62eb7f78c16
BLAKE2b-256 29b1fd11ee5249df35137dca0bccab38389b6c2e800909f3aee9eceaefc2aac7

See more details on using hashes here.

Provenance

The following attestation bundles were made for delite_core-0.2.5-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.5-py3-none-manylinux_2_28_aarch64.whl.

File metadata

File hashes

Hashes for delite_core-0.2.5-py3-none-manylinux_2_28_aarch64.whl
Algorithm Hash digest
SHA256 1698ad146fa1ed48fb5b8b7c84cb61a6afe3b695538bcb3292576b894b390fe4
MD5 c2baed05efc3edc748c252a429fe66cd
BLAKE2b-256 24904f28d5b23159e97c1addefeb982b3723082bc3aef9a3a6e3df9a50bfc661

See more details on using hashes here.

Provenance

The following attestation bundles were made for delite_core-0.2.5-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.5-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.

File metadata

File hashes

Hashes for delite_core-0.2.5-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 da486c3ca1ca00a4ff43b4fa103ded131c74aafc5bd0c34abfdf279f701a1377
MD5 abceebaf97aab291c9ffefc2cc5ac3ed
BLAKE2b-256 be6e4dc7e81039f373da4f2c5f094ebc501c16b7a97c1d12c599b91efcde8e65

See more details on using hashes here.

Provenance

The following attestation bundles were made for delite_core-0.2.5-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.5-py3-none-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for delite_core-0.2.5-py3-none-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 606454bdaf3076b14cdd32aaa3efa9912c20a9ccb32b82c547296e2554d4ed9b
MD5 0e54cd22b0b82daecda0f43820bc23a4
BLAKE2b-256 2abcff3d20f276d32606994fce7b94f1a3ece736924abbadd967fbfd4fd5d725

See more details on using hashes here.

Provenance

The following attestation bundles were made for delite_core-0.2.5-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.5-py3-none-macosx_10_12_x86_64.whl.

File metadata

File hashes

Hashes for delite_core-0.2.5-py3-none-macosx_10_12_x86_64.whl
Algorithm Hash digest
SHA256 632c692db42148802e9ca9de76c466e1a7dfdfe301ceca762563e78876e6bcaa
MD5 09e882c6a8bba5dee00aea5757822ce8
BLAKE2b-256 816df43a555787b9dc2f15bad621e0ffcd207a2d0e59f4baf47715e3b3be2693

See more details on using hashes here.

Provenance

The following attestation bundles were made for delite_core-0.2.5-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