Metadata-Version: 2.4
Name: diagrid
Version: 0.4.7
Summary: Diagrid namespace package
Requires-Python: <3.14,>=3.11
Description-Content-Type: text/markdown
License-File: LICENSE.md
Requires-Dist: dapr>=1.17.3
Requires-Dist: dapr-ext-workflow>=1.17.3
Requires-Dist: diagrid-cli>=0.1.0
Provides-Extra: agent-core
Requires-Dist: dapr-agents; extra == "agent-core"
Requires-Dist: dapr-ext-langgraph>=1.17.3; extra == "agent-core"
Requires-Dist: dapr-ext-strands>=1.17.3; extra == "agent-core"
Requires-Dist: fastapi>=0.129.0; extra == "agent-core"
Requires-Dist: uvicorn>=0.41.0; extra == "agent-core"
Requires-Dist: pydantic<2.14,>=2.12.5; extra == "agent-core"
Requires-Dist: langgraph>=1.1.10; extra == "agent-core"
Provides-Extra: langgraph
Requires-Dist: langgraph>=1.1.10; extra == "langgraph"
Requires-Dist: langchain-core>=1.3.2; extra == "langgraph"
Requires-Dist: diagrid[agent-core]; extra == "langgraph"
Provides-Extra: crewai
Requires-Dist: crewai<1.16,>=1.6.1; extra == "crewai"
Requires-Dist: litellm!=1.82.7,!=1.82.8,>=1.0.0; extra == "crewai"
Requires-Dist: diagrid[agent-core]; extra == "crewai"
Provides-Extra: adk
Requires-Dist: google-adk<3,>=1.32.0; extra == "adk"
Requires-Dist: diagrid[agent-core]; extra == "adk"
Provides-Extra: strands
Requires-Dist: openai>=2.33.0; extra == "strands"
Requires-Dist: strands-agents>=1.38.0; extra == "strands"
Requires-Dist: diagrid[agent-core]; extra == "strands"
Provides-Extra: openai-agents
Requires-Dist: openai-agents>=0.15.1; extra == "openai-agents"
Requires-Dist: openai>=2.33.0; extra == "openai-agents"
Requires-Dist: diagrid[agent-core]; extra == "openai-agents"
Provides-Extra: claude-agents
Requires-Dist: claude-agent-sdk>=0.1.45; extra == "claude-agents"
Requires-Dist: anthropic>=0.40.0; extra == "claude-agents"
Requires-Dist: diagrid[agent-core]; extra == "claude-agents"
Provides-Extra: pydantic-ai
Requires-Dist: pydantic-ai-slim[openai]>=1.89.1; extra == "pydantic-ai"
Requires-Dist: diagrid[agent-core]; extra == "pydantic-ai"
Provides-Extra: deepagents
Requires-Dist: deepagents>=0.5.6; extra == "deepagents"
Requires-Dist: diagrid[langgraph]; extra == "deepagents"
Requires-Dist: langchain-openai>=1.2.1; extra == "deepagents"
Provides-Extra: langchain
Requires-Dist: langchain-core>=1.3.2; extra == "langchain"
Requires-Dist: langchain-openai>=1.2.1; extra == "langchain"
Requires-Dist: diagrid[agent-core]; extra == "langchain"
Provides-Extra: smolagents
Requires-Dist: smolagents>=1.26.0; extra == "smolagents"
Requires-Dist: diagrid[agent-core]; extra == "smolagents"
Provides-Extra: identity
Requires-Dist: pyjwt[crypto]>=2.13; extra == "identity"
Requires-Dist: httpx2>=2.12; extra == "identity"
Requires-Dist: starlette>=1.3; extra == "identity"
Provides-Extra: holmesgpt
Requires-Dist: holmesgpt>=0.26.0; extra == "holmesgpt"
Requires-Dist: diagrid[agent-core,identity]; extra == "holmesgpt"
Provides-Extra: all
Requires-Dist: diagrid[adk,claude_agents,crewai,deepagents,identity,langchain,langgraph,openai_agents,pydantic_ai,smolagents,strands]; extra == "all"
Dynamic: license-file

# Durable Workflows for AI agents

**Make your AI agents resilient to failure and outages**

The `diagrid` package is an extension SDK for the open-source [Dapr](https://github.com/dapr/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](https://diagrid.ws/get-catalyst).

## Community

Have questions, hit a bug, or want to share what you're building? Join the [Diagrid Community Discord](https://diagrid.ws/diagrid-community) 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:

```bash
# 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.

## 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.

```bash
pip install "diagrid[identity]"
```

Two lines wire it up:

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

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

### Reading the verified caller

```python
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:

```python
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:

```python
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:

```python
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:

- **Managed quickstart templates** — `diagridpy init my-project --framework langgraph` bootstraps a Catalyst project, local kind cluster, Helm chart, and framework template in one command. Templates are cloned from [`diagridio/catalyst-quickstarts`](https://github.com/diagridio/catalyst-quickstarts).
- **In-repo code samples** — clone this repo and run any framework's example directly. See [`examples/`](examples/) for the index, or jump straight to a framework: [`langgraph`](examples/langgraph/), [`crewai`](examples/crewai/), [`adk`](examples/adk/), [`strands`](examples/strands/), [`openai_agents`](examples/openai_agents/), [`claude_agents`](examples/claude_agents/), [`pydantic_ai`](examples/pydantic_ai/), [`deepagents`](examples/deepagents/), [`langchain`](examples/langchain/), [`smolagents`](examples/smolagents/), [`holmesgpt`](examples/holmesgpt/).

## 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](https://docs.diagrid.io/getting-started/quickstarts/ai-agents/) to get started in less than 5 minutes.

## How It Works

This SDK leverages [Dapr Workflows](https://docs.dapr.io/developing-applications/building-blocks/workflow/) 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.
