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.
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.
Prerequisites
- Diagrid Catalyst account
- Diagrid CLI
- Python 3.12 or 3.13, or .NET 10 SDK for the Microsoft Agent Framework tab, or JDK 21 and Maven 3.9+ for the Spring AI tab
- uv for the Python-based quickstarts
- An OpenAI API key (or an Anthropic API key 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
diagrid login
Confirm your identity:
diagrid whoami
2. Clone and Navigate
git clone https://github.com/diagridio/catalyst-quickstarts.git
Navigate to the quickstart directory for your framework:
- Dapr Agents
- CrewAI
- LangGraph
- Strands
- OpenAI Agents
- Google ADK
- Pydantic AI
- Microsoft Agent Framework (.NET)
- Spring AI
- Deep Agents
- Deep Agents (Sub Agents)
- Claude Agents
- HolmesGPT
cd catalyst-quickstarts/agents/dapr-agents/durable-agent
cd catalyst-quickstarts/agents/crewai
cd catalyst-quickstarts/agents/langgraph
cd catalyst-quickstarts/agents/strands
cd catalyst-quickstarts/agents/openai-agents
cd catalyst-quickstarts/agents/adk
cd catalyst-quickstarts/agents/pydantic-ai
cd catalyst-quickstarts/agents/microsoft-dotnet
cd catalyst-quickstarts/agents/spring-ai/event-planner
cd catalyst-quickstarts/agents/deepagents
cd catalyst-quickstarts/agents/deepagents
cd catalyst-quickstarts/agents/claude-agents
HolmesGPT has no directory in catalyst-quickstarts yet. Use the runnable examples in the python-ai repository instead:
git clone https://github.com/diagridio/python-ai.git
cd python-ai/examples/holmesgpt
3. Explore the Code
- Dapr Agents
- CrewAI
- LangGraph
- Strands
- OpenAI Agents
- Google ADK
- Pydantic AI
- Microsoft Agent Framework (.NET)
- Spring AI
- Deep Agents
- Deep Agents (Sub Agents)
- Claude Agents
- HolmesGPT
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:
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:
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")))
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.
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:
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:
runner = DaprWorkflowAgentRunner(
name="venue-scout",
agent=agent,
max_iterations=10,
)
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.
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:
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:
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.
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.
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:
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:
runner = DaprWorkflowAgentRunner(
name="budget-planner",
agent=agent,
max_iterations=10,
)
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.
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:
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:
runner = DaprWorkflowAgentRunner(
name="catering-coordinator",
agent=agent,
max_iterations=10,
)
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.
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:
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:
runner = DaprWorkflowAgentRunner(
name="entertainment-planner",
agent=agent,
max_iterations=10,
)
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.
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:
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:
runner = DaprWorkflowAgentRunner(
name="decoration-planner",
agent=agent,
max_iterations=10,
)
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.
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.
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:
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.
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;
}
}
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.
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:
@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:
<dependency>
<groupId>io.diagrid</groupId>
<artifactId>diagrid-spring-ai-starter</artifactId>
<version>0.2.0</version>
</dependency>
0.2.0is the version this quickstart pins. For newer releases see Maven Central.
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:
@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:
@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) {}
}
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.
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:
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:
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,
)
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.
Sub-Agent Orchestration — a supervisor agent coordinates two specialist sub-agents (Researcher and Analyst), each running as an independent durable Dapr workflow.
Open subagent_workflows.py. Each sub-agent has its own tools and runs as a separate DaprWorkflowDeepAgentRunner:
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:
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.
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.
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:
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:
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,
)
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.
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:
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.
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.
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
- CrewAI
- LangGraph
- Strands
- OpenAI Agents
- Google ADK
- Pydantic AI
- Microsoft Agent Framework (.NET)
- Spring AI
- Deep Agents
- Deep Agents (Sub Agents)
- Claude Agents
- HolmesGPT
The LLM is configured via DaprChatClient(component_name="llm-provider") — a Dapr component in resources/llm-provider.yaml that references your OpenAI API key:
apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
name: llm-provider
spec:
type: conversation.openai
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.
- macOS/Linux
- Windows
export OPENAI_API_KEY="your-key-here"
$env:OPENAI_API_KEY = "your-key-here"
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.
- macOS/Linux
- Windows
export OPENAI_API_KEY="your-key-here"
$env:OPENAI_API_KEY = "your-key-here"
- macOS/Linux
- Windows
export OPENAI_API_KEY="your-key-here"
$env:OPENAI_API_KEY = "your-key-here"
- macOS/Linux
- Windows
export OPENAI_API_KEY="your-key-here"
$env:OPENAI_API_KEY = "your-key-here"
- macOS/Linux
- Windows
export OPENAI_API_KEY="your-key-here"
$env:OPENAI_API_KEY = "your-key-here"
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
- Windows
export DIAGRID_QUICKSTART_MODEL="openai"
export OPENAI_API_KEY="your-key-here"
$env:DIAGRID_QUICKSTART_MODEL = "openai"
$env:OPENAI_API_KEY = "your-key-here"
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
- Windows
export DIAGRID_QUICKSTART_MODEL="openai"
export OPENAI_API_KEY="your-key-here"
$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.
- macOS/Linux
- Windows
export OPENAI_API_KEY="your-key-here"
$env:OPENAI_API_KEY = "your-key-here"
- macOS/Linux
- Windows
export OPENAI_API_KEY="your-key-here"
$env:OPENAI_API_KEY = "your-key-here"
- macOS/Linux
- Windows
export ANTHROPIC_API_KEY="your-key-here"
$env:ANTHROPIC_API_KEY = "your-key-here"
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
- Windows
export OPENAI_API_KEY="your-key-here"
export MODEL="gpt-4.1-2025-04-14"
$env:OPENAI_API_KEY = "your-key-here"
$env:MODEL="gpt-4.1-2025-04-14"
5. Install Dependencies
- Dapr Agents
- CrewAI
- LangGraph
- Strands
- OpenAI Agents
- Google ADK
- Pydantic AI
- Microsoft Agent Framework (.NET)
- Spring AI
- Deep Agents
- Deep Agents (Sub Agents)
- Claude Agents
- HolmesGPT
uv sync
uv sync
uv sync
uv sync
uv sync
uv sync
uv sync
dotnet build
mvn package -DskipTests
uv sync
uv sync
uv sync
python -m venv .venv
source .venv/bin/activate
pip install "diagrid[holmesgpt]"
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].
6. Create Catalyst Project and Agent
Set up the Catalyst project, then register the agent.
- Dapr Agents
- CrewAI
- LangGraph
- Strands
- OpenAI Agents
- Google ADK
- Pydantic AI
- Microsoft Agent Framework (.NET)
- Spring AI
- Deep Agents
- Deep Agents (Sub Agents)
- Claude Agents
Create a Catalyst project with managed workflow, key-value, and pub/sub enabled, and set it as the default for this session.
diagrid project create durable-agent-quickstart --enable-managed-workflow --deploy-managed-kv --deploy-managed-pubsub --wait --use
Register the agent with the project.
diagrid agent create invitations-manager --wait
Create a Catalyst project with managed workflow, key-value, and pub/sub enabled, and set it as the default for this session.
diagrid project create crewai-quickstart --enable-managed-workflow --deploy-managed-kv --deploy-managed-pubsub --wait --use
Register the agent with the project.
diagrid agent create venue-scout --wait
Create a Catalyst project with managed workflow, key-value, and pub/sub enabled, and set it as the default for this session.
diagrid project create langgraph-quickstart --enable-managed-workflow --deploy-managed-kv --deploy-managed-pubsub --wait --use
Register the agent with the project.
diagrid agent create schedule-planner --wait
Create a Catalyst project with managed workflow, key-value, and pub/sub enabled, and set it as the default for this session.
diagrid project create strands-quickstart --enable-managed-workflow --deploy-managed-kv --deploy-managed-pubsub --wait --use
Register the agent with the project.
diagrid agent create budget-planner --wait
Create a Catalyst project with managed workflow, key-value, and pub/sub enabled, and set it as the default for this session.
diagrid project create openai-quickstart --enable-managed-workflow --deploy-managed-kv --deploy-managed-pubsub --wait --use
Register the agent with the project.
diagrid agent create catering-coordinator --wait
Create a Catalyst project with managed workflow, key-value, and pub/sub enabled, and set it as the default for this session.
diagrid project create adk-quickstart --enable-managed-workflow --deploy-managed-kv --deploy-managed-pubsub --wait --use
Register the agent with the project.
diagrid agent create entertainment-planner --wait
Create a Catalyst project with managed workflow, key-value, and pub/sub enabled, and set it as the default for this session.
diagrid project create pydantic-ai-quickstart --enable-managed-workflow --deploy-managed-kv --deploy-managed-pubsub --wait --use
Register the agent with the project.
diagrid agent create decoration-planner --wait
Create a Catalyst project with managed workflow, key-value, and pub/sub enabled, and set it as the default for this session.
diagrid project create dotnet-quickstart --enable-managed-workflow --deploy-managed-kv --deploy-managed-pubsub --wait --use
Register the agent with the project.
diagrid agent create event-planner --wait
Create a Catalyst project with managed workflow enabled, and set it as the default for this session.
diagrid project create spring-ai-quickstart --enable-managed-workflow --deploy-managed-kv --wait --use
Register the agent with the project.
diagrid agent create spring-ai-event-planner --wait
Create a Catalyst project with managed workflow, key-value, and pub/sub enabled, and set it as the default for this session.
diagrid project create deepagents-quickstart --enable-managed-workflow --deploy-managed-kv --deploy-managed-pubsub --wait --use
Register the agent with the project.
diagrid agent create transportation-planner --wait
Create a Catalyst project with managed workflow, key-value, and pub/sub enabled, and set it as the default for this session.
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.
diagrid agent create researcher --wait
diagrid agent create analyst --wait
diagrid agent create supervisor --wait
Create a Catalyst project with managed workflow, key-value, and pub/sub enabled, and set it as the default for this session.
diagrid project create claude-quickstart --enable-managed-workflow --deploy-managed-kv --deploy-managed-pubsub --wait --use
Register the agent with the project.
diagrid agent create photography-planner --wait
7. Run with Catalyst Cloud
- Dapr Agents
- CrewAI
- LangGraph
- Strands
- OpenAI Agents
- Google ADK
- Pydantic AI
- Microsoft Agent Framework (.NET)
- Spring AI
- Deep Agents
- Deep Agents (Sub Agents)
- Claude Agents
- HolmesGPT
uv run diagrid dev run -f dev-python-durable-agent.yaml --approve
uv run diagrid dev run -f dev-crash-test.yaml --approve
uv run diagrid dev run -f dev-crash-test.yaml --approve
uv run diagrid dev run -f dev-crash-test.yaml --approve
uv run diagrid dev run -f dev-crash-test.yaml --approve
uv run diagrid dev run -f dev-crash-test.yaml --approve
uv run diagrid dev run -f dev-crash-test.yaml --approve
diagrid dev run -f dev-dotnet-agent.yaml --approve
diagrid dev run -f dev-spring-ai-event-planner.yaml --approve
uv run diagrid dev run -f dev-crash-test.yaml --approve
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:
== 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...
uv run diagrid dev run -f dev-crash-test.yaml --approve
The examples repository has no multi-app run file, so pass the app details and the command directly:
diagrid dev run --project holmesgpt-agent-qs \
--id holmes-server --app-port 5001 --approve \
-- python server.py
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, 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
- CrewAI
- LangGraph
- Strands
- OpenAI Agents
- Google ADK
- Pydantic AI
- Microsoft Agent Framework (.NET)
- Spring AI
- Deep Agents
- Deep Agents (Sub Agents)
- Claude Agents
- HolmesGPT
- macOS/Linux
- Windows
- VS Code REST Client
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"}'
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"}'
Open test.http and click Send Request above the request. Requires the REST Client extension.
Expected output:
== APP - invitations-manager == Invitations sent: 70 via email, 30 via physical mail
- macOS/Linux
- Windows
- VS Code REST Client
curl -X POST http://localhost:8001/run \
-H "Content-Type: application/json" \
-d '{"prompt": "Find a venue in Austin for a company gala"}'
Invoke-RestMethod -Method Post -Uri 'http://localhost:8001/run' -ContentType 'application/json' -Body '{"prompt": "Find a venue in Austin for a company gala"}'
Open test.http and click Send Request above the request. Requires the REST Client extension.
You'll see step 1 complete, then the process crashes at step 2:
== 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.
- macOS/Linux
- Windows
- VS Code REST Client
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}'
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}'
Open 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 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.
== 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
- macOS/Linux
- Windows
- VS Code REST Client
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"}'
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"}'
Open test.http and click Send Request above the request. Requires the REST Client extension.
You'll see step 1 complete, then the process crashes at step 2:
== 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.
- macOS/Linux
- Windows
- VS Code REST Client
curl -X POST http://localhost:8001/run \
-H "Content-Type: application/json" \
-d '{"prompt": "Find catering for a corporate gala"}'
Invoke-RestMethod -Method Post -Uri 'http://localhost:8001/run' -ContentType 'application/json' -Body '{"prompt": "Find catering for a corporate gala"}'
Open test.http and click Send Request above the request. Requires the REST Client extension.
You'll see step 1 complete, then the process crashes at step 2:
== 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.
- macOS/Linux
- Windows
- VS Code REST Client
curl -X POST http://localhost:8001/run \
-H "Content-Type: application/json" \
-d '{"prompt": "Find entertainment for a corporate holiday party"}'
Invoke-RestMethod -Method Post -Uri 'http://localhost:8001/run' -ContentType 'application/json' -Body '{"prompt": "Find entertainment for a corporate holiday party"}'
Open test.http and click Send Request above the request. Requires the REST Client extension.
You'll see step 1 complete, then the process crashes at step 2:
== 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.
- macOS/Linux
- Windows
- VS Code REST Client
curl -X POST http://localhost:8001/run \
-H "Content-Type: application/json" \
-d '{"prompt": "Find decorations for a garden wedding theme"}'
Invoke-RestMethod -Method Post -Uri 'http://localhost:8001/run' -ContentType 'application/json' -Body '{"prompt": "Find decorations for a garden wedding theme"}'
Open test.http and click Send Request above the request. Requires the REST Client extension.
You'll see step 1 complete, then the process crashes at step 2:
== 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.
- macOS/Linux
- Windows
- VS Code REST Client
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}'
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}'
Open test.http and click Send Request above the request. Requires the REST Client extension.
Tool 2 takes about 30 seconds, so this request takes about half a minute and all three tools complete:
== 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
- macOS/Linux
- Windows
- VS Code REST Client
curl -X POST http://localhost:8080/run \
-H "Content-Type: application/json" \
-d '{"prompt": "Find a venue in Austin for a company gala"}'
Invoke-RestMethod -Method Post -Uri 'http://localhost:8080/run' -ContentType 'application/json' -Body '{"prompt": "Find a venue in Austin for a company gala"}'
Open test.http and click Send Request above the request. Requires the REST Client extension.
You'll see tool 1 complete, then the process crashes at tool 2:
== 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.
- macOS/Linux
- Windows
- VS Code REST Client
curl -X POST http://localhost:8001/run \
-H "Content-Type: application/json" \
-d '{"prompt": "Find transportation for a corporate gala with 200 guests"}'
Invoke-RestMethod -Method Post -Uri 'http://localhost:8001/run' -ContentType 'application/json' -Body '{"prompt": "Find transportation for a corporate gala with 200 guests"}'
Open test.http and click Send Request above the request. Requires the REST Client extension.
You'll see step 1 complete, then the process crashes at step 2:
== 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.
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.
- macOS/Linux
- Windows
- VS Code REST Client
curl -X POST http://localhost:8001/run \
-H "Content-Type: application/json" \
-d '{"prompt": "Find photography for a corporate gala"}'
Invoke-RestMethod -Method Post -Uri 'http://localhost:8001/run' -ContentType 'application/json' -Body '{"prompt": "Find photography for a corporate gala"}'
Open test.http and click Send Request above the request. Requires the REST Client extension.
You'll see step 1 complete, then the process crashes at step 2:
== 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.
- macOS/Linux
- Windows
curl -X POST http://localhost:5001/investigations \
-H "Content-Type: application/json" \
-d '{"question": "Why is checkout-api crashlooping?"}'
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
- Windows
curl -N http://localhost:5001/investigations/<id>/stream
curl.exe -N http://localhost:5001/investigations/<id>/stream
curl.exe here and not Invoke-RestMethodThis 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.
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
- CrewAI
- LangGraph
- Strands
- OpenAI Agents
- Google ADK
- Pydantic AI
- Microsoft Agent Framework (.NET)
- Spring AI
- Deep Agents
- Deep Agents (Sub Agents)
- Claude Agents
- HolmesGPT
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 for a detailed walkthrough.
Open crash_test.py and comment out the crash line:
# os._exit(1) # 💥 comment out this line before the second run
Restart:
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:
== 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
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:
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:
== 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:
== 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.
Open crash_test.py and comment out the crash line:
# os._exit(1) # 💥 comment out this line before the second run
Restart:
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:
== 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)
Open crash_test.py and comment out the crash line:
# os._exit(1) # 💥 comment out this line before the second run
Restart:
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:
== 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
Open crash_test.py and comment out the crash line:
# os._exit(1) # 💥 comment out this line before the second run
Restart:
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:
== 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
Open crash_test.py and comment out the crash line:
# os._exit(1) # 💥 comment out this line before the second run
Restart:
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:
== 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
Restart with the same command as step 7:
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:
== 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.
Open EventPlannerTools.java and comment out the crash line in step_two_compare:
// Runtime.getRuntime().halt(1); // 💥 comment out this line before the second run
Restart:
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:
== 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
Open crash_test.py and comment out the crash line:
# os._exit(1) # 💥 comment out this line before the second run
Restart:
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:
== 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.
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) tab instead.
Open crash_test.py and comment out the crash line:
# os._exit(1) # 💥 comment out this line before the second run
Restart:
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:
== 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
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:
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
- Windows
curl -X POST http://localhost:5001/investigations/<id>/approve \
-H "Content-Type: application/json" \
-d '{"tool_call_id": "<tool-call-id>", "approved": true}'
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.
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.
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 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
- CrewAI
- LangGraph
- Strands
- OpenAI Agents
- Google ADK
- Pydantic AI
- Microsoft Agent Framework (.NET)
- Spring AI
- Deep Agents
- Deep Agents (Sub Agents)
- Claude Agents
- HolmesGPT
diagrid project delete durable-agent-quickstart
diagrid project delete crewai-quickstart
diagrid project delete langgraph-quickstart
diagrid project delete strands-quickstart
diagrid project delete openai-quickstart
diagrid project delete adk-quickstart
diagrid project delete pydantic-ai-quickstart
diagrid project delete dotnet-quickstart
diagrid project delete spring-ai-quickstart
diagrid project delete deepagents-quickstart
diagrid project delete deepagents-quickstart
diagrid project delete claude-quickstart
diagrid project delete holmesgpt-agent-qs
Summary
- Dapr Agents
- CrewAI
- LangGraph
- Strands
- OpenAI Agents
- Google ADK
- Pydantic AI
- Microsoft Agent Framework (.NET)
- Spring AI
- Deep Agents
- Deep Agents (Sub Agents)
- Claude Agents
- HolmesGPT
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
In this quickstart, you:
- Wrapped a standard CrewAI agent with
DaprWorkflowAgentRunnerfor 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
In this quickstart, you:
- Wrapped a standard LangGraph graph with
DaprWorkflowGraphRunnerfor 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
In this quickstart, you:
- Wrapped a standard Strands agent with
DaprWorkflowAgentRunnerfor 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
In this quickstart, you:
- Wrapped a standard OpenAI Agents agent with
DaprWorkflowAgentRunnerfor 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
In this quickstart, you:
- Wrapped a standard Google ADK agent with
DaprWorkflowAgentRunnerfor 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
In this quickstart, you:
- Wrapped a standard Pydantic AI agent with
DaprWorkflowAgentRunnerfor 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
In this quickstart, you:
- Built a Microsoft Agent Framework agent workflow in .NET with
Diagrid.AI.Microsoft.AgentFrameworkfor 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
In this quickstart, you:
- Made a standard Spring AI
ChatClientdurable by adding thediagrid-spring-ai-starterdependency — 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
In this quickstart, you:
- Wrapped a standard LangChain Deep Agents agent with
DaprWorkflowDeepAgentRunnerfor 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
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
In this quickstart, you:
- Wrapped a standard Claude Agent SDK agent with
DaprWorkflowAgentRunnerfor 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
In this quickstart, you:
- Wrapped a standard HolmesGPT SRE investigation with
DaprWorkflowHolmesRunnerfor 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
Next Steps
- Dapr Agents
- CrewAI
- LangGraph
- Strands
- OpenAI Agents
- Google ADK
- Pydantic AI
- Microsoft Agent Framework (.NET)
- Spring AI
- Deep Agents
- Deep Agents (Sub Agents)
- Claude Agents
- HolmesGPT
- Explore the Durable Agent Quickstart for a richer example with parallel tool execution
- Try the Multi-Agent Quickstart to orchestrate multiple cooperating agents
- Learn how to deploy AI agents to Kubernetes
- Learn how to build durable workflows with CrewAI + Dapr
- Set up access policies to secure your agent
- Learn how to deploy AI agents to Kubernetes
- Learn how to build durable workflows with LangGraph + Dapr
- Set up access policies to secure your agent
- Learn how to deploy AI agents to Kubernetes
- Learn how to build durable workflows with Strands + Dapr
- Set up access policies to secure your agent
- Learn how to deploy AI agents to Kubernetes
- Learn how to build durable workflows with OpenAI Agents + Dapr
- Set up access policies to secure your agent
- Learn how to deploy AI agents to Kubernetes
- Learn how to build durable workflows with Google ADK + Dapr
- Set up access policies to secure your agent
- Learn how to deploy AI agents to Kubernetes
- Learn how to build durable workflows with Pydantic AI + Dapr
- Set up access policies to secure your agent
- Learn how to deploy AI agents to Kubernetes
- Learn more about the Microsoft Agent Framework + Dapr
- Learn how to deploy AI agents to Kubernetes
- Learn more about Spring AI + Dapr
- Learn how to deploy AI agents to Kubernetes
- Learn how to build durable workflows with Deep Agents + Dapr
- Learn how to deploy AI agents to Kubernetes
- Try the Durable Workflows quickstart to see crash recovery in action with Deep Agents
- Explore the Deep Agents + Dapr overview for more integration patterns
- Learn how to deploy AI agents to Kubernetes
- Explore the Claude Agent SDK + Dapr integration in the python-ai repository
- Learn how to deploy AI agents to Kubernetes
- Learn how to build durable workflows with HolmesGPT + Dapr Workflow
- Explore the HolmesGPT examples in the python-ai repository
- Learn how to deploy AI agents to Kubernetes