# 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 `diagrid-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

```mermaid
sequenceDiagram
    participant C as Client (POST /chat)
    participant M as MessageChatMemoryAdvisor
    participant D as DurableAdvisor
    participant W as Catalyst Workflow
    participant S as kvstore (Dapr state)

    C->>M: ChatClient.call()
    M->>S: before() — save USER message
    M->>D: proceed down the chain
    D->>W: run model + tools as durable activities
    W-->>D: result (durable — survives a crash)
    D-->>M: response
    M->>S: after() — save ASSISTANT reply (only on success)
    M-->>C: answer
    Note over D,W: Crash here → the workflow lives on,<br/>but after() never runs, so no answer is saved.<br/>Re-attach with the same instance id to finish the chain.
```

## Prerequisites

- [Diagrid Catalyst account](https://catalyst.diagrid.io/)
- [Diagrid CLI](https://docs.diagrid.io/getting-started/install-cli)
- [JDK 21](https://adoptium.net/) or later, and [Maven 3.9+](https://maven.apache.org/download.cgi)
- [An OpenAI API key](https://platform.openai.com/api-keys)

## 1. Log in to Catalyst

```bash
diagrid login
diagrid whoami
```

## 2. Clone and Navigate

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

```java
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 `diagrid-spring-ai-memory` — the Catalyst-managed
`kvstore` store (`application.properties`):

```properties
diagrid.spring-ai.memory.enabled=true
diagrid.spring-ai.memory.statestore=kvstore
```

## 4. Configure API Key

**macOS/Linux**

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

**Windows**

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

## 5. Install Dependencies

```bash
mvn package -DskipTests
```

## 6. Run with Catalyst Cloud

Create the Catalyst project with managed workflow and a managed KV store enabled (and set it as the default for this session), register the agent, then run:

```bash
diagrid project create spring-ai-durable-memory --enable-managed-workflow --deploy-managed-kv --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":

**macOS/Linux**

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

**Windows**

```powershell
Invoke-RestMethod -Method Post -Uri '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:

**macOS/Linux**

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

**Windows**

```powershell
ConvertTo-Json ([object[]](Invoke-RestMethod -Uri 'http://localhost:8080/history?conversationId=alice')) -Compress
```

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

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

### 7.3 Crash the app mid-call

**macOS/Linux**

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

**Windows**

```powershell
Invoke-RestMethod -Method Post -Uri 'http://localhost:8080/crash/kill'
```

The process dies before it can answer, so this request reports a connection reset (a PowerShell error on Windows) rather than a status code — that is expected. The workflow is safe in Catalyst — but the memory advisor's `after` phase never ran.

### 7.4 Restart and re-check memory

```bash
diagrid dev run -f dev-spring-ai-durable-memory.yaml --approve
```

Check memory again:

**macOS/Linux**

```bash
curl "http://localhost:8080/history?conversationId=alice"
# ["USER: Book a flight to Oslo, reference OSLO-1"]   — still no answer
```

**Windows**

```powershell
ConvertTo-Json ([object[]](Invoke-RestMethod -Uri 'http://localhost:8080/history?conversationId=alice')) -Compress
# ["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:

**macOS/Linux**

```bash
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"
```

**Windows**

```powershell
Invoke-RestMethod -Method Post -Uri 'http://localhost:8080/chat?id=trip-1&conversationId=alice&message=Book%20a%20flight%20to%20Oslo,%20reference%20OSLO-1'
ConvertTo-Json ([object[]](Invoke-RestMethod -Uri 'http://localhost:8080/history?conversationId=alice'))
```

```json
[
  "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](https://catalyst.diagrid.io/), 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:

```bash
diagrid project delete spring-ai-durable-memory
```

## Summary

In this quickstart, you:

- Backed Spring AI chat memory with a Dapr state store via `diagrid-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

- Try the [durable workflows quickstart](https://docs.diagrid.io/getting-started/quickstarts/ai-agents?agentframework=spring-ai) for the drop-in crash-recovery basics
- 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)

---

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