Skip to main content

Quickstart: Durable Memory

Build a durable Spring AI chat agent whose conversation is persisted with a MessageChatMemoryAdvisor, and see exactly what durability does and does not cover: the Dapr Workflow (model + tools) survives a crash, but the memory advisor — like every Spring AI response advisor — records the assistant's reply only after a successful call.

You will learn how to:

  • Back Spring AI chat memory with a Dapr state store using dapr-spring-ai-memory
  • Recognize that Spring AI runs advisors synchronously — a response advisor's phase runs on the caller thread after the call returns
  • See that a crash keeps the durable workflow but skips the response phase, so the answer isn't saved until you re-attach
  • Complete the advisor chain by re-issuing the call under the same instance id

Prerequisites

1. Log in to Catalyst

diagrid login
diagrid whoami

2. Clone and Navigate

git clone https://github.com/diagridio/catalyst-quickstarts.git
cd catalyst-quickstarts/agents/spring-ai/durable-memory

3. Explore the Code

The agent is ordinary Spring AI. The MessageChatMemoryAdvisor is a response advisor: its before phase saves the user message, its after phase saves the assistant reply — and after runs only if the call returns successfully.

public MemoryChatController(ChatClient.Builder builder, ChatMemory chatMemory) {
this.chatMemory = chatMemory;
this.chat = builder
.defaultSystem(SYSTEM)
.defaultAdvisors(MessageChatMemoryAdvisor.builder(chatMemory).build())
.build();
}

@PostMapping("/chat")
public ResponseEntity<String> chat(@RequestParam String id,
@RequestParam String conversationId,
@RequestParam String message) {
String answer = chat.prompt()
.user(message)
.advisors(a -> a
.param(DurableAdvisor.INSTANCE_ID_KEY, id) // durability handle (re-attach)
.param(ChatMemory.CONVERSATION_ID, conversationId)) // memory grouping key
.call()
.content();
return ResponseEntity.ok(answer);
}

Chat memory is backed by a Dapr state store via dapr-spring-ai-memory — the Catalyst-managed agent-memory store (application.properties):

dapr.spring-ai.memory.enabled=true
dapr.spring-ai.memory.statestore=agent-memory

4. Configure API Key

export OPENAI_API_KEY="your-openai-api-key"

5. Install Dependencies

mvn package -DskipTests

6. Run with Catalyst Cloud

Create the Catalyst project with agent infrastructure enabled (and set it as the default for this session), register the agent, then run:

diagrid project create spring-ai-durable-memory --enable-agent-infrastructure --wait --use
diagrid agent create spring-ai-durable-memory --wait
diagrid dev run -f dev-spring-ai-durable-memory.yaml --approve

7. See "response advisors run after a success call"

7.1 Start a durable turn (it blocks on a slow tool)

From a new terminal — the booking runs under an instance id you own (trip-1) and a conversationId (alice), and blocks ~30s while the tool "commits":

curl -X POST "http://localhost:8080/chat?id=trip-1&conversationId=alice&message=Book%20a%20flight%20to%20Oslo,%20reference%20OSLO-1"

7.2 Inspect memory

In a third terminal, while the call is still blocked:

curl "http://localhost:8080/history?conversationId=alice"

Only the user question is there — before() saved it, but after() (which saves the answer) hasn't run yet:

["USER: Book a flight to Oslo, reference OSLO-1"]

7.3 Crash the app mid-call

curl -X POST "http://localhost:8080/crash/kill"

The process dies. The workflow is safe in Catalyst — but the memory advisor's after phase never ran.

7.4 Restart and re-check memory

diagrid dev run -f dev-spring-ai-durable-memory.yaml --approve
curl "http://localhost:8080/history?conversationId=alice"
# ["USER: Book a flight to Oslo, reference OSLO-1"] — still no answer

7.5 Re-attach — now the response advisor runs

Re-issue the same call with the same instance id. It attaches to the resumed workflow, returns successfully, and only now does after() record the answer:

curl -X POST "http://localhost:8080/chat?id=trip-1&conversationId=alice&message=Book%20a%20flight%20to%20Oslo,%20reference%20OSLO-1"
curl "http://localhost:8080/history?conversationId=alice"
[
"USER: Book a flight to Oslo, reference OSLO-1",
"USER: Book a flight to Oslo, reference OSLO-1",
"ASSISTANT: Booking OSLO-1 confirmed. Confirmation code: BK-..."
]

The assistant answer appears only after the successful call. The question appears twice because before() runs on every attempt (the crashed one and the re-attach) — a reminder that advisor phases run per call, while the workflow is what's durable.

tip

Put anything that must survive a crash inside the workflow — as a @Tool activity, whose result is checkpointed. Advisor response-phase effects (memory, logging, post-processing) are best-effort on top of a successful call; make retries safe with a caller-owned instance id.

8. View in the Catalyst Web Console

Open the Catalyst Cloud web console, go to Workflows, and inspect the instance to see the model and tool activities that were checkpointed and replayed on recovery.

9. Clean Up

Stop the app with Ctrl+C, then delete the project:

diagrid project delete spring-ai-durable-memory

Summary

In this quickstart, you:

  • Backed Spring AI chat memory with a Dapr state store via dapr-spring-ai-memory
  • Saw that MessageChatMemoryAdvisor persists the assistant reply in its response phase — only after a successful call
  • Confirmed the durability boundary: the workflow survives a crash, but the caller-side advisor chain runs only on success
  • Used a caller-owned instance id to re-attach and complete the advisor chain

Next Steps


Spring is a trademark of Broadcom Inc. and/or its subsidiaries.