# AI Agents

Agent frameworks like CrewAI, LangGraph, and others give you tools, LLM orchestration, and prompt management — but none of them handle what happens when things go wrong in production. Some offer basic checkpointing, but you still need to detect failures at scale, build your own recovery mechanisms, and coordinate resumption across instances to avoid duplicate runs.

Catalyst adds the missing infrastructure: **automatic failure detection**, **automatic recovery at scale**, and **multi-instance agent coordination**. Your agent code stays the same — Catalyst handles the rest.

Catalyst works with the following agent frameworks.

**Supported agent frameworks:** Dapr Agents, CrewAI, LangGraph, Strands, Microsoft Agent Framework, Spring AI, Google ADK, OpenAI Agents, Pydantic AI, Deep Agents, Deep Agents (Sub Agents), Claude Agents, HolmesGPT, Mastra

:::note
[Catalyst Cloud](https://docs.diagrid.io/operate/hosting/catalyst-cloud) is free and the fastest way to get started — no infrastructure to set up. For production or on-premises requirements, Diagrid also offers [self-hosted enterprise deployments](https://docs.diagrid.io/operate/hosting/enterprise-self-hosted).
:::

## Prerequisites

- [Diagrid Catalyst account](https://catalyst.diagrid.io/)
- [Diagrid CLI](https://docs.diagrid.io/getting-started/install-cli)
- [Python 3.12 or 3.13](https://www.python.org/downloads/), or [.NET 10 SDK](https://dotnet.microsoft.com/download/dotnet/10.0) for the Microsoft Agent Framework tab, or [JDK 21](https://adoptium.net/) and [Maven 3.9+](https://maven.apache.org/download.cgi) for the Spring AI tab, or [Node.js 22.13 or newer](https://nodejs.org/en/) for the Mastra tab
- [uv](https://docs.astral.sh/uv/getting-started/installation/) for the Python-based quickstarts
- [An OpenAI API key](https://platform.openai.com/api-keys) (or [an Anthropic API key](https://console.anthropic.com/settings/keys) for the Claude Agents tab). Not needed for the LangGraph, Microsoft Agent Framework, or Spring AI tabs — those run offline by default.

## 1. Log in to Catalyst

```bash
diagrid login
```

Confirm your identity:

```bash
diagrid whoami
```

## 2. Clone and Navigate

```bash
git clone https://github.com/diagridio/catalyst-quickstarts.git
```

Navigate to the quickstart directory for your framework:

**Dapr Agents**

```bash
cd catalyst-quickstarts/agents/dapr-agents/durable-agent
```

**CrewAI**

```bash
cd catalyst-quickstarts/agents/crewai
```

**LangGraph**

```bash
cd catalyst-quickstarts/agents/langgraph
```

**Strands**

```bash
cd catalyst-quickstarts/agents/strands
```

**OpenAI Agents**

```bash
cd catalyst-quickstarts/agents/openai-agents
```

**Google ADK**

```bash
cd catalyst-quickstarts/agents/adk
```

**Pydantic AI**

```bash
cd catalyst-quickstarts/agents/pydantic-ai
```

**Microsoft Agent Framework (.NET)**

```bash
cd catalyst-quickstarts/agents/microsoft-dotnet
```

**Spring AI**

```bash
cd catalyst-quickstarts/agents/spring-ai/event-planner
```

**Deep Agents**

```bash
cd catalyst-quickstarts/agents/deepagents
```

**Deep Agents (Sub Agents)**

```bash
cd catalyst-quickstarts/agents/deepagents
```

**Claude Agents**

```bash
cd catalyst-quickstarts/agents/claude-agents
```

**HolmesGPT**

HolmesGPT has no directory in `catalyst-quickstarts` yet. Use the runnable examples in the `python-ai` repository instead:

```bash
git clone https://github.com/diagridio/python-ai.git
cd python-ai/examples/holmesgpt
```

**Mastra**

```bash
cd catalyst-quickstarts/agents/mastra
```

## 3. Explore the Code

**Dapr Agents**

**Invitations Manager** — a durable agent that sends event invitations to guests via email and physical mail. Dapr Agents is the native AI agent framework built on Dapr — durability, state, and pub/sub are built into the agent itself.

Open `main.py`. The agent uses Pydantic models for structured tool input and output:

```python
from typing import List
from pydantic import BaseModel, Field
from dapr_agents import tool, DurableAgent
from dapr_agents.llm import DaprChatClient

class InvitationResult(BaseModel):
    sent: int = Field(description="Number of invitations sent")
    method: str = Field(description="Delivery method")

class InvitationSchema(BaseModel):
    guest_count: int = Field(description="Number of guests to invite")
    event_type: str = Field(description="Type of event")

@tool(args_model=InvitationSchema)
def send_invitations(guest_count: int, event_type: str) -> List[InvitationResult]:
    """Send event invitations to guests."""
    return [
        InvitationResult(sent=int(guest_count * 0.7), method="email"),
        InvitationResult(sent=int(guest_count * 0.3), method="physical mail"),
    ]
```

The `DurableAgent` class brings everything together — memory, state, registry, and pub/sub are all configured at the agent level:

```python
agent = DurableAgent(
    name="invitations-manager",
    role="Invitations Manager",
    goal="Send event invitations to guests using the send_invitations tool. Report how many were sent by email and physical mail.",
    instructions=[
        "When asked to send invitations, use the send_invitations tool with the guest count and event type.",
        "Always report back the exact number of invitations sent and via which delivery method.",
    ],
    tools=[send_invitations],
    llm=DaprChatClient(component_name="llm-provider"),
    memory=AgentMemoryConfig(
        store=ConversationDaprStateMemory(store_name="kvstore")
    ),
    state=AgentStateConfig(
        store=StateStoreService(store_name="kvstore", key_prefix="invitations-manager:"),
    ),
    registry=AgentRegistryConfig(
        store=StateStoreService(store_name="agent-registry"),
    ),
    pubsub=AgentPubSubConfig(
        pubsub_name="pubsub",
        agent_topic="events.invitations.requests",
        broadcast_topic="agents.broadcast",
    ),
)

runner = AgentRunner()
runner.serve(agent, port=int(os.environ.get("APP_PORT", "8006")))
```

:::tip
Unlike other frameworks, Dapr Agents has durability, state, pub/sub, and failure recovery built in natively. Automatic failure detection, crash recovery, and multi-instance coordination are all handled out of the box — no wrapper needed.
:::

**CrewAI**

**Crash Recovery Demo** — a 3-step pipeline that demonstrates how Catalyst recovers from a mid-execution crash.

Open `crash_test.py`. It defines three tools that the agent calls in sequence — step 2 deliberately crashes the process:

```python
from crewai import Agent, Task
from crewai.tools import tool
from diagrid.agent.crewai import DaprWorkflowAgentRunner

@tool("Step 1 - Search venues")
def step_one_search(city: str) -> str:
    """Search for event venues in a city. This is the first step."""
    return f"Found 3 venues in {city}. Now call step_two___compare_venues."

@tool("Step 2 - Compare venues")
def step_two_compare(data: str) -> str:
    """Compare the venue options. This is the second step."""
    os._exit(1)  # 💥 Simulates a crash — comment out this line before the second run
    return "Grand Ballroom is the best option. Now call step_three___confirm_booking."

@tool("Step 3 - Confirm booking")
def step_three_confirm(selection: str) -> str:
    """Confirm the venue booking. This is the third and final step."""
    return "Booking confirmed for Grand Ballroom. All steps complete!"
```

The `DaprWorkflowAgentRunner` wraps the standard CrewAI agent — each tool call becomes a durable Dapr workflow activity:

```python
runner = DaprWorkflowAgentRunner(
    name="venue-scout",
    agent=agent,
    max_iterations=10,
)
```

:::tip
CrewAI gives you multi-agent crews and tool orchestration but has no built-in durability. The `DaprWorkflowAgentRunner` wraps your existing agent — no code changes needed — and Catalyst adds automatic failure detection, crash recovery, and multi-instance coordination.
:::

**LangGraph**

**Crash Recovery Demo** — a 3-node graph that demonstrates how Catalyst recovers from a mid-execution crash.

Open `crash_test.py`. It defines a 3-node `StateGraph` whose middle node deliberately takes about 30 seconds. That delay is the window the crash lands in: the app kills itself partway through it, so there is no source edit and nothing to time by hand:

```python
from langgraph.graph import StateGraph, START, END
from diagrid.agent.langgraph import DaprWorkflowGraphRunner

def check_venues(state: PlannerState) -> dict:
    result = "Grand Ballroom available on March 15 (2PM-6PM, 6PM-11PM)"
    return {"results": state["results"] + [result]}

def compare_options(state: PlannerState) -> dict:
    time.sleep(int(os.environ.get("CRASH_DELAY_SECONDS", "30")))  # the window the crash lands in
    result = "Grand Ballroom (6PM-11PM) is the best option for 200 guests"
    return {"results": state["results"] + [result]}

def confirm_booking(state: PlannerState) -> dict:
    result = "Booking confirmed: Grand Ballroom, March 15, 6PM-11PM"
    return {"results": state["results"] + [result]}

graph = StateGraph(PlannerState)
graph.add_node("check_venues", check_venues)
graph.add_node("compare_options", compare_options)
graph.add_node("confirm_booking", confirm_booking)
graph.add_edge(START, "check_venues")
graph.add_edge("check_venues", "compare_options")
graph.add_edge("compare_options", "confirm_booking")
graph.add_edge("confirm_booking", END)
```

The `DaprWorkflowGraphRunner` wraps the compiled graph — each node becomes a durable Dapr workflow activity:

```python
runner = DaprWorkflowGraphRunner(
    graph=graph.compile(),
    name="schedule-planner",
)
```

The node order is the design. `check_venues` completes and Catalyst records its result before `compare_options` starts, so the crash lands between two known points and the restart can show that only the interrupted node ran again. The app also exposes `POST /crash/run`, which takes the workflow instance ID you want the run to use and an optional `kill_after_seconds` telling the application when to kill itself.

:::tip
LangGraph gives you graph-based orchestration with conditional routing but has no built-in durability. The `DaprWorkflowGraphRunner` wraps your compiled graph — no code changes needed — and Catalyst adds automatic failure detection, crash recovery, and multi-instance coordination.
:::

**Strands**

**Crash Recovery Demo** — a 3-step pipeline that demonstrates how Catalyst recovers from a mid-execution crash.

Open `crash_test.py`. It defines three tools that the agent calls in sequence — step 2 deliberately crashes the process:

```python
from strands import Agent, tool
from strands.models.openai import OpenAIModel
from diagrid.agent.strands import DaprWorkflowAgentRunner

@tool
def step_one_calculate(items: str) -> str:
    """Calculate initial budget from cost items. This is the first step."""
    return "Estimated budget: $8,550. Now call step_two_analyze."

@tool
def step_two_analyze(data: str) -> str:
    """Analyze the budget for cost savings. This is the second step."""
    os._exit(1)  # 💥 Simulates a crash — comment out this line before the second run
    return "Found $1,200 in potential savings. Now call step_three_finalize."

@tool
def step_three_finalize(analysis: str) -> str:
    """Finalize the budget report. This is the third and final step."""
    return "Final budget: $7,350 (saved $1,200). All steps complete!"
```

The `DaprWorkflowAgentRunner` wraps the standard Strands agent — each tool call becomes a durable Dapr workflow activity:

```python
runner = DaprWorkflowAgentRunner(
    name="budget-planner",
    agent=agent,
    max_iterations=10,
)
```

:::tip
Strands gives you a model-driven agent framework with tool use but has no built-in durability. The `DaprWorkflowAgentRunner` wraps your existing agent — no code changes needed — and Catalyst adds automatic failure detection, crash recovery, and multi-instance coordination.
:::

**OpenAI Agents**

**Crash Recovery Demo** — a 3-step pipeline that demonstrates how Catalyst recovers from a mid-execution crash.

Open `crash_test.py`. It defines three tools that the agent calls in sequence — step 2 deliberately crashes the process:

```python
from agents import Agent, function_tool
from diagrid.agent.openai_agents import DaprWorkflowAgentRunner

@function_tool
def step_one_search(cuisine: str) -> str:
    """Search for catering options. This is the first step."""
    return f"Found 3 {cuisine} catering options. Now call step_two_compare."

@function_tool
def step_two_compare(data: str) -> str:
    """Compare catering options. This is the second step."""
    os._exit(1)  # 💥 Simulates a crash — comment out this line before the second run
    return "Farm Fresh Events is the best value. Now call step_three_confirm."

@function_tool
def step_three_confirm(selection: str) -> str:
    """Confirm the catering selection. This is the third and final step."""
    return "Catering confirmed with Farm Fresh Events. All steps complete!"
```

The `DaprWorkflowAgentRunner` wraps the standard OpenAI Agents agent — each tool call becomes a durable Dapr workflow activity:

```python
runner = DaprWorkflowAgentRunner(
    name="catering-coordinator",
    agent=agent,
    max_iterations=10,
)
```

:::tip
The OpenAI Agents SDK gives you function tools and agent handoffs but has no built-in durability. The `DaprWorkflowAgentRunner` wraps your existing agent — no code changes needed — and Catalyst adds automatic failure detection, crash recovery, and multi-instance coordination.
:::

**Google ADK**

**Crash Recovery Demo** — a 3-step pipeline that demonstrates how Catalyst recovers from a mid-execution crash.

Open `crash_test.py`. It defines three tools that the agent calls in sequence — step 2 deliberately crashes the process:

```python
from google.adk.agents import LlmAgent
from google.adk.models.lite_llm import LiteLlm
from google.adk.tools import FunctionTool
from diagrid.agent.adk import DaprWorkflowAgentRunner

def step_one_find(event_type: str) -> str:
    """Find entertainment options. This is the first step."""
    return f"Found 3 entertainment options for {event_type}. Now call step_two_compare."

def step_two_compare(data: str) -> str:
    """Compare entertainment options. This is the second step."""
    os._exit(1)  # 💥 Simulates a crash — comment out this line before the second run
    return "Live Jazz Band is the best option. Now call step_three_confirm."

def step_three_confirm(selection: str) -> str:
    """Confirm the entertainment booking. This is the third and final step."""
    return "Entertainment confirmed with Live Jazz Band. All steps complete!"
```

The `DaprWorkflowAgentRunner` wraps the standard Google ADK agent — each tool call becomes a durable Dapr workflow activity:

```python
runner = DaprWorkflowAgentRunner(
    name="entertainment-planner",
    agent=agent,
    max_iterations=10,
)
```

:::tip
Google ADK gives you a comprehensive agent development kit with Gemini integration but has no built-in durability. The `DaprWorkflowAgentRunner` wraps your existing agent — no code changes needed — and Catalyst adds automatic failure detection, crash recovery, and multi-instance coordination.
:::

**Pydantic AI**

**Crash Recovery Demo** — a 3-step pipeline that demonstrates how Catalyst recovers from a mid-execution crash.

Open `crash_test.py`. It defines three tools that the agent calls in sequence — step 2 deliberately crashes the process:

```python
from pydantic_ai import Agent
from diagrid.agent.pydantic_ai import DaprWorkflowAgentRunner

def step_one_search(theme: str) -> str:
    """Search for decoration packages. This is the first step."""
    return f"Found 3 decoration packages for {theme}. Now call step_two_compare."

def step_two_compare(data: str) -> str:
    """Compare decoration packages. This is the second step."""
    os._exit(1)  # 💥 Simulates a crash — comment out this line before the second run
    return "Elegant Events Decor is the best value. Now call step_three_confirm."

def step_three_confirm(selection: str) -> str:
    """Confirm the decoration selection. This is the third and final step."""
    return "Decorations confirmed with Elegant Events Decor. All steps complete!"
```

The `DaprWorkflowAgentRunner` wraps the standard Pydantic AI agent — each tool call becomes a durable Dapr workflow activity:

```python
runner = DaprWorkflowAgentRunner(
    name="decoration-planner",
    agent=agent,
    max_iterations=10,
)
```

:::tip
Pydantic AI gives you a type-safe agent framework with structured outputs but has no built-in durability. The `DaprWorkflowAgentRunner` wraps your existing agent — no code changes needed — and Catalyst adds automatic failure detection, crash recovery, and multi-instance coordination.
:::

**Microsoft Agent Framework (.NET)**

**Crash Recovery Demo** — a 3-tool agent pipeline that demonstrates how Catalyst recovers from a mid-execution crash.

Open `Program.cs`. It defines three tools that the agent calls in sequence. Tool 2 takes about 30 seconds, and that delay is the window the crash lands in: when a request sends `kill_after_seconds`, tool 2 arms a timer as it starts and the app kills *itself* at a known point inside its own delay.

```csharp
using Dapr.Workflow;
using Diagrid.AI.Microsoft.AgentFramework.Abstractions;
using Diagrid.AI.Microsoft.AgentFramework.Catalyst;
using Diagrid.AI.Microsoft.AgentFramework.Hosting;
using Microsoft.Extensions.AI;
using OpenAI;

// How long step_two_compare takes. This is the window the crash lands in: three
// instantaneous tools would give you nothing to interrupt.
var delaySeconds = int.TryParse(Environment.GetEnvironmentVariable("CRASH_DELAY_SECONDS"), out var seconds)
    ? seconds
    : 30;

var tools = new List<AITool>
{
    AIFunctionFactory.Create((string city) =>
    {
        Console.WriteLine($">>> TOOL 1: Searching venues in '{city}'...");
        Console.WriteLine(">>> TOOL 1 COMPLETE: Found 3 venues");
        return $"Found 3 venues in {city}. Now call step_two_compare.";
    }, "step_one_search", "Search for event venues in a city. This is the first step."),

    AIFunctionFactory.Create(async (string data) =>
    {
        // Read AND clear in one step, so one recorded request arms exactly one execution.
        var armed = SelfKill.Consume();
        Console.WriteLine(armed > 0
            ? $">>> TOOL 2: Comparing venues over ~{delaySeconds}s, but this process kills itself"
                + $" {armed}s into the run, as asked by kill_after_seconds. It resumes on restart."
            : $">>> TOOL 2: Comparing venues over ~{delaySeconds}s. KILL THE APP NOW to"
                + " test crash recovery (POST /crash/kill, or kill -9). It resumes on restart.");
        if (armed > 0)
        {
            // Armed here, where tool 2 actually starts, and not at the request: this tool is a
            // durable activity, so it runs on a genuine first execution and not on an attach.
            SelfKill.Arm(armed);
        }
        await Task.Delay(TimeSpan.FromSeconds(delaySeconds));
        Console.WriteLine(">>> TOOL 2 COMPLETE: Grand Ballroom is the best option");
        return "Grand Ballroom is the best option. Now call step_three_confirm.";
    }, "step_two_compare", "Compare the venue options. This is the second step."),

    AIFunctionFactory.Create((string selection) =>
    {
        Console.WriteLine(">>> TOOL 3: Confirming booking...");
        Console.WriteLine(">>> TOOL 3 COMPLETE: Booking confirmed for Grand Ballroom");
        return "Booking confirmed for Grand Ballroom. All steps complete!";
    }, "step_three_confirm", "Confirm the venue booking. This is the third and final step."),
};
```

`IDaprAgentInvoker` wraps each agent invocation in a durable Dapr workflow. This demo registers a workflow of its own anyway, for one reason: `RunAgentAsync` mints the instance ID itself, so nothing outside the process could be told which execution to watch. Registering `CrashRecoveryWorkflow` is what lets the caller supply the ID:

```csharp
builder.Services.AddDaprAgents(registrations: options =>
    {
        // Registering our own workflow is what gives the caller the instance ID.
        options.RegisterWorkflow<CrashRecoveryWorkflow>();
    })
    .WithAgent(sp =>
    {
        // Canned by default: this quickstart is about durable execution, not model quality, so
        // it ships an offline model and needs no account. Set DIAGRID_QUICKSTART_MODEL=openai
        // (and export OPENAI_API_KEY) for a real provider.
        IChatClient chatClient;
        if (string.Equals(
            Environment.GetEnvironmentVariable("DIAGRID_QUICKSTART_MODEL"),
            "openai",
            StringComparison.OrdinalIgnoreCase))
        {
            var apiKey = Environment.GetEnvironmentVariable("OPENAI_API_KEY");
            chatClient = new OpenAIClient(apiKey)
                .GetChatClient("gpt-4.1-2025-04-14")
                .AsIChatClient();
        }
        else
        {
            chatClient = new CannedChatClient();
        }

        return chatClient.AsAIAgent(
            instructions: """
                You are an event planner. Call all three tools in sequence:
                1. First call step_one_search with the city name
                2. Then call step_two_compare with the result from step 1
                3. Finally call step_three_confirm with the result from step 2
                Do NOT skip any steps.
                """,
            name: "event-planner",
            tools: tools);
    })
    .WithCatalyst(new DiagridCatalystOptions
    {
        Registry = new RegistryMetadata { ResourceName = "agent-registry" },
    });

app.MapPost("/run", async (IDaprAgentInvoker invoker, RunRequest req, CancellationToken ct) =>
{
    var agent = invoker.GetAgent("event-planner");
    var result = await invoker.RunAgentAsync(agent, req.Prompt, cancellationToken: ct);
    return Results.Ok(new { response = result.Text });
});
```

`CrashRecoveryWorkflow` is the whole of the durability code the app writes by hand. `WorkflowContext.RunAgentAsync` keeps the agent run itself durable: every model call and every tool call is still a separate activity, so a completed activity is not replayed after a crash.

```csharp
public sealed class CrashRecoveryWorkflow : Workflow<string, string>
{
    public override async Task<string> RunAsync(WorkflowContext context, string prompt)
    {
        var agent = context.GetAgent("event-planner");
        var response = await context.RunAgentAsync(agent, prompt);
        return response.Text;
    }
}
```

:::tip
The Microsoft Agent Framework provides a familiar .NET dependency injection experience. The `Diagrid.AI.Microsoft.AgentFramework` package bridges Microsoft's agent abstractions with Dapr Workflows — Catalyst adds automatic failure detection, crash recovery, and multi-instance coordination.
:::

**Spring AI**

**Crash Recovery Demo** — a 3-tool agent pipeline that demonstrates how Catalyst recovers from a mid-execution crash.

The app is plain Spring AI — a `ChatClient` and three `@Tool` beans. Open `EventPlannerTools.java`; the agent calls the tools in sequence and tool 2 deliberately crashes the process:

```java
@Component
public class EventPlannerTools {

  @Tool(name = "step_one_search", description = "Search for event venues in a city. This is the first step.")
  public String stepOneSearch(@ToolParam(description = "the city to search in") String city) {
    LOG.info(">>> TOOL 1: Searching venues in '{}'...", city);
    return "Found 3 venues in " + city + ". Now call step_two_compare.";
  }

  @Tool(name = "step_two_compare", description = "Compare the venue options. This is the second step.")
  public String stepTwoCompare(@ToolParam(description = "the venues found in step one") String data) {
    LOG.info(">>> TOOL 2: Comparing venues...");
    Runtime.getRuntime().halt(1); // 💥 Simulates a crash (halt skips shutdown hooks)
    return "Grand Ballroom is the best option. Now call step_three_confirm.";
  }

  @Tool(name = "step_three_confirm", description = "Confirm the venue booking. This is the third and final step.")
  public String stepThreeConfirm(@ToolParam(description = "the selected venue") String selection) {
    LOG.info(">>> TOOL 3: Confirming booking...");
    return "Booking confirmed for Grand Ballroom. All steps complete!";
  }
}
```

There is **no durability code** in the app. Adding the `diagrid-spring-ai-starter` to the classpath is what makes every `ChatClient.call()` run as a durable Dapr workflow — the model turns and each `@Tool` call become checkpointed activities:

```xml
<dependency>
  <groupId>io.diagrid</groupId>
  <artifactId>diagrid-spring-ai-starter</artifactId>
  <version>0.2.0</version>
</dependency>
```

> `0.2.0` is the version this quickstart pins. For newer releases see [Maven Central](https://central.sonatype.com/artifact/io.diagrid/diagrid-spring-ai-starter).

The agent is a `ChatClient` **bean**, built from the injected `ChatClient.Builder` so the durability advisor attaches automatically. Being a bean is also what registers it: the registry records one agent per `ChatClient` bean, named after the bean, which is why the `@Bean` name matches the `diagrid agent create` name from step 6. Open `EventPlannerAgentConfig.java`:

```java
@Configuration
public class EventPlannerAgentConfig {

  private static final String SYSTEM = """
      You are an event planner. Call all three tools in sequence:
      1. First call step_one_search with the city name
      2. Then call step_two_compare with the result from step 1
      3. Finally call step_three_confirm with the result from step 2
      Do NOT skip any steps.""";

  @Bean("spring-ai-event-planner")
  ChatClient eventPlanner(ChatClient.Builder builder) {
    return builder.defaultSystem(SYSTEM).build();
  }
}
```

The controller is ordinary Spring AI — it injects the bean and calls it:

```java
@RestController
public class EventPlannerController {

  private final ChatClient chatClient;

  public EventPlannerController(ChatClient chatClient) {
    this.chatClient = chatClient;
  }

  @PostMapping("/run")
  public RunResponse run(@RequestBody RunRequest request) {
    String response = chatClient.prompt().user(request.prompt()).call().content();
    return new RunResponse(response);
  }

  public record RunRequest(String prompt) {}

  public record RunResponse(String response) {}
}
```

:::tip
Spring AI gives you `ChatClient`, tool calling, and model abstractions but has no built-in durability. The `diagrid-spring-ai-starter` adds it with no code changes — just one dependency — and Catalyst adds automatic failure detection, crash recovery, and multi-instance coordination.
:::

**Deep Agents**

**Crash Recovery Demo** — a 3-step pipeline that demonstrates how Catalyst recovers from a mid-execution crash.

Open `crash_test.py`. It defines three tools that the agent calls in sequence — step 2 deliberately crashes the process:

```python
from langchain_core.tools import tool
from deepagents import create_deep_agent
from diagrid.agent.deepagents import DaprWorkflowDeepAgentRunner

@tool
def step_one_search(event_type: str) -> str:
    """Search for transportation options. This is the first step."""
    return f"Found 3 transportation options for {event_type}. Now call step_two_compare."

@tool
def step_two_compare(data: str) -> str:
    """Compare transportation options. This is the second step."""
    os._exit(1)  # 💥 Simulates a crash — comment out this line before the second run
    return "Premier Shuttle Co. is the best value. Now call step_three_confirm."

@tool
def step_three_confirm(selection: str) -> str:
    """Confirm the transportation selection. This is the third and final step."""
    return "Transportation confirmed with Premier Shuttle Co. All steps complete!"
```

The `DaprWorkflowDeepAgentRunner` wraps the Deep Agent — each tool call becomes a durable Dapr workflow activity:

```python
agent = create_deep_agent(
    model="openai:gpt-4o-mini",
    tools=[step_one_search, step_two_compare, step_three_confirm],
    system_prompt="Execute exactly three tools in sequence...",
    name="transportation-planner",
)

runner = DaprWorkflowDeepAgentRunner(
    agent=agent,
    name="transportation-planner",
    max_steps=10,
)
```

:::tip
LangChain Deep Agents gives you a LangChain-compatible agent framework with tool use but has no built-in durability. The `DaprWorkflowDeepAgentRunner` wraps your existing agent — no code changes needed — and Catalyst adds automatic failure detection, crash recovery, and multi-instance coordination.
:::

**Deep Agents (Sub Agents)**

**Sub-Agent Orchestration** — a supervisor agent coordinates two specialist sub-agents (Researcher and Analyst), each running as an independent durable Dapr workflow.

![Sub-Agent Orchestration with Catalyst](https://docs.diagrid.io/img/frameworks/async_subagents_diagram_catalyst.svg)

Open `subagent_workflows.py`. Each sub-agent has its own tools and runs as a separate `DaprWorkflowDeepAgentRunner`:

```python
from deepagents import AsyncSubAgent, create_deep_agent
from langchain.agents import create_agent
from langchain_core.tools import tool
from diagrid.agent.deepagents import DaprWorkflowDeepAgentRunner

@tool
def search_web(query: str) -> str:
    """Search the web for information on a given topic."""
    return f"Found 3 sources on '{query}': ..."

def make_researcher():
    return create_agent(
        model="openai:gpt-4o-mini",
        tools=[search_web],
        system_prompt="You are a research agent...",
        name="researcher",
    )
```

The supervisor uses `AsyncSubAgent` to delegate to sub-agents over HTTP via the Agent Protocol:

```python
def make_supervisor():
    return create_deep_agent(
        model="openai:gpt-4o-mini",
        subagents=[
            AsyncSubAgent(
                name="researcher",
                description="Research agent that searches the web...",
                graph_id="researcher",
                url="http://localhost:8001",
            ),
            AsyncSubAgent(
                name="analyst",
                description="Analyst agent that produces analysis reports...",
                graph_id="analyst",
                url="http://localhost:8002",
            ),
        ],
        system_prompt="You are a supervisor that orchestrates research and analysis...",
        name="supervisor",
    )
```

Each sub-agent is wrapped in `DaprWorkflowDeepAgentRunner` and exposed via an `AgentProtocolAdapter` — a thin FastAPI server implementing the Agent Protocol (HTTP). The supervisor communicates with sub-agents over HTTP, and each agent's workflow is independently durable.

:::tip
Each sub-agent runs as its own independent Dapr workflow. If any agent crashes, only that agent's workflow is affected — the supervisor and other sub-agents continue running. On restart, each agent resumes from its last checkpointed state.
:::

**Claude Agents**

**Crash Recovery Demo** — a 3-step pipeline that demonstrates how Catalyst recovers from a mid-execution crash.

Open `crash_test.py`. It defines three tools that the agent calls in sequence — step 2 deliberately crashes the process:

```python
import os

from claude_agent_sdk import ClaudeAgentOptions, tool
from diagrid.agent.claude_agents import DaprWorkflowAgentRunner

@tool("step_one_search", "Search for photography options. This is the first step.", {"event_type": str})
async def step_one_search(args):
    text = f"Found 3 {args['event_type']} photography options. Now call step_two_compare."
    return {"content": [{"type": "text", "text": text}]}

@tool("step_two_compare", "Compare photography options. This is the second step.", {"data": str})
async def step_two_compare(args):
    os._exit(1)  # 💥 Simulates a crash — comment out this line before the second run
    return {"content": [{"type": "text", "text": "Candid Frames is the best value. Now call step_three_confirm."}]}

@tool("step_three_confirm", "Confirm the photography selection. This is the third and final step.", {"selection": str})
async def step_three_confirm(args):
    return {"content": [{"type": "text", "text": "Photography confirmed with Candid Frames. All steps complete!"}]}
```

The `DaprWorkflowAgentRunner` wraps a standard Claude Agent SDK setup — each LLM turn and each tool call becomes a durable Dapr workflow activity:

```python
options = ClaudeAgentOptions(
    system_prompt="Execute exactly three tools in sequence...",
    model=os.environ.get("CLAUDE_MODEL", "claude-sonnet-4-6"),
)

runner = DaprWorkflowAgentRunner(
    name="photography-planner",
    options=options,
    tools=[step_one_search, step_two_compare, step_three_confirm],
    max_iterations=10,
)
```

:::tip
The Claude Agent SDK gives you tool-calling with Anthropic models but has no built-in durability. The `DaprWorkflowAgentRunner` wraps your tools — no code changes needed — and Catalyst adds automatic failure detection, crash recovery, and multi-instance coordination.
:::

**HolmesGPT**

**Durable Investigation** — an SRE agent that investigates a cluster problem, parks on a tool call that needs human approval, and resumes after a full process restart.

The examples directory contains `basic.py` (a single investigation run), `server.py` (an HTTP server with streaming and approval endpoints), and `phase_a_schedule_and_park.py` / `phase_b_resume_after_crash.py` (the crash-and-resume test).

Open `server.py`. `DaprWorkflowHolmesRunner` wraps a standard HolmesGPT setup — HolmesGPT renders its own prompt, toolsets, and runbooks locally, while every reasoning step and tool call becomes a durable Dapr workflow activity:

```python
from diagrid.agent.holmesgpt import DaprWorkflowHolmesRunner

def main() -> None:
    runner = DaprWorkflowHolmesRunner(
        name="sre-agent",
        max_steps=10,
    )
    runner.serve(port=5001)
```

`runner.serve()` exposes the investigation over HTTP, including an approval endpoint for tool calls that fall outside the default allow list.

:::tip
HolmesGPT gives you a mature SRE toolset — Kubernetes, logs, and observability queries — but an investigation is a long, multi-step loop with no built-in durability. Wrapping it in `DaprWorkflowHolmesRunner` means a paused approval or a crashed process doesn't lose the investigation: Catalyst replays the completed steps and continues.
:::

**Mastra**

**Crash Recovery Demo** — an SRE agent that looks up an incident's status, demonstrating how Catalyst recovers from a mid-turn crash.

Open `crash_test.ts`. Unlike the other frameworks' tabs above, the crash here happens inside a **model call**, not a tool — after the first model call and the `checkIncidentStatus` tool call have both completed and been checkpointed, but before the second model call returns:

```typescript
import { Agent } from '@mastra/core/agent';
import { createTool } from '@mastra/core/tools';
import { DaprWorkflowAgentRunner, type InvokeModelInput } from '@diagrid/agent-mastra';
import { z } from 'zod';

const checkIncidentStatus = createTool({
  id: 'checkIncidentStatus',
  description: 'Check the status of an ongoing incident by its incident ID.',
  inputSchema: z.object({ incidentId: z.string() }),
  execute: ({ incidentId }) => Promise.resolve({
    incidentId,
    status: 'mitigated, monitoring for recurrence',
    assignee: 'on-call SRE',
  }),
});

const agent = new Agent({
  id: 'sre-agent',
  name: 'sre-agent',
  instructions: 'You look up incident statuses. Always use the checkIncidentStatus tool.',
  model: resolveModel(),
  tools: { checkIncidentStatus },
});
```

The `DaprWorkflowAgentRunner` wraps the standard Mastra agent — each model call and each tool call becomes its own durable Dapr workflow activity:

```typescript
const runner = new DaprWorkflowAgentRunner({
  agent,
  name: 'sre-agent',
  maxIterations: 10,
});
```

The crash point is a counter wrapped around the runner's own model invoker, persisted to a state file so it survives the process restart — not a line you comment out, so there is no source edit between the two runs:

```typescript
const realInvoker = runner.modelInvoker;
runner.setModelInvoker(async (input: InvokeModelInput) => {
  const state = loadState();
  if (state.modelCalls + 1 === CRASH_ON_MODEL_CALL && !state.crashed) {
    state.crashed = true;
    saveState(state);
    process.exit(1); // 💥 crashes during the 2nd model call — no code edit for the second run
  }
  state.modelCalls += 1;
  saveState(state);
  return realInvoker(input);
});
```

:::tip
Mastra gives you agents, tool calling, and model routing but has no built-in durability. The `DaprWorkflowAgentRunner` wraps your existing agent — no code changes needed — and Catalyst adds automatic failure detection, crash recovery, and multi-instance coordination.
:::

## 4. Configure API Key

This quickstart uses OpenAI as the LLM provider, Google Gemini for ADK and Anthropic for the Claude Agents SDK. Catalyst is LLM-agnostic — you're free to use any provider supported by your chosen framework. The **LangGraph**, **Microsoft Agent Framework** and **Spring AI** tabs need no key at all: LangGraph's crash-test app makes no model call, and the other two ship a deterministic offline model you can swap for a real provider with `DIAGRID_QUICKSTART_MODEL=openai`.

**Dapr Agents**

The LLM is configured via `DaprChatClient(component_name="llm-provider")` — a Dapr component in `resources/llm-provider.yaml` that references your OpenAI API key:

```yaml
apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
  name: llm-provider
spec:
  type: conversation.openai
  version: v1
  metadata:
  - name: key
    value: "{{OPENAI_API_KEY}}"
  - name: model
    value: gpt-4.1-2025-04-14
```

Update the `key` value with your OpenAI API key.

**CrewAI**

**macOS/Linux**

```bash
export OPENAI_API_KEY="your-key-here"
```

**Windows**

```powershell
$env:OPENAI_API_KEY = "your-key-here"
```

**LangGraph**

**No API key needed.** This tab runs `crash_test.py`, a 3-node graph with no model call in it at all, so there is nothing to configure here. Skip to the next step.

**Strands**

**macOS/Linux**

```bash
export OPENAI_API_KEY="your-key-here"
```

**Windows**

```powershell
$env:OPENAI_API_KEY = "your-key-here"
```

**OpenAI Agents**

**macOS/Linux**

```bash
export OPENAI_API_KEY="your-key-here"
```

**Windows**

```powershell
$env:OPENAI_API_KEY = "your-key-here"
```

**Google ADK**

**macOS/Linux**

```bash
export OPENAI_API_KEY="your-key-here"
```

**Windows**

```powershell
$env:OPENAI_API_KEY = "your-key-here"
```

**Pydantic AI**

**macOS/Linux**

```bash
export OPENAI_API_KEY="your-key-here"
```

**Windows**

```powershell
$env:OPENAI_API_KEY = "your-key-here"
```

**Microsoft Agent Framework (.NET)**

**This tab needs no API key.** The app ships an offline model (`CannedChatClient`) that calls the three tools in the order the demo needs, so the crash and the recovery are the only moving parts. It announces itself in the startup log, so no such line means the app is talking to a real provider. To run against OpenAI instead, set both variables:

**macOS/Linux**

```bash
export DIAGRID_QUICKSTART_MODEL="openai"
export OPENAI_API_KEY="your-key-here"
```

**Windows**

```powershell
$env:DIAGRID_QUICKSTART_MODEL = "openai"
$env:OPENAI_API_KEY = "your-key-here"
```

**Spring AI**

**This tab needs no API key.** The app ships an offline model (`CannedChatModel`) that calls the three tools in order, so the crash and the recovery are the only moving parts. It announces itself in the startup log, so no such line means the app is talking to a real provider. To run against OpenAI instead, set both variables:

**macOS/Linux**

```bash
export DIAGRID_QUICKSTART_MODEL="openai"
export OPENAI_API_KEY="your-key-here"
```

**Windows**

```powershell
$env:DIAGRID_QUICKSTART_MODEL = "openai"
$env:OPENAI_API_KEY = "your-key-here"
```

On that path a missing or wrong key no longer stops the app from starting. The provider rejects the first request instead, and because the model call is a durable activity that failure is retried a few times before it surfaces.

**Deep Agents**

**macOS/Linux**

```bash
export OPENAI_API_KEY="your-key-here"
```

**Windows**

```powershell
$env:OPENAI_API_KEY = "your-key-here"
```

**Deep Agents (Sub Agents)**

**macOS/Linux**

```bash
export OPENAI_API_KEY="your-key-here"
```

**Windows**

```powershell
$env:OPENAI_API_KEY = "your-key-here"
```

**Claude Agents**

**macOS/Linux**

```bash
export ANTHROPIC_API_KEY="your-key-here"
```

**Windows**

```powershell
$env:ANTHROPIC_API_KEY = "your-key-here"
```

**HolmesGPT**

HolmesGPT resolves its LLM through LiteLLM. `server.py` needs only a provider key; the `phase_a`/`phase_b` scripts also read an explicit `MODEL`, so export both:

**macOS/Linux**

```bash
export OPENAI_API_KEY="your-key-here"
export MODEL="gpt-4.1-2025-04-14"
```

**Windows**

```powershell
$env:OPENAI_API_KEY = "your-key-here"
$env:MODEL="gpt-4.1-2025-04-14"
```

**Mastra**

**macOS/Linux**

```bash
export OPENAI_API_KEY="your-key-here"
```

**Windows**

```powershell
$env:OPENAI_API_KEY = "your-key-here"
```

To run against a local [Ollama](https://ollama.com) model instead — no API key needed:

```bash
ollama serve &
ollama pull qwen3:0.6b
export OLLAMA_ENDPOINT="http://localhost:11434/v1"
export OLLAMA_MODEL="qwen3:0.6b"   # optional, this is the default
```

## 5. Install Dependencies

**Dapr Agents**

```bash
uv sync
```

**CrewAI**

```bash
uv sync
```

**LangGraph**

```bash
uv sync
```

**Strands**

```bash
uv sync
```

**OpenAI Agents**

```bash
uv sync
```

**Google ADK**

```bash
uv sync
```

**Pydantic AI**

```bash
uv sync
```

**Microsoft Agent Framework (.NET)**

```bash
dotnet build
```

**Spring AI**

```bash
mvn package -DskipTests
```

**Deep Agents**

```bash
uv sync
```

**Deep Agents (Sub Agents)**

```bash
uv sync
```

**Claude Agents**

```bash
uv sync
```

**HolmesGPT**

```bash
python -m venv .venv
source .venv/bin/activate
pip install "diagrid[holmesgpt]"
```

:::caution
Install HolmesGPT in its own virtual environment. It ships strict dependency pins that conflict with the other agent extras, which is why it is not part of `diagrid[all]`.
:::

**Mastra**

```bash
npm install
```

## 6. Create Catalyst Project and Agent

Set up the Catalyst project, then register the agent.

**Dapr Agents**

Create a Catalyst project with managed workflow, key-value, and pub/sub enabled, and set it as the default for this session.

```bash
diagrid project create durable-agent-quickstart --enable-managed-workflow --deploy-managed-kv --deploy-managed-pubsub --wait --use
```

Register the agent with the project.

```bash
diagrid agent create invitations-manager --wait
```

**CrewAI**

Create a Catalyst project with managed workflow, key-value, and pub/sub enabled, and set it as the default for this session.

```bash
diagrid project create crewai-quickstart --enable-managed-workflow --deploy-managed-kv --deploy-managed-pubsub --wait --use
```

Register the agent with the project.

```bash
diagrid agent create venue-scout --wait
```

**LangGraph**

Create a Catalyst project with managed workflow, key-value, and pub/sub enabled, and set it as the default for this session.

```bash
diagrid project create langgraph-quickstart --enable-managed-workflow --deploy-managed-kv --deploy-managed-pubsub --wait --use
```

Register the agent with the project.

```bash
diagrid agent create schedule-planner --wait
```

**Strands**

Create a Catalyst project with managed workflow, key-value, and pub/sub enabled, and set it as the default for this session.

```bash
diagrid project create strands-quickstart --enable-managed-workflow --deploy-managed-kv --deploy-managed-pubsub --wait --use
```

Register the agent with the project.

```bash
diagrid agent create budget-planner --wait
```

**OpenAI Agents**

Create a Catalyst project with managed workflow, key-value, and pub/sub enabled, and set it as the default for this session.

```bash
diagrid project create openai-quickstart --enable-managed-workflow --deploy-managed-kv --deploy-managed-pubsub --wait --use
```

Register the agent with the project.

```bash
diagrid agent create catering-coordinator --wait
```

**Google ADK**

Create a Catalyst project with managed workflow, key-value, and pub/sub enabled, and set it as the default for this session.

```bash
diagrid project create adk-quickstart --enable-managed-workflow --deploy-managed-kv --deploy-managed-pubsub --wait --use
```

Register the agent with the project.

```bash
diagrid agent create entertainment-planner --wait
```

**Pydantic AI**

Create a Catalyst project with managed workflow, key-value, and pub/sub enabled, and set it as the default for this session.

```bash
diagrid project create pydantic-ai-quickstart --enable-managed-workflow --deploy-managed-kv --deploy-managed-pubsub --wait --use
```

Register the agent with the project.

```bash
diagrid agent create decoration-planner --wait
```

**Microsoft Agent Framework (.NET)**

Create a Catalyst project with managed workflow, key-value, and pub/sub enabled, and set it as the default for this session.

```bash
diagrid project create dotnet-quickstart --enable-managed-workflow --deploy-managed-kv --deploy-managed-pubsub --wait --use
```

Register the agent with the project.

```bash
diagrid agent create event-planner --wait
```

**Spring AI**

Create a Catalyst project with managed workflow enabled, and set it as the default for this session.

```bash
diagrid project create spring-ai-quickstart --enable-managed-workflow --deploy-managed-kv --wait --use
```

Register the agent with the project.

```bash
diagrid agent create spring-ai-event-planner --wait
```

**Deep Agents**

Create a Catalyst project with managed workflow, key-value, and pub/sub enabled, and set it as the default for this session.

```bash
diagrid project create deepagents-quickstart --enable-managed-workflow --deploy-managed-kv --deploy-managed-pubsub --wait --use
```

Register the agent with the project.

```bash
diagrid agent create transportation-planner --wait
```

**Deep Agents (Sub Agents)**

Create a Catalyst project with managed workflow, key-value, and pub/sub enabled, and set it as the default for this session.

```bash
diagrid project create deepagents-quickstart --enable-managed-workflow --deploy-managed-kv --deploy-managed-pubsub --wait --use
```

Register the sub-agents and supervisor with the project.

```bash
diagrid agent create researcher --wait
diagrid agent create analyst --wait
diagrid agent create supervisor --wait
```

**Claude Agents**

Create a Catalyst project with managed workflow, key-value, and pub/sub enabled, and set it as the default for this session.

```bash
diagrid project create claude-quickstart --enable-managed-workflow --deploy-managed-kv --deploy-managed-pubsub --wait --use
```

Register the agent with the project.

```bash
diagrid agent create photography-planner --wait
```

**Mastra**

Create a Catalyst project with managed workflow and key-value store enabled, and set it as the default for this session.

```bash
diagrid project create mastra-quickstart --enable-managed-workflow --deploy-managed-kv --wait --use
```

Register the agent with the project.

```bash
diagrid agent create sre-agent --wait
```

## 7. Run with Catalyst Cloud

**Dapr Agents**

```bash
uv run diagrid dev run -f dev-python-durable-agent.yaml --approve
```

**CrewAI**

```bash
uv run diagrid dev run -f dev-crash-test.yaml --approve
```

**LangGraph**

```bash
uv run diagrid dev run -f dev-crash-test.yaml --approve
```

**Strands**

```bash
uv run diagrid dev run -f dev-crash-test.yaml --approve
```

**OpenAI Agents**

```bash
uv run diagrid dev run -f dev-crash-test.yaml --approve
```

**Google ADK**

```bash
uv run diagrid dev run -f dev-crash-test.yaml --approve
```

**Pydantic AI**

```bash
uv run diagrid dev run -f dev-crash-test.yaml --approve
```

**Microsoft Agent Framework (.NET)**

```bash
diagrid dev run -f dev-dotnet-agent.yaml --approve
```

**Spring AI**

```bash
diagrid dev run -f dev-spring-ai-event-planner.yaml --approve
```

**Deep Agents**

```bash
uv run diagrid dev run -f dev-crash-test.yaml --approve
```

**Deep Agents (Sub Agents)**

```bash
uv run diagrid dev run -f dev-subagent-workflows.yaml --approve
```

Wait for all three agents to report they are ready. You should see log output for the researcher (port 8001), analyst (port 8002), and supervisor.

The supervisor automatically triggers the research → analysis pipeline on startup:

```text
== APP - supervisor == ================================================================
== APP - supervisor ==   SUPERVISOR -- Research and analyze: advances in durable AI agent orchestration
== APP - supervisor == ================================================================
== APP - supervisor ==
== APP - supervisor ==   Workflow started: graph-supervisor-...
== APP - researcher ==   [Researcher] Searching: advances in durable AI agent orchestration
== APP - analyst ==   [Analyst] Analyzing: advances in durable AI agent orchestration
== APP - supervisor ==
== APP - supervisor == ================================================================
== APP - supervisor ==   SUPERVISOR FINAL RESPONSE
== APP - supervisor == ================================================================
== APP - supervisor ==   Based on the research and analysis, here is a synthesis...
```

**Claude Agents**

```bash
uv run diagrid dev run -f dev-crash-test.yaml --approve
```

**HolmesGPT**

The examples repository has no multi-app run file, so pass the app details and the command directly:

```bash
diagrid dev run --project holmesgpt-agent-qs \
  --id holmes-server --app-port 5001 --approve \
  -- python server.py
```

**Mastra**

If you've run this demo before, clear its state file first so this counts as a fresh first run:

```bash
rm -f "${TMPDIR:-/tmp}/diagrid-mastra-crash-state.json"
```

```bash
diagrid dev run -f dev-crash-test.yaml --approve
```

This single command is the whole demo: `crash_test.ts` sends its own prompt through the agent, then deliberately crashes itself partway through — see step 8.

:::tip
`diagrid dev run` runs your code locally and connects it to the Catalyst Cloud workflow engine. Your agent code never leaves your machine — only workflow state is stored in Catalyst.
:::

Wait for the readiness log before proceeding — `Uvicorn running on <localhost:port>` for the Python frameworks, or `Established gRPC bidirectional stream with Dapr sidecar` for the Microsoft Agent Framework (.NET) tab.

You can open the [Catalyst Cloud web console](https://catalyst.diagrid.io/), navigate to the **Agents** section, and select your agent from the list to inspect its configuration and executions.

## 8. Trigger the Agent

Open a **new terminal** and trigger the agent:

**Dapr Agents**

**macOS/Linux**

```bash
curl -X POST http://localhost:8006/agent/run \
  -H "Content-Type: application/json" \
  -d '{"task": "Send invitations to 100 guests for a corporate networking event"}'
```

**Windows**

```powershell
Invoke-RestMethod -Method Post -Uri 'http://localhost:8006/agent/run' -ContentType 'application/json' -Body '{"task": "Send invitations to 100 guests for a corporate networking event"}'
```

**VS Code REST Client**

Open [`test.http`](https://github.com/diagridio/catalyst-quickstarts/blob/main/agents/dapr-agents/durable-agent/test.http) and click *Send Request* above the request. Requires the [REST Client](https://marketplace.visualstudio.com/items?itemName=humao.rest-client) extension.

Expected output:

```text
== APP - invitations-manager == Invitations sent: 70 via email, 30 via physical mail
```

**CrewAI**

**macOS/Linux**

```bash
curl -X POST http://localhost:8001/run \
  -H "Content-Type: application/json" \
  -d '{"prompt": "Find a venue in Austin for a company gala"}'
```

**Windows**

```powershell
Invoke-RestMethod -Method Post -Uri 'http://localhost:8001/run' -ContentType 'application/json' -Body '{"prompt": "Find a venue in Austin for a company gala"}'
```

**VS Code REST Client**

Open [`test.http`](https://github.com/diagridio/catalyst-quickstarts/blob/main/agents/crewai/test.http) and click *Send Request* above the request. Requires the [REST Client](https://marketplace.visualstudio.com/items?itemName=humao.rest-client) extension.

You'll see step 1 complete, then the process crashes at step 2:

```text
== APP - venue-scout == >>> TOOL 1: Searching venues in 'Austin'...
== APP - venue-scout == >>> TOOL 1 COMPLETE: Found 3 venues in Austin
== APP - venue-scout == >>> TOOL 2: Comparing venues...
```

The process exits — this is expected.

**LangGraph**

**macOS/Linux**

```bash
curl -X POST http://localhost:8001/crash/run \
  -H "Content-Type: application/json" \
  -d '{"id": "gala-42", "topic": "company gala on March 15", "kill_after_seconds": 8}'
```

**Windows**

```powershell
Invoke-RestMethod -Method Post -Uri 'http://localhost:8001/crash/run' -ContentType 'application/json' -Body '{"id": "gala-42", "topic": "company gala on March 15", "kill_after_seconds": 8}'
```

**VS Code REST Client**

Open [`test.http`](https://github.com/diagridio/catalyst-quickstarts/blob/main/agents/langgraph/test.http), add `"kill_after_seconds": 8` to the body of the *Crash Recovery: run the graph under an ID you own* request, then click *Send Request* above it. The file ships that field as a comment rather than in the body, so this one edit is on you. Requires the [REST Client](https://marketplace.visualstudio.com/items?itemName=humao.rest-client) extension.

Note that the instance ID is defined in the request (`gala-42`), this makes it easier to identify the run and checking the results.
`kill_after_seconds` ensures the entire application crashes: the app arms a timer when `compare_options` starts and kills *itself* 8 seconds in.
The request blocks until that happens and then reports a connection reset. The process is gone before it can answer, which is exactly what a real crash looks like.

```text
== APP - schedule-planner == >>> STEP 1: Checking venue availability for 'company gala on March 15'...
== APP - schedule-planner == >>> STEP 1 COMPLETE: Grand Ballroom available on March 15 (2PM-6PM, 6PM-11PM)
== APP - schedule-planner == >>> STEP 2: Comparing venue options over ~30s, but this process kills itself 8s into the run, as asked by kill_after_seconds. It resumes on restart.
== APP - schedule-planner == >>> crash: killing this process 8s into the run, as asked by kill_after_seconds
```

**Strands**

**macOS/Linux**

```bash
curl -X POST http://localhost:8001/run \
  -H "Content-Type: application/json" \
  -d '{"prompt": "Calculate a budget for a corporate retreat with venue, catering, and entertainment"}'
```

**Windows**

```powershell
Invoke-RestMethod -Method Post -Uri 'http://localhost:8001/run' -ContentType 'application/json' -Body '{"prompt": "Calculate a budget for a corporate retreat with venue, catering, and entertainment"}'
```

**VS Code REST Client**

Open [`test.http`](https://github.com/diagridio/catalyst-quickstarts/blob/main/agents/strands/test.http) and click *Send Request* above the request. Requires the [REST Client](https://marketplace.visualstudio.com/items?itemName=humao.rest-client) extension.

You'll see step 1 complete, then the process crashes at step 2:

```text
== APP - budget-planner == >>> TOOL 1: Calculating budget for '...'...
== APP - budget-planner == >>> TOOL 1 COMPLETE: Estimated budget: $8,550
== APP - budget-planner == >>> TOOL 2: Analyzing costs...
```

The process exits — this is expected.

**OpenAI Agents**

**macOS/Linux**

```bash
curl -X POST http://localhost:8001/run \
  -H "Content-Type: application/json" \
  -d '{"prompt": "Find catering for a corporate gala"}'
```

**Windows**

```powershell
Invoke-RestMethod -Method Post -Uri 'http://localhost:8001/run' -ContentType 'application/json' -Body '{"prompt": "Find catering for a corporate gala"}'
```

**VS Code REST Client**

Open [`test.http`](https://github.com/diagridio/catalyst-quickstarts/blob/main/agents/openai-agents/test.http) and click *Send Request* above the request. Requires the [REST Client](https://marketplace.visualstudio.com/items?itemName=humao.rest-client) extension.

You'll see step 1 complete, then the process crashes at step 2:

```text
== APP - catering-coordinator == >>> TOOL 1: Searching catering for '...'...
== APP - catering-coordinator == >>> TOOL 1 COMPLETE: Found 3 ... catering options
== APP - catering-coordinator == >>> TOOL 2: Comparing options...
```

The process exits — this is expected.

**Google ADK**

**macOS/Linux**

```bash
curl -X POST http://localhost:8001/run \
  -H "Content-Type: application/json" \
  -d '{"prompt": "Find entertainment for a corporate holiday party"}'
```

**Windows**

```powershell
Invoke-RestMethod -Method Post -Uri 'http://localhost:8001/run' -ContentType 'application/json' -Body '{"prompt": "Find entertainment for a corporate holiday party"}'
```

**VS Code REST Client**

Open [`test.http`](https://github.com/diagridio/catalyst-quickstarts/blob/main/agents/adk/test.http) and click *Send Request* above the request. Requires the [REST Client](https://marketplace.visualstudio.com/items?itemName=humao.rest-client) extension.

You'll see step 1 complete, then the process crashes at step 2:

```text
== APP - entertainment-planner == >>> TOOL 1: Finding entertainment for '...'...
== APP - entertainment-planner == >>> TOOL 1 COMPLETE: Found 3 entertainment options for ...
== APP - entertainment-planner == >>> TOOL 2: Comparing options...
```

The process exits — this is expected.

**Pydantic AI**

**macOS/Linux**

```bash
curl -X POST http://localhost:8001/run \
  -H "Content-Type: application/json" \
  -d '{"prompt": "Find decorations for a garden wedding theme"}'
```

**Windows**

```powershell
Invoke-RestMethod -Method Post -Uri 'http://localhost:8001/run' -ContentType 'application/json' -Body '{"prompt": "Find decorations for a garden wedding theme"}'
```

**VS Code REST Client**

Open [`test.http`](https://github.com/diagridio/catalyst-quickstarts/blob/main/agents/pydantic-ai/test.http) and click *Send Request* above the request. Requires the [REST Client](https://marketplace.visualstudio.com/items?itemName=humao.rest-client) extension.

You'll see step 1 complete, then the process crashes at step 2:

```text
== APP - decoration-planner == >>> TOOL 1: Searching decorations for '...'...
== APP - decoration-planner == >>> TOOL 1 COMPLETE: Found 3 decoration packages for ...
== APP - decoration-planner == >>> TOOL 2: Comparing packages...
```

The process exits — this is expected.

**Microsoft Agent Framework (.NET)**

**macOS/Linux**

```bash
curl -X POST http://localhost:5050/crash/run \
  -H "Content-Type: application/json" \
  -d '{"id": "gala-42", "prompt": "Find a venue in Austin for a company gala", "kill_after_seconds": 8}'
```

**Windows**

```powershell
Invoke-RestMethod -Method Post -Uri 'http://localhost:5050/crash/run' -ContentType 'application/json' -Body '{"id": "gala-42", "prompt": "Find a venue in Austin for a company gala", "kill_after_seconds": 8}'
```

**VS Code REST Client**

Open [`test.http`](https://github.com/diagridio/catalyst-quickstarts/blob/main/agents/microsoft-dotnet/test.http) and click *Send Request* above the request. Requires the [REST Client](https://marketplace.visualstudio.com/items?itemName=humao.rest-client) extension.

Tool 2 takes about 30 seconds, so this request takes about half a minute and all three tools complete:

```text
== APP - event-planner == >>> TOOL 1: Searching venues in 'Austin'...
== APP - event-planner == >>> TOOL 1 COMPLETE: Found 3 venues
== APP - event-planner == >>> TOOL 2: Comparing venues over ~30s, but this process kills itself 8s into the run, as asked by kill_after_seconds. It resumes on restart.
== APP - event-planner == >>> crash: killing this process 8s into the run, as asked by kill_after_seconds
```

**Spring AI**

**macOS/Linux**

```bash
curl -X POST http://localhost:8080/run \
  -H "Content-Type: application/json" \
  -d '{"prompt": "Find a venue in Austin for a company gala"}'
```

**Windows**

```powershell
Invoke-RestMethod -Method Post -Uri 'http://localhost:8080/run' -ContentType 'application/json' -Body '{"prompt": "Find a venue in Austin for a company gala"}'
```

**VS Code REST Client**

Open [`test.http`](https://github.com/diagridio/catalyst-quickstarts/blob/main/agents/spring-ai/event-planner/test.http) and click *Send Request* above the request. Requires the [REST Client](https://marketplace.visualstudio.com/items?itemName=humao.rest-client) extension.

You'll see tool 1 complete, then the process crashes at tool 2:

```text
== APP - spring-ai-event-planner == >>> TOOL 1: Searching venues in 'Austin'...
== APP - spring-ai-event-planner == >>> TOOL 1 COMPLETE: Found 3 venues
== APP - spring-ai-event-planner == >>> TOOL 2: Comparing venues...
```

The process exits — this is expected.

**Deep Agents**

**macOS/Linux**

```bash
curl -X POST http://localhost:8001/run \
  -H "Content-Type: application/json" \
  -d '{"prompt": "Find transportation for a corporate gala with 200 guests"}'
```

**Windows**

```powershell
Invoke-RestMethod -Method Post -Uri 'http://localhost:8001/run' -ContentType 'application/json' -Body '{"prompt": "Find transportation for a corporate gala with 200 guests"}'
```

**VS Code REST Client**

Open [`test.http`](https://github.com/diagridio/catalyst-quickstarts/blob/main/agents/deepagents/test.http) and click *Send Request* above the request. Requires the [REST Client](https://marketplace.visualstudio.com/items?itemName=humao.rest-client) extension.

You'll see step 1 complete, then the process crashes at step 2:

```text
== APP - transportation-planner == >>> TOOL 1: Searching transportation for '...'...
== APP - transportation-planner == >>> TOOL 1 COMPLETE: Found 3 transportation options for ...
== APP - transportation-planner == >>> TOOL 2: Comparing options...
```

The process exits — this is expected.

**Deep Agents (Sub Agents)**

No curl is needed — the supervisor automatically triggers on startup (see step 7). The supervisor orchestrates the full research → analysis pipeline and prints the final synthesized response.

**Claude Agents**

**macOS/Linux**

```bash
curl -X POST http://localhost:8001/run \
  -H "Content-Type: application/json" \
  -d '{"prompt": "Find photography for a corporate gala"}'
```

**Windows**

```powershell
Invoke-RestMethod -Method Post -Uri 'http://localhost:8001/run' -ContentType 'application/json' -Body '{"prompt": "Find photography for a corporate gala"}'
```

**VS Code REST Client**

Open [`test.http`](https://github.com/diagridio/catalyst-quickstarts/blob/main/agents/claude-agents/test.http) and click *Send Request* above the request. Requires the [REST Client](https://marketplace.visualstudio.com/items?itemName=humao.rest-client) extension.

You'll see step 1 complete, then the process crashes at step 2:

```text
== APP - photography-planner == >>> TOOL 1: Searching photography for '...'...
== APP - photography-planner == >>> TOOL 1 COMPLETE: Found 3 ... photography options
== APP - photography-planner == >>> TOOL 2: Comparing options...
```

The process exits — this is expected.

**HolmesGPT**

**macOS/Linux**

```bash
curl -X POST http://localhost:5001/investigations \
  -H "Content-Type: application/json" \
  -d '{"question": "Why is checkout-api crashlooping?"}'
```

**Windows**

```powershell
Invoke-RestMethod -Method Post -Uri 'http://localhost:5001/investigations' -ContentType 'application/json' -Body '{"question": "Why is checkout-api crashlooping?"}'
```

The response includes the workflow ID for the investigation. Stream its events:

**macOS/Linux**

```bash
curl -N http://localhost:5001/investigations/<id>/stream
```

**Windows**

```powershell
curl.exe -N http://localhost:5001/investigations/<id>/stream
```

:::note\[Why `curl.exe` here and not `Invoke-RestMethod`]
This endpoint streams events as the investigation runs. `Invoke-RestMethod` buffers the whole response before returning, so it would show nothing until the investigation finished. `curl.exe` — the real curl binary, included with Windows 10 1803 and later — streams as intended. Spelling out the `.exe` matters: in Windows PowerShell, a bare `curl` resolves to the `Invoke-WebRequest` alias instead.
:::

The investigation reasons and calls toolsets until it reaches a tool call outside the default allow list, then emits an `approval_required` event and parks. Note the `tool_call_id` on that event — you need it in the next step.

**Mastra**

No curl needed — `crash_test.ts` is a one-shot script, not an HTTP server. The command from step 7 already sent the prompt; that same run is what crashes:

```text
== APP - sre-agent == >>> Model call 1 (total across runs)
== APP - sre-agent == >>> checkIncidentStatus ran (total across runs: 1)
== APP - sre-agent ==
== APP - sre-agent == >>> Killing the process during model call 2, before it returns.
== APP - sre-agent == >>> Run "npm run crash-test" again — Catalyst will resume this workflow.
```

The process exits — this is expected.

## 9. Crash Recovery

Your code runs locally throughout this test. The Catalyst Cloud workflow engine — not your machine — tracks which steps completed and stores their results. That's what makes recovery possible even after a full process crash.

**Dapr Agents**

Stop the running application with `Ctrl+C`.

Crash recovery is built into Dapr Agents natively — the `DurableAgent` class automatically persists each tool execution as a workflow activity. If the process crashes, it resumes from the last saved state. See the [Durable Agent Quickstart](https://docs.diagrid.io/develop/agents/dapr-agents/durable-agent-quickstart) for a detailed walkthrough.

**CrewAI**

Open `crash_test.py` and comment out the crash line:

```python
# os._exit(1)  # 💥 comment out this line before the second run
```

Restart:

```bash
uv run diagrid dev run -f dev-crash-test.yaml
```

You do **not** need to curl again — the existing workflow resumes automatically. The workflow **resumes from tool 2** — tool 1 is not re-executed:

```text
== APP - venue-scout == >>> TOOL 2: Comparing venues...
== APP - venue-scout == >>> TOOL 2 COMPLETE: Grand Ballroom is the best option
== APP - venue-scout == >>> TOOL 3: Confirming booking...
== APP - venue-scout == >>> TOOL 3 COMPLETE: Booking confirmed for Grand Ballroom
```

**LangGraph**

The app has already crashed itself, 8 seconds into `compare_options`, because step 8 asked it to. The workflow instance `gala-42` is unaffected — it lives in Catalyst, not in the process that just died.

Now restart with the same command as step 7:

```bash
uv run diagrid dev run -f dev-crash-test.yaml --approve
```

**That is the whole recovery. You do not have to send anything.** As soon as the restarted app's worker reconnects, Catalyst hands `gala-42` back to it, `compare_options` starts over, and about 30 seconds later the graph finishes:

```text
== APP - schedule-planner == >>> STEP 2: Comparing venue options over ~30s. KILL THE APP NOW to test crash recovery (POST /crash/kill, or kill -9). It resumes on restart.
== APP - schedule-planner == >>> STEP 2 COMPLETE: Grand Ballroom (6PM-11PM) is the best option for 200 guests
== APP - schedule-planner == >>> STEP 3: Confirming booking...
== APP - schedule-planner == >>> STEP 3 COMPLETE: Booking confirmed: Grand Ballroom, March 15, 6PM-11PM
```

Step 2's line reads differently this time: nothing is armed in the restarted process, so it prints its un-armed prompt to kill the app. Ignore it — the run is already finishing on its own.

`check_venues` is **not** re-executed: its `STEP 1` lines do not appear a second time. That node had completed and Catalyst had recorded its result, so the engine replayed the saved value instead of running the node again. Only the node that was interrupted runs twice.

To collect the answer, send the **identical** request from step 8 once more. The crash killed the connection that was waiting for it, so this opens a new one; because the instance already exists, the call attaches to it instead of starting a second run:

```text
== APP - schedule-planner == >>> Attaching to the existing run gala-42 instead of starting a second one
```

The reply uses the one JSON shape every crash demo in this repo returns, `{"id", "result", "message"}`: a `200` carries the graph's recorded output in `result`, and a `202` (the wait budget elapsed first) carries the attach instruction in `message`. The `202` is not a failure — send the same request again to attach again.

**Strands**

Open `crash_test.py` and comment out the crash line:

```python
# os._exit(1)  # 💥 comment out this line before the second run
```

Restart:

```bash
uv run diagrid dev run -f dev-crash-test.yaml
```

You do **not** need to curl again — the existing workflow resumes automatically. The workflow **resumes from tool 2** — tool 1 is not re-executed:

```text
== APP - budget-planner == >>> TOOL 2: Analyzing costs...
== APP - budget-planner == >>> TOOL 2 COMPLETE: Found $1,200 in potential savings
== APP - budget-planner == >>> TOOL 3: Finalizing budget...
== APP - budget-planner == >>> TOOL 3 COMPLETE: Final budget: $7,350 (saved $1,200)
```

**OpenAI Agents**

Open `crash_test.py` and comment out the crash line:

```python
# os._exit(1)  # 💥 comment out this line before the second run
```

Restart:

```bash
uv run diagrid dev run -f dev-crash-test.yaml
```

You do **not** need to curl again — the existing workflow resumes automatically. The workflow **resumes from tool 2** — tool 1 is not re-executed:

```text
== APP - catering-coordinator == >>> TOOL 2: Comparing options...
== APP - catering-coordinator == >>> TOOL 2 COMPLETE: Farm Fresh Events is the best value
== APP - catering-coordinator == >>> TOOL 3: Confirming selection...
== APP - catering-coordinator == >>> TOOL 3 COMPLETE: Catering confirmed with Farm Fresh Events
```

**Google ADK**

Open `crash_test.py` and comment out the crash line:

```python
# os._exit(1)  # 💥 comment out this line before the second run
```

Restart:

```bash
uv run diagrid dev run -f dev-crash-test.yaml
```

You do **not** need to curl again — the existing workflow resumes automatically. The workflow **resumes from tool 2** — tool 1 is not re-executed:

```text
== APP - entertainment-planner == >>> TOOL 2: Comparing options...
== APP - entertainment-planner == >>> TOOL 2 COMPLETE: Live Jazz Band is the best option
== APP - entertainment-planner == >>> TOOL 3: Confirming booking...
== APP - entertainment-planner == >>> TOOL 3 COMPLETE: Entertainment confirmed with Live Jazz Band
```

**Pydantic AI**

Open `crash_test.py` and comment out the crash line:

```python
# os._exit(1)  # 💥 comment out this line before the second run
```

Restart:

```bash
uv run diagrid dev run -f dev-crash-test.yaml
```

You do **not** need to curl again — the existing workflow resumes automatically. The workflow **resumes from tool 2** — tool 1 is not re-executed:

```text
== APP - decoration-planner == >>> TOOL 2: Comparing packages...
== APP - decoration-planner == >>> TOOL 2 COMPLETE: Elegant Events Decor is the best value
== APP - decoration-planner == >>> TOOL 3: Confirming selection...
== APP - decoration-planner == >>> TOOL 3 COMPLETE: Decorations confirmed with Elegant Events Decor
```

**Microsoft Agent Framework (.NET)**

Restart with the same command as step 7:

```bash
diagrid dev run -f dev-dotnet-agent.yaml --approve
```

**That is the whole recovery. You do not have to send anything.** Catalyst has been retrying the interrupted tool call the entire time the app was down, and it hands the pending work back within a second of the restarted app's worker reconnecting:

```text
== APP - event-planner == >>> TOOL 2: Comparing venues over ~30s. KILL THE APP NOW to test crash recovery (POST /crash/kill, or kill -9). It resumes on restart.
== APP - event-planner == >>> TOOL 2 COMPLETE: Grand Ballroom is the best option
== APP - event-planner == >>> TOOL 3: Confirming booking...
== APP - event-planner == >>> TOOL 3 COMPLETE: Booking confirmed for Grand Ballroom
```

Tool 2's line reads differently this time: nothing is armed in the restarted process, so it prints its un-armed prompt to kill the app. Ignore it — the run is already finishing on its own.

`>>> TOOL 1: Searching venues in 'Austin'...` does **not** appear again, and neither does the LLM call that chose it. Those activities had completed and Catalyst had recorded their results, so the replay took the recorded values. Only the activity that was interrupted runs a second time.

To collect the answer, send the **identical** `/crash/run` request once more. The crash killed the connection that was waiting for it, so this opens a new one; because the instance already exists, the call attaches to the run you started before the crash instead of starting a second one, and the app logs `Attaching to the existing run gala-42` to say so.

**Spring AI**

Open `EventPlannerTools.java` and comment out the crash line in `step_two_compare`:

```java
// Runtime.getRuntime().halt(1); // 💥 comment out this line before the second run
```

Restart:

```bash
diagrid dev run -f dev-spring-ai-event-planner.yaml --approve
```

You do **not** need to curl again — the existing workflow resumes automatically. `step_one_search` and the first model turn are replayed from history (not re-executed), and execution continues from tool 2:

```text
== APP - spring-ai-event-planner == >>> TOOL 2: Comparing venues...
== APP - spring-ai-event-planner == >>> TOOL 2 COMPLETE: Grand Ballroom is the best option
== APP - spring-ai-event-planner == >>> TOOL 3: Confirming booking...
== APP - spring-ai-event-planner == >>> TOOL 3 COMPLETE: Booking confirmed for Grand Ballroom
```

**Deep Agents**

Open `crash_test.py` and comment out the crash line:

```python
# os._exit(1)  # 💥 comment out this line before the second run
```

Restart:

```bash
uv run diagrid dev run -f dev-crash-test.yaml --approve
```

You do **not** need to curl again — the existing workflow resumes automatically. The workflow **resumes from tool 2** — tool 1 is not re-executed:

```text
== APP - transportation-planner == >>> TOOL 2: Comparing options...
== APP - transportation-planner == >>> TOOL 2 COMPLETE: Premier Shuttle Co. is the best value
== APP - transportation-planner == >>> TOOL 3: Confirming selection...
== APP - transportation-planner == >>> TOOL 3 COMPLETE: Transportation confirmed with Premier Shuttle Co.
```

**Deep Agents (Sub Agents)**

This tab demonstrates multi-agent orchestration rather than crash recovery. Each sub-agent runs as its own independent Dapr workflow — if any agent crashes, only that agent's workflow is affected. The supervisor and other sub-agents continue running, and the crashed agent resumes from its last checkpointed state on restart.

To see single-agent crash recovery in action, try the [Deep Agents (Durable Workflows)](?agentframework=deepagents) tab instead.

**Claude Agents**

Open `crash_test.py` and comment out the crash line:

```python
# os._exit(1)  # 💥 comment out this line before the second run
```

Restart:

```bash
uv run diagrid dev run -f dev-crash-test.yaml --approve
```

You do **not** need to curl again — the existing workflow resumes automatically. The workflow **resumes from tool 2** — tool 1 is not re-executed:

```text
== APP - photography-planner == >>> TOOL 2: Comparing options...
== APP - photography-planner == >>> TOOL 2 COMPLETE: Candid Frames is the best value
== APP - photography-planner == >>> TOOL 3: Confirming selection...
== APP - photography-planner == >>> TOOL 3 COMPLETE: Photography confirmed with Candid Frames
```

**HolmesGPT**

The investigation is parked on an approval, so you can kill the process outright — no code edit needed. Stop it with `Ctrl+C`, then restart the same command:

```bash
diagrid dev run --project holmesgpt-agent-qs \
  --id holmes-server --app-port 5001 --approve \
  -- python server.py
```

The workflow is still `RUNNING` after the restart — Catalyst rehydrates it from its checkpointed history. Approve the parked tool call to let it finish, using the `tool_call_id` from step 7:

**macOS/Linux**

```bash
curl -X POST http://localhost:5001/investigations/<id>/approve \
  -H "Content-Type: application/json" \
  -d '{"tool_call_id": "<tool-call-id>", "approved": true}'
```

**Windows**

```powershell
Invoke-RestMethod -Method Post -Uri 'http://localhost:5001/investigations/<id>/approve' -ContentType 'application/json' -Body '{"tool_call_id": "<tool-call-id>", "approved": true}'
```

The investigation **resumes from the parked tool call** — the earlier reasoning steps and tool results are replayed from Catalyst, not re-executed, and the workflow runs to `COMPLETED` with its final answer.

:::tip
`phase_a_schedule_and_park.py` and `phase_b_resume_after_crash.py` in the same directory automate this exact sequence: phase A schedules an investigation and holds it at the approval, and phase B attaches from a fresh process after a `SIGKILL`, asserts the workflow is still `RUNNING`, then approves it and drains the remaining events.
:::

**Mastra**

The app has already crashed itself, during the second model call, because step 7's command ran to that point automatically. The workflow instance is unaffected — it lives in Catalyst, not in the process that just died.

Restart with the same command as step 7:

```bash
diagrid dev run -f dev-crash-test.yaml --approve
```

**That is the whole recovery. You do not have to send anything.** As soon as the restarted process's worker reconnects, Catalyst redelivers the pending work, and the turn completes:

```text
== APP - sre-agent == >>> Model call 2 (total across runs)
== APP - sre-agent ==
== APP - sre-agent == Status:      completed
== APP - sre-agent == Answer:      The status of incident INC-4471 is mitigated and currently under monitoring for recurrence.
== APP - sre-agent == Model calls: 2 (across all runs)
== APP - sre-agent == Tool runs:   1 (across all runs)
== APP - sre-agent ==
== APP - sre-agent == ✅ Recovery confirmed: checkIncidentStatus ran exactly once across both runs.
== APP - sre-agent ==    Its result was replayed from Catalyst workflow history, not recomputed.
```

`checkIncidentStatus` does **not** run a second time — its `Tool runs` count stays at 1. That activity had completed and Catalyst had recorded its result, so the replay took the recorded value instead of calling the tool again. Only the interrupted model call runs a second time.

:::tip
Because workflow state is stored remotely in Catalyst (not in your process), the engine replays saved results instead of re-executing completed steps. This works even after a full process crash.
:::

## 10. View in the Catalyst Web Console

Open the [Catalyst Cloud web console](https://catalyst.diagrid.io/) and navigate to the **Workflows** section. Select the workflow instance to inspect the full execution trace, including the input/output of every tool and LLM call and state persistence.

## 11. Clean Up

Stop the running application with `Ctrl+C`, then delete the Catalyst project:

**Dapr Agents**

```bash
diagrid project delete durable-agent-quickstart
```

**CrewAI**

```bash
diagrid project delete crewai-quickstart
```

**LangGraph**

```bash
diagrid project delete langgraph-quickstart
```

**Strands**

```bash
diagrid project delete strands-quickstart
```

**OpenAI Agents**

```bash
diagrid project delete openai-quickstart
```

**Google ADK**

```bash
diagrid project delete adk-quickstart
```

**Pydantic AI**

```bash
diagrid project delete pydantic-ai-quickstart
```

**Microsoft Agent Framework (.NET)**

```bash
diagrid project delete dotnet-quickstart
```

**Spring AI**

```bash
diagrid project delete spring-ai-quickstart
```

**Deep Agents**

```bash
diagrid project delete deepagents-quickstart
```

**Deep Agents (Sub Agents)**

```bash
diagrid project delete deepagents-quickstart
```

**Claude Agents**

```bash
diagrid project delete claude-quickstart
```

**HolmesGPT**

```bash
diagrid project delete holmesgpt-agent-qs
```

**Mastra**

```bash
diagrid project delete mastra-quickstart
```

## Summary

**Dapr Agents**

In this quickstart, you:

- Built a **Dapr Agents** durable agent with structured tool schemas and Dapr-native LLM configuration
- Ran it locally connected to Catalyst Cloud for state persistence and crash recovery
- Triggered the agent via REST API and inspected execution in the Catalyst console

**CrewAI**

In this quickstart, you:

- Wrapped a standard **CrewAI** agent with `DaprWorkflowAgentRunner` for durable execution
- Triggered a crash mid-pipeline and saw Catalyst recover automatically on restart
- Verified that completed steps were not re-executed — only the remaining steps ran

**LangGraph**

In this quickstart, you:

- Wrapped a standard **LangGraph** graph with `DaprWorkflowGraphRunner` for durable execution
- Triggered a crash mid-pipeline and saw Catalyst recover automatically on restart
- Verified that completed steps were not re-executed — only the remaining steps ran

**Strands**

In this quickstart, you:

- Wrapped a standard **Strands** agent with `DaprWorkflowAgentRunner` for durable execution
- Triggered a crash mid-pipeline and saw Catalyst recover automatically on restart
- Verified that completed steps were not re-executed — only the remaining steps ran

**OpenAI Agents**

In this quickstart, you:

- Wrapped a standard **OpenAI Agents** agent with `DaprWorkflowAgentRunner` for durable execution
- Triggered a crash mid-pipeline and saw Catalyst recover automatically on restart
- Verified that completed steps were not re-executed — only the remaining steps ran

**Google ADK**

In this quickstart, you:

- Wrapped a standard **Google ADK** agent with `DaprWorkflowAgentRunner` for durable execution
- Triggered a crash mid-pipeline and saw Catalyst recover automatically on restart
- Verified that completed steps were not re-executed — only the remaining steps ran

**Pydantic AI**

In this quickstart, you:

- Wrapped a standard **Pydantic AI** agent with `DaprWorkflowAgentRunner` for durable execution
- Triggered a crash mid-pipeline and saw Catalyst recover automatically on restart
- Verified that completed steps were not re-executed — only the remaining steps ran

**Microsoft Agent Framework (.NET)**

In this quickstart, you:

- Built a **Microsoft Agent Framework** agent workflow in .NET with `Diagrid.AI.Microsoft.AgentFramework` for durable execution
- Triggered a crash mid-workflow and saw Catalyst recover automatically on restart
- Verified that completed steps were not re-executed — only the remaining steps ran

**Spring AI**

In this quickstart, you:

- Made a standard **Spring AI** `ChatClient` durable by adding the `diagrid-spring-ai-starter` dependency — no code changes
- Triggered a crash mid-workflow and saw Catalyst recover automatically on restart
- Verified that completed steps were not re-executed — only the remaining steps ran

**Deep Agents**

In this quickstart, you:

- Wrapped a standard **LangChain Deep Agents** agent with `DaprWorkflowDeepAgentRunner` for durable execution
- Triggered a crash mid-pipeline and saw Catalyst recover automatically on restart
- Verified that completed steps were not re-executed — only the remaining steps ran

**Deep Agents (Sub Agents)**

In this quickstart, you:

- Ran a **supervisor agent** that orchestrates two specialist sub-agents using `AsyncSubAgent`
- Deployed each sub-agent as an **independent durable Dapr workflow** with its own checkpoint state
- Coordinated agents over **HTTP via the Agent Protocol** — each agent runs in its own process
- Observed multi-agent workflow execution in the Catalyst console

**Claude Agents**

In this quickstart, you:

- Wrapped a standard **Claude Agent SDK** agent with `DaprWorkflowAgentRunner` for durable execution
- Triggered a crash mid-pipeline and saw Catalyst recover automatically on restart
- Verified that completed steps were not re-executed — only the remaining steps ran

**HolmesGPT**

In this quickstart, you:

- Wrapped a standard **HolmesGPT** SRE investigation with `DaprWorkflowHolmesRunner` for durable execution
- Parked the investigation on a human approval, then killed and restarted the process
- Verified the workflow survived the restart and resumed from the parked tool call instead of re-investigating

**Mastra**

In this quickstart, you:

- Wrapped a standard **Mastra** agent with `DaprWorkflowAgentRunner` for durable execution
- Triggered a crash mid-turn — inside a model call, not just a tool call — and saw Catalyst recover automatically on restart
- Verified that a completed activity was not re-executed — only the interrupted model call ran again

## Next Steps

**Dapr Agents**

- Explore the [Durable Agent Quickstart](https://docs.diagrid.io/develop/agents/dapr-agents/durable-agent-quickstart) for a richer example with parallel tool execution
- Try the [Multi-Agent Quickstart](https://docs.diagrid.io/develop/agents/multi-agent-quickstart) to orchestrate multiple cooperating agents
- Learn how to [deploy AI agents to Kubernetes](https://docs.diagrid.io/develop/agents/kubernetes-deploy)

**CrewAI**

- Learn how to build durable workflows with [CrewAI + Dapr](https://docs.diagrid.io/develop/agents/crewai/)
- Set up [access policies](https://docs.diagrid.io/develop/agents/crewai/crewai-access-policies) to secure your agent
- Learn how to [deploy AI agents to Kubernetes](https://docs.diagrid.io/develop/agents/kubernetes-deploy)

**LangGraph**

- Learn how to build durable workflows with [LangGraph + Dapr](https://docs.diagrid.io/develop/agents/langgraph/)
- Set up [access policies](https://docs.diagrid.io/develop/agents/langgraph/langgraph-access-policies) to secure your agent
- Learn how to [deploy AI agents to Kubernetes](https://docs.diagrid.io/develop/agents/kubernetes-deploy)

**Strands**

- Learn how to build durable workflows with [Strands + Dapr](https://docs.diagrid.io/develop/agents/strands/)
- Set up [access policies](https://docs.diagrid.io/develop/agents/strands/strands-access-policies) to secure your agent
- Learn how to [deploy AI agents to Kubernetes](https://docs.diagrid.io/develop/agents/kubernetes-deploy)

**OpenAI Agents**

- Learn how to build durable workflows with [OpenAI Agents + Dapr](https://docs.diagrid.io/develop/agents/openai/)
- Set up [access policies](https://docs.diagrid.io/develop/agents/openai/openai-access-policies) to secure your agent
- Learn how to [deploy AI agents to Kubernetes](https://docs.diagrid.io/develop/agents/kubernetes-deploy)

**Google ADK**

- Learn how to build durable workflows with [Google ADK + Dapr](https://docs.diagrid.io/develop/agents/adk/)
- Set up [access policies](https://docs.diagrid.io/develop/agents/adk/adk-access-policies) to secure your agent
- Learn how to [deploy AI agents to Kubernetes](https://docs.diagrid.io/develop/agents/kubernetes-deploy)

**Pydantic AI**

- Learn how to build durable workflows with [Pydantic AI + Dapr](https://docs.diagrid.io/develop/agents/pydantic-ai/)
- Set up [access policies](https://docs.diagrid.io/develop/agents/pydantic-ai/pydantic-ai-access-policies) to secure your agent
- Learn how to [deploy AI agents to Kubernetes](https://docs.diagrid.io/develop/agents/kubernetes-deploy)

**Microsoft Agent Framework (.NET)**

- Learn more about the [Microsoft Agent Framework + Dapr](https://docs.diagrid.io/develop/agents/microsoft/)
- Learn how to [deploy AI agents to Kubernetes](https://docs.diagrid.io/develop/agents/kubernetes-deploy)

**Spring AI**

- Learn more about [Spring AI + Dapr](https://docs.diagrid.io/develop/agents/spring-ai/)
- Learn how to [deploy AI agents to Kubernetes](https://docs.diagrid.io/develop/agents/kubernetes-deploy)

**Deep Agents**

- Learn how to build durable workflows with [Deep Agents + Dapr](https://docs.diagrid.io/develop/agents/deepagents/)
- Learn how to [deploy AI agents to Kubernetes](https://docs.diagrid.io/develop/agents/kubernetes-deploy)

**Deep Agents (Sub Agents)**

- Try the [Durable Workflows quickstart](?agentframework=deepagents) to see crash recovery in action with Deep Agents
- Explore the [Deep Agents + Dapr overview](https://docs.diagrid.io/develop/agents/deepagents/) for more integration patterns
- Learn how to [deploy AI agents to Kubernetes](https://docs.diagrid.io/develop/agents/kubernetes-deploy)

**Claude Agents**

- Explore the [Claude Agent SDK + Dapr integration](https://github.com/diagridio/python-ai/tree/main/diagrid/agent/claude_agents) in the python-ai repository
- Learn how to [deploy AI agents to Kubernetes](https://docs.diagrid.io/develop/agents/kubernetes-deploy)

**HolmesGPT**

- Learn how to build durable workflows with [HolmesGPT + Dapr Workflow](https://docs.diagrid.io/develop/agents/holmesgpt/)
- Explore the [HolmesGPT examples](https://github.com/diagridio/python-ai/tree/main/examples/holmesgpt) in the python-ai repository
- Learn how to [deploy AI agents to Kubernetes](https://docs.diagrid.io/develop/agents/kubernetes-deploy)

**Mastra**

- Explore the [Mastra adapter](https://github.com/diagridio/typescript-ai/tree/main/packages/mastra) in the typescript-ai repository
- Learn how to [deploy AI agents to Kubernetes](https://docs.diagrid.io/develop/agents/kubernetes-deploy)
