Skip to content
diagridioPublic

About

Durable execution for AI agents in Python: make LangGraph, CrewAI, Pydantic AI, OpenAI Agents, Google ADK, Strands and more fault-tolerant with Dapr Workflows on Diagrid Catalyst. Crash-proof agents, no re-run LLM calls.

Topics

Resources

Contributing

Security policy

Stars

9 stars

Watchers

1 watching

Forks

Repository files navigation

Durable Workflows for AI agents

Durable execution for AI agents and workflows.

Make your AI agents resilient to failure and outages

The diagrid package is an extension SDK for the open-source Dapr project to build durable, fault-tolerant AI agents. It integrates seamlessly with popular agent frameworks, wrapping them in Dapr Workflows to ensure agents can recover from failures, persist state across restarts, and scale effectively.

Get started with Diagrid Catalyst for free. Catalyst has a permanent free tier, with Cloud Plus for higher limits.

Community

Have questions, hit a bug, or want to share what you're building? Join the Diagrid Community Discord to connect with the team and other users.

Features

  • Multi-Framework Support: Native integrations for LangGraph, CrewAI, Google ADK, Strands, PydanticAI, OpenAI Agents, Claude Agent SDK, LangChain, Smolagents, LangChain Deep Agents, and HolmesGPT.
  • Durability: Agent state is automatically persisted in the database of your choice. If your process crashes, the agent resumes from the last successful step.
  • Fault Tolerance: Built-in retries and error handling powered by Dapr.
  • Observability: Deep insights into agent execution, tool calls, and state transitions.

Installation

Install the base package along with the extension for your chosen framework:

# For LangGraph
pip install "diagrid[langgraph]"

# For CrewAI
pip install "diagrid[crewai]"

# For Google ADK
pip install "diagrid[adk]"

# For Strands
pip install "diagrid[strands]"

# For Pydantic AI
pip install "diagrid[pydantic_ai]"

# For OpenAI Agents
pip install "diagrid[openai_agents]"

# For LangChain Deep Agents
pip install "diagrid[deepagents]"

# For Claude Agent SDK
pip install "diagrid[claude_agents]"

# For LangChain
pip install "diagrid[langchain]"

# For Smolagents
pip install "diagrid[smolagents]"

# For HolmesGPT (install in a dedicated environment — see note below)
pip install "diagrid[holmesgpt]"

Note: diagrid[holmesgpt] is intentionally not part of diagrid[all]. HolmesGPT ships strict pins on fastapi, uvicorn, cachetools, mcp, and httpx[socks] that conflict with the looser constraints used by the other agent extras. Install it in its own environment.

Usage analytics

Every agent runner reports one anonymous usage event when it is constructed, once per package per process. PyPI publishes aggregate download counts only, so this is how Diagrid sees which package versions run on which platforms. Because it is one event per process, a Kubernetes deployment produces one event per replica per restart: the numbers count process starts, not deployments or users.

What is sent: the package name and version, the diagrid-core version, operating system, architecture, Python version, the agent framework and the installed version of its library (framework, framework_version), kind=agent, whether the process points at Catalyst or at a plain Dapr sidecar (target), and whether it runs under a CI variable (ci). Nothing else: no application data, configuration, app IDs, prompts, or hostnames. The receiving service is Scarf. It derives coarse company and location information from the request IP. How Scarf handles that data is described in Scarf's privacy policy.

It never gets in the way: the request runs on a background daemon thread with a one second socket timeout, every failure is swallowed, and the calling thread never waits. Blocked egress and air-gapped clusters behave normally. Nothing is written to your application's output; a DEBUG line on the diagrid.core.analytics logger records whether the event was sent or skipped, for operators who want to confirm an opt-out.

To opt out, set any of these environment variables before starting your application:

export DO_NOT_TRACK=1
# or
export SCARF_NO_ANALYTICS=1
# or
export DIAGRID_NO_ANALYTICS=1

Verified identity

Catalyst signs the calling user's identity into an X-Diagrid-User-Token header on every inbound request. diagrid.identity verifies it before your handler runs, and puts it back on the calls your agent makes on the caller's behalf.

pip install "diagrid[identity]"

Two lines wire it up:

from diagrid.identity import OAuthConfig
from diagrid.identity.asgi import OAuthMiddleware

app.add_middleware(OAuthMiddleware, config=OAuthConfig(scopes={"agent.invoke"}))

Reading the verified caller

from fastapi import Request

from diagrid.identity.asgi import verified_user


@app.post("/invoke")
async def invoke(request: Request):
    user = verified_user(request)  # VerifiedUser | None
    return {
        "subject": user.subject,
        "tenant": user.tenant,
        "scopes": list(user.scopes),
        "admin": user.has_scope("admin.write"),
    }

VerifiedUser carries subject, tenant, scopes, claims and issuer_id, plus has_scope(scope). scopes keeps set semantics but iterates in sorted order, so a response that echoes it is stable across requests and across SDKs.

verified_user() returns None only when the request carried no token and require_auth=False allowed it through. A token that is present is always verified, and an invalid one never reaches the handler.

Outbound calls on behalf of the caller

The sidecar mints the token for the inbound request, so an outbound call has to carry it explicitly. Use the identity-aware client and it rides along:

from diagrid.identity.http import AsyncClient

client = AsyncClient()  # an httpx2.AsyncClient that sends the caller's token

The token is read from the inbound request context at send time, not baked in at construction, so one long-lived client is safe to share: concurrent requests each carry their own caller's token. The header is cleared before it is set, and it only ever travels to the origin the caller addressed — a redirect away from that origin drops it.

For a client you cannot replace, install the same behaviour as a request hook:

import httpx2

from diagrid.identity.http import attach_identity_headers_async

client = httpx2.AsyncClient(event_hooks={"request": [attach_identity_headers_async]})

attach_identity_headers is the synchronous counterpart. A call made with no inbound user context — a cron, pub/sub or scheduled trigger — proceeds unauthenticated with the header omitted rather than raising.

OAuthConfig

Field Type Default Meaning
scopes FrozenSet[str] frozenset() Scopes every caller must carry; a token short of one gets 403.
issuer Optional[str] None Expected iss. Discovered when unset.
audience Optional[str] None Expected aud. Discovered when unset.
jwks_uri Optional[str] None JWKS endpoint. Discovered when unset.
require_auth bool True Reject a request that carries no token. The default is fail-closed, and so is OAuthConfig().
allow_insecure_jwks bool False Opt in to a plaintext JWKS URI on a non-loopback host. The key set is the root of trust, so https is otherwise required; loopback is exempt because that is where the local sidecar serves. It relaxes the rule to plain http only — a file:// JWKS URI, or any other scheme, stays refused.

Discovery precedence

Coordinates come from four sources, in this order:

  1. Explicit config — whatever of issuer, audience and jwks_uri you set on OAuthConfig.
  2. The local sidecar — GET http://127.0.0.1:$DAPR_HTTP_PORT/v1.0/metadata (CATALYST_DAPR_HTTP_PORT wins if both are set). Local is tried before remote so a deployed in-cluster app keeps using the loopback call rather than a network round trip.
  3. The remote sidecar — GET $DAPR_HTTP_ENDPOINT/v1.0/metadata, which is the diagrid dev run shape: the app runs on your machine against a Catalyst-hosted sidecar, so nothing is listening on 127.0.0.1. DAPR_API_TOKEN is sent as the dapr-api-token header when it is set.
  4. Environment variables — DIAGRID_DP_SENTRY_ISSUER and DIAGRID_DP_SENTRY_AUDIENCE.

jwks_uri resolves explicit first, then the value the sidecar advertises — adopted only when the discovered issuer is the issuer being verified, so a pinned issuer is never checked against a foreign issuer's keys — and otherwise issuer + /jwks.json. If nothing resolves, a token-carrying request is answered 503 oauth.not_configured rather than let through.

Tokens are accepted for RS256 and ES256 only, must carry exp, iss and sub, are allowed 120s of clock skew, and the key set is cached for 300s.

Supplying the verifier yourself

OAuthMiddleware builds its own verifier from the coordinates above. Pass one instead — a pre-built JWKSVerifier, or any object satisfying the TokenVerifier protocol — when the app resolves coordinates its own way, or to stand a double in during a test:

app.add_middleware(OAuthMiddleware, config=OAuthConfig(), verifier=my_verifier)

The seam is on the middleware, not on OAuthConfig, which stays pure policy.

Rejections

Every rejection is {"error": "<code>"} with Cache-Control: no-store. The codes are constants on OAuthErrorCodes.

Status Code When
401 oauth.missing_token No X-Diagrid-User-Token, and require_auth=True.
401 oauth.expired exp is in the past, beyond the skew allowance.
401 oauth.invalid_issuer iss is not the expected issuer.
401 oauth.invalid_audience aud is not the expected audience.
401 oauth.invalid_signature Signature does not verify against the key set.
401 oauth.decode_error The token is malformed.
401 oauth.invalid_token Any other claim validation failure.
403 oauth.missing_scope Verified, but short of OAuthConfig.scopes.
503 oauth.not_configured No identity coordinates could be resolved.
503 oauth.verifier_unavailable Key material has not loaded yet.

Prerequisites

  • Python: 3.11 or higher

Examples & Quickstarts

Two paths to your first running agent:

Getting Started with Diagrid Catalyst

Diagrid Catalyst is a fully managed workflow engine for AI agents, built on the open-source CNCF Dapr Workflow project. It's the easiest way to test the different agentic integrations for free.

See quickstarts to get started in less than 5 minutes.

How It Works

This SDK leverages Dapr Workflows to orchestrate agent execution.

  1. Orchestration: The agent's control loop is modeled as a workflow.
  2. Activities: Each tool execution or LLM call is modeled as a durable activity.
  3. State Store: Dapr saves the workflow state to a configured state store (e.g., Redis, CosmosDB) after every step.

Your code can run anywhere (local machine, Kubernetes, EC2, etc.) while the fully managed workflow engine takes care of the agent's execution state, making it crash-proof and resilient to any outage or failure.

About

Durable execution for AI agents in Python: make LangGraph, CrewAI, Pydantic AI, OpenAI Agents, Google ADK, Strands and more fault-tolerant with Dapr Workflows on Diagrid Catalyst. Crash-proof agents, no re-run LLM calls.

Topics

Resources

Contributing

Security policy

Stars

9 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages