Skip to main content

Pyantra

Typed, observable, reliable workflows for production AI agents.

CI PyPI Python Versions License: MIT

Pyantra is a Python framework for building AI agent workflows as typed graphs of nodes and edges. It is designed to be small, composable, and dependency-free, with reliability and observability built in by default.

Agents should be observable, reproducible, testable, reliable, and cost-aware by default.


Features

  • Typed workflows — nodes flow state through the graph and are type-checked end to end.
  • Compile-time validation — malformed graphs fail early with clear errors, not at runtime.
  • Reliability first-class — per-node retry with backoff, timeouts, and circuit breakers.
  • Checkpoints — durable snapshots that let failed runs resume where they left off.
  • Structured observability — every run produces a rich event trace.
  • LLM abstraction — a dependency-free provider interface with built-in token/cost tracking.
  • Sync + async — one traversal engine exposed through both run() and arun().
  • Zero dependencies — pure Python standard library. No databases, no services.

Installation

pip install pyantra

Requires Python 3.10 or later.


Quickstart

from dataclasses import dataclass

from pyantra import Graph


@dataclass
class State:
    value: int


graph = Graph(State)


@graph.node
def increment(state: State) -> State:
    state.value += 1
    return state


@graph.node
def double(state: State) -> State:
    state.value *= 2
    return state


graph.set_entry_point(increment)
graph.add_edge(increment, double)

app = graph.compile()

result = app.run(State(value=1))

assert result.state.value == 4

Core concepts

A workflow is a graph of nodes connected by edges. Nodes receive state and return updated state (or mutate it in place and return None). The graph is compiled — and validated — before it can be executed.

Conditional routing

@graph.node
def classify(state: State) -> State:
    return state

@graph.node
def process_positive(state: State) -> State:
    ...

@graph.node
def process_negative(state: State) -> State:
    ...

def route(state: State) -> str:
    return "positive" if state.value >= 0 else "negative"

graph.set_entry_point(classify)
graph.add_conditional_edges(
    classify,
    route,
    {"positive": process_positive, "negative": process_negative},
)

Nodes can also terminate a workflow explicitly:

from pyantra import END

graph.add_edge(final_node, END)

Async execution

The exact same graph runs asynchronously:

result = await app.arun(State(value=1))

Async nodes (async def) are supported seamlessly in both modes.


Reliability

Reliability is configured per node with NodeConfig. No configuration means fail-fast: a raised exception fails the node immediately.

from pyantra import Backoff, Graph, NodeConfig

graph = Graph(State)

@graph.node
def fetch(state: State) -> State:
    ...

fetch.config = NodeConfig(
    retries=4,                       # retries after the first attempt
    backoff=Backoff.EXPONENTIAL,     # or Backoff.FIXED / Backoff.NONE
    base_delay=1.0,
    max_delay=30.0,
    timeout=15.0,                    # seconds per attempt
)

graph.set_entry_point(fetch)

NodeConfig can also be passed directly when registering a node:

graph.add_node(fetch, name="fetch", config=NodeConfig(retries=3))

Never retry certain errors

Errors that must not be retried (bad input, schema violations, …) can be marked explicitly — they fail immediately regardless of the retry policy:

from pyantra import non_retryable

@non_retryable
class ValidationError(Exception):
    ...

def fetch(state: State) -> State:
    raise ValidationError("bad request")

Circuit breakers

A circuit breaker stops hammering a node after a run of consecutive failures, then allows a trial call once a reset period elapses:

from pyantra import CircuitBreaker, NodeConfig

breaker = CircuitBreaker(failure_threshold=5, reset_timeout=30.0)

graph.add_node(
    external_api,
    name="external_api",
    config=NodeConfig(breaker=breaker),
)

LLMs

Pyantra ships a dependency-free provider abstraction (LLM) plus Message, Usage, and LLMResponse value types. Any model adapter implements generate() / agenerate(); providers (OpenAI, Anthropic, …) can live as extras. Cost and token usage is aggregated per run with UsageTracker, and MockLLM provides scripted responses for tests.

from pyantra import Message, MockLLM, UsageTracker

llm = MockLLM(responses=["summarized"], input_tokens=3, output_tokens=2)
tracker = UsageTracker()

def summarize(state: State) -> State:
    resp = llm.generate([Message(role="user", content=state.prompt)])
    tracker.add(resp.usage)
    state.summary = resp.content
    return state

tracker.total reports aggregate input/output/cache tokens and cost. See docs/llm.md for the design and roadmap.


Checkpoints and resume

Pass a checkpoint store to run() and a run can resume from its last successful node after a failure:

from pyantra import MemoryCheckpointStore

store = MemoryCheckpointStore()

first = app.run(state, checkpointer=store, run_id="order-123")
assert first.status == RunStatus.FAILED

# Re-run with the same run_id: resumes where it stopped instead of restarting.
second = app.run(state, checkpointer=store, run_id="order-123")

CheckpointStore is an abstract interface; in-memory storage ships by default and durable backends (SQLite, Postgres, Redis) can be added behind the same API.


Observability

Every run returns a Run object with a structured event trace — no logging parsing required:

result = app.run(state)

result.run_id      # unique id for the run
result.status      # RunStatus (pending, running, completed, failed, ...)
result.state       # final (or last known) state
result.events      # ordered list of RunEvent
result.error       # human-readable failure message, when failed
result.exception   # the underlying exception, when failed

Example events:

run.started        node.started      node.attempt.failed
run.completed      node.completed    node.attempt.timeout
run.failed         node.failed       node.retrying
run.resumed        edge.selected

Errors

All exceptions derive from PyantraError:

PyantraError
├── GraphCompileError      — the graph failed validation at compile time
├── GraphExecutionError
│   ├── NodeExecutionError — a node raised during execution
│   ├── NodeTimeoutError   — a node exceeded its configured timeout
│   ├── RetryExhaustedError— retries were exhausted
│   ├── CircuitOpenError   — a circuit breaker refused execution
│   ├── InvalidRouteError  — a router returned an unknown destination
│   └── MaxIterationsError — a run exceeded max_iterations
├── CheckpointError        — checkpoint storage or resume failed
└── NonRetryableError      — base class for never-retried errors

Examples

End-to-end runnable examples live in examples/:

python examples/basic_workflow.py
python examples/reliability_workflow.py

Development

pip install -e ".[dev]"

ruff check .          # lint
mypy pyantra          # type check
pytest                # test suite

This repository uses conventional commits.


Roadmap

  • Automatic LLM usage capture with per-run budgets and compression
  • LLM caching and model tiering
  • Multi-agent delegation and scoped handoffs
  • Human-in-the-loop pause/resume
  • Deterministic replay and trace-based regression testing

License

MIT

Download files

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

Source Distribution

pyantra-0.2.0.tar.gz (25.2 kB view details)

Uploaded Source

Built Distribution

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

pyantra-0.2.0-py3-none-any.whl (26.3 kB view details)

Uploaded Python 3

File details

Details for the file pyantra-0.2.0.tar.gz.

File metadata

  • Download URL: pyantra-0.2.0.tar.gz
  • Upload date:
  • Size: 25.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for pyantra-0.2.0.tar.gz
Algorithm Hash digest
SHA256 1e178da3e42dd4388b7c62aa37cadef52406882550a12406a422c68ac8bcb10b
MD5 c30f48f530405ee5a601f9cce6f68c7a
BLAKE2b-256 73c36539ba48a3a97f2ce9987ced150b3e8efb1e21259ffe94c316cf80a3234c

See more details on using hashes here.

Provenance

The following attestation bundles were made for pyantra-0.2.0.tar.gz:

Publisher: publish.yml on Eskaykaushik/pyantra

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

File details

Details for the file pyantra-0.2.0-py3-none-any.whl.

File metadata

  • Download URL: pyantra-0.2.0-py3-none-any.whl
  • Upload date:
  • Size: 26.3 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for pyantra-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 2d44193b69bc3b5f3ecc6832f2ba88617dc4b0c1d883a7eb2dda91bc53c6699b
MD5 10462056ddb1beef9f5fcec6b3d7386d
BLAKE2b-256 6f423e7446fb7da5a51410f39f1047cf4a153780f140a7b3632a3fd0c5574bca

See more details on using hashes here.

Provenance

The following attestation bundles were made for pyantra-0.2.0-py3-none-any.whl:

Publisher: publish.yml on Eskaykaushik/pyantra

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