Skip to main content

Quickstart: Crash Recovery

Run a durable Spring AI booking agent, kill it mid-call, and recover the same run by re-issuing the request under an instance id you own — so a retry attaches to the in-flight workflow instead of starting a second booking.

You will learn how to:

  • Schedule a durable ChatClient.call() under a caller-owned instance id (DurableAdvisor.INSTANCE_ID_KEY)
  • Survive a hard crash of the app (which also hosts the in-process workflow worker)
  • Re-attach to the resumed run on restart and collect its result — with no duplicate side effect
  • Make a real, side-effecting tool safe to retry

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/crash-recovery

3. Explore the Code

The booking agent is a named ChatClient bean so its run gets a per-agent workflow name. Each call sets a caller-owned instance id via DurableAdvisor.INSTANCE_ID_KEY — that id is the attach handle a retry re-uses:

@GetMapping("/crash/book")
public ResponseEntity<String> book(@RequestParam String id,
@RequestParam(defaultValue = "ABC123") String reference) {
try {
String answer = agent.prompt()
.user("Confirm the booking with reference " + reference + ".")
.advisors(a -> a.param(DurableAdvisor.INSTANCE_ID_KEY, id)) // caller-owned id → attach on retry
.call()
.content();
return ResponseEntity.ok(answer);
} catch (DurableCallTimeoutException e) {
// Wait budget elapsed (not a failure): the run is still going — re-issue the same id to attach.
return ResponseEntity.accepted().body("still running as " + e.instanceId() + " — re-issue to attach");
}
}

The booking tool is a global @Tool bean (SlowBookingTools.commitReservation) that sleeps ~30s — long enough to crash mid-call. It must be a bean (not a per-call tool) so it's re-registered on the restarted worker and the resumed activity can run it. Its confirmation code is derived from the reference, so a re-attached call returns the same code — visible proof the booking wasn't redone.

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-crash-recovery --enable-agent-infrastructure --wait --use
diagrid agent create spring-ai-crash-recovery --wait
diagrid dev run -f dev-spring-ai-crash-recovery.yaml --approve

7. Crash and re-attach

7.1 Book under an id you own (blocks ~30s)

From Terminal A — this schedules the booking under trip-42 and blocks while the tool "commits":

curl "http://localhost:8080/crash/book?id=trip-42&reference=ABC123"

Watch the app log for >>> commitReservation(ABC123) — committing over ~30s.

7.2 Kill the app mid-call

From Terminal B, during that window:

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

The app process dies (Terminal A's curl sees a reset). The workflow trip-42 keeps living in Catalyst.

7.3 Restart the app

The project and agent already exist, so just run:

diagrid dev run -f dev-spring-ai-crash-recovery.yaml --approve

The durable runtime resumes instance trip-42; the pre-crash LLM turn is not re-executed.

7.4 Re-issue the same call — it attaches

From Terminal A, re-run the exact same request:

curl "http://localhost:8080/crash/book?id=trip-42&reference=ABC123"

It attaches to the resumed run (waiting if it's still committing, or returning the recorded answer if it finished) and returns the same confirmation code — no second booking:

Booking ABC123 confirmed. Confirmation code: BK-...
tip

The instance id is a bearer handle you own — guard it like a primary key. A durable activity is at-least-once, so a side-effecting tool should key off a business value (here, the booking reference) to stay idempotent under recovery.

8. View in the Catalyst Web Console

Open the Catalyst Cloud web console, go to Workflows, and inspect instance trip-42 — the completed activity was not re-executed on recovery.

9. Clean Up

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

diagrid project delete spring-ai-crash-recovery

Summary

In this quickstart, you:

  • Scheduled a durable ChatClient.call() under a caller-owned instance id
  • Killed the app mid-booking and saw the workflow survive in Catalyst
  • Re-attached on restart by re-issuing the same id — same confirmation code, no double booking
  • Made a side-effecting tool safe to retry

Next Steps


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