back to blog & resources
Blog

Bilgin Ibryam

|

December 3, 2025

Durable Agentic Workflows with Dapr

Workflows have been around since the early days of software automation. From complex business process orchestration to microservices coordination, they’ve always been about defining a sequence of deterministic steps that lead to a predictable outcome. But as AI agents enter the picture, we are witnessing a new kind of workflow that reasons, adapts, and evolves dynamically, yet preserves the benefits of durability, reliability, and auditability.


In this post, we’ll show step-by-step how to turn a deterministic workflow into an agentic one. We will explain what makes an agent durable, and what makes a workflow agentic

What is a Workflow?

A workflow is a collection of connected steps designed to achieve a goal. Traditional workflows are deterministic as they follow predefined logic and can be easily modeled as state machines. Modern LLMs and Generative AI bring new flexibility, allowing parts of the workflow to be expressed in natural language and dynamically adapted to inputs while still keeping the underlying deterministic flow.

Here’s a simple Python workflow example using Dapr Workflows to chain two LLM activities. This workflow finds a character from the Lord of the Rings with the first LLM call and then makes a second LLM call to get a famous line for that character

@runtime.workflow(name="lotr_quote_workflow")
def lotr_quote_workflow(ctx: DaprWorkflowContext):
    character = yield ctx.call_activity(get_character)
    line = yield ctx.call_activity(get_line, input={"character": character})
    return line

@runtime.activity
@llm_activity(prompt="""
Pick a random character from The Lord of the Rings. 
Respond with the character's name only. """, llm=llm)
def get_character(ctx) -> str:
    pass

@runtime.activity
@llm_activity(prompt="What is a famous line by {character}?", llm=llm)
def get_line(ctx, character: str) -> str:
    pass

This workflow remains fully deterministic; the orchestration and state transitions are predictable (it finds a character first and then a quote second), but it calls LLMs at the edges to add adaptability using LLM to handle the inputs. The @llm_activity decorator in Dapr Agents makes it seamless to turn any workflow activity into a reliable, observable LLM client call, using Dapr’s Conversation API behind the scenes.

What is an Agent?

In the workflow above, we called LLMs twice, but technically, there is no AI agent. The industry definition of an agent is:

“An LLM agent runs tools in a loop to achieve a goal.”

A more complete definition of an AI agent is “a goal-driven program that can reason, plan, and act using a combination of LLMs, tools, and memory.” In essence, an agent is code that has the autonomy to perform multiple turns of an LLM and tool calls with the ability to preserve that interaction history in its memory. 

Let’s extend the previous workflow example by turning the quote discovery logic into a simple agent with a tool that can validate the characters:

@runtime.activity
@llm_activity(prompt="""
Pick a random character from The Lord of the Rings. 
Respond with the character's name only. """, llm=llm)
def get_character(ctx) -> str:
    pass

lotr_agent = Agent(
    name="lotr_character_agent",
    role="Character Picker",
    goal="Pick and validate a random Lord of the Rings character",
    instructions=["Pick a random LOTR character","Validate that the character exists in the story"],
    tools=[character_validator_tool],

    # Dapr conversation API for LLM interactions
    llm=DaprChatClient(component_name="openai"),

    # Memory for keeping context or past picks
    memory=ConversationDaprStateMemory(store_name="memory-state", session_id="lotr-session"),
)
result = await lotr_agent.run("Pick a random LOTR character")

This example shows how a simple LLM call can be turned into an agent by introducing a tool, and a memory. The LLM reasoning is now part of an agentic loop with the ability to make decisions, validate its outputs, and iterate as many times as required to achieve its goal.

This agent can also simply be embedded into the previous workflow lotr_quote_workflow using the following syntax:

@runtime.activity
@agent_activity(agent=lotr_agent)
def get_validated_character(ctx) -> dict:
    pass

In this architecture, the agent acts as a local decision-maker, not a global one. The workflow still defines the business process logic and orchestration, while the agent focuses on localized reasoning and action.

The agent has a key advantage over plain LLM call: it ensures that every character generated by the LLM is valid by calling a deterministic validation tool, such as one checking against a database of known Lord of the Rings characters. The agent can continue generating random characters until it finds a valid result, unlike the first version, which made only a single LLM call without validation.

What is an Agentic Workflow?

Now let’s adapt from a deterministic workflow to an agentic workflow. An agentic workflow is a workflow created or managed by an agent. Instead of following a fixed deterministic path that is hard-coded, the agent dynamically plans steps, executes actions, reflects on outcomes, and adjusts its plan until a goal is achieved. It’s an adaptive orchestration where the agent acts as both the planner and executor of the workflow.

Here’s a reimplementation of the earlier workflow example, this time using a DurableAgent class from Dapr Agents, but rather than hard-coding the steps, we define a goal and give the necessary tools and instructions to achieve it:

lotr_durable_agent = DurableAgent(
name="lotr_quote_agent",
       role="Character Quote Finder",
       goal="Pick a valid Lord of the Rings character, find a famous quote, and validate it",
       instructions=[
            "Pick a random Lord of the Rings character",
            "Validate that the character exists in the story",
            "If valid, ask the LLM for a famous quote from that character",
            "Validate that the quote belongs to that character before returning the final result"
       ],
       tools=[validate_character, validate_quote],
memory = AgentMemoryConfig(
       	store=ConversationDaprStateMemory(
           		store_name="memory-state",
             		session_id="lotr-durable-session",
        	)
    	)
    	state = AgentStateConfig(
        	store=StateStoreService(store_name="durable-agent-state"),
    	)
)
    
result = await runner.run(
lotr_durable_agent, payload={"task": "Give me a famous quote"},
)

This durable agent extends the previous workflow example by also adding a quote validation tool. It selects a random character, validates it using validate_character tool, then picks a quote, and uses the validate_quote tool to confirm the quote matches the character before returning the final result. 

While the interface to define the agent is simple, behind the scenes every reasoning step, tool invocation, and workflow transition is executed through Dapr’s workflow runtime. This means each step is stored in the state store, making the agent’s behavior durable, fault-tolerant, and recoverable. The result is a reliable, long-running automation model powered by Dapr Durable Agents. This is critical for taking an agent into production. Agents that are not durable and fault tolerant will cost you more money, and cannot be relied on to complete their goals. It is like a person getting half way through a task and then starting from the beginning when things go wrong.

As a result of this design, the same agent can follow multiple possible execution paths adapting to the user input. It can find a quote for a single character, or quotes for multiple characters at once.

Agentic Workflow adapting to use queries

In the image above the agent workflow visualization in Diagrid Catalyst shows two different possible inputs and outcomes of this durable agent’s execution:

  • In the first case on the left, the agent is asked to find a single character, validates it, followed by a quote that is validated.
  • In the second case on the right, the agent is asked to find two quotes, by performing two character selections, followed by two quote generations, demonstrating both parallel tool execution, and sequential reasoning of character and quote generation.

Durability is a key enabler of these workflows. Unlike in-memory agent frameworks where the reasoning state and conversation history are lost after a crash, persistence gives agents real-world resilience. Durable agents handle failures gracefully, maintain long-running context, and effectively handle unreliable cloud infrastructure. In essence, durability transforms intelligent, but ephemeral agents, into reliable automation that can recover, resume, and continue reasoning seamlessly.

Diagrid Catalyst. The platform for Durable Agents and Workflows

Catalyst is a complete platform for creating durable agentic applications. It gives agents durability by preserving execution state and conversation memory across failures. It also provides full visibility into agent execution where every step, LLM call, and tool call can be inspected with inputs and outputs, and helps with troubleshooting and explaining agent behavior.

Inspecting Workflow Steps in Catalyst

Catalyst acts as an operationally focused AI gateway with secured LLM access through managed secrets, consistent resiliency policies, request logging, latency and error metrics, and complete visibility into all agent and LLM traffic. It includes an agent runtime catalogue where every Dapr based agent is automatically discovered and exposed with its metadata, sessions, executions, and tool activity. This helps teams explain, debug, and operate agent behaviour with confidence. Sign up to Catalyst and create your first durable agent here: 

https://www.diagrid.io/catalyst

Conclusion

Deterministic workflows combined with local LLM or agent logic create a powerful approach for building reliable and adaptable systems. Deterministic workflows provide structure, traceability, and reproducibility. Adding LLM-backed reasoning or an agent at specific steps makes those AI workflows more context-aware and adaptable without sacrificing control.

From deterministic to agentic workflows

Agentic workflows extend this further. When backed by durable execution, they gain flexibility to handle tasks where adaptability matters more than strict determinism. The combination of deterministic orchestration and agentic reasoning creates an adaptive system that can follow predefined processes yet intelligently adapt to dynamic inputs.

With Dapr Agents, both deterministic and adaptive scenarios are powered by Dapr Workflows, providing durable execution. This means every reasoning step, tool invocation, and decision is stored and observable, bringing the reliability, visibility, and traceability that production-grade agentic applications demand.

Catalyst gives you the fastest path to creating a durable agent and operating it reliably in production. Use this quickstart to deploy your first agent.

Want to go deeper or try this in your environment?

  • Join the Dapr Discord to connect with the community.
  • Sign up to Catalyst and deploy your first durable agent or workflow (<- provide links to quickstarts for both)
  • Reach out to Diagrid for guidance, support, and enterprise Dapr expertise.

Explore the Dapr University self-paced courses on Dapr Workflow and Dapr Agents.

No items found.