Blast Radius / Engineering blog
GitHub
FalkorDB Graph Hacks · Agent Memory and Coordination

Agents that remember
and recover.

How we built Blast Radius: four on-call agents that share persistent graph memory, check past fixes against current evidence, and hand off work when a worker fails.

FalkorDBagent memorymulti-agent coordination

Recorded benchmark · Oct 16, 2026

fewer model calls than agents with no memory, same accuracy
right memory in the top 5, against 55% for text similarity
duplicate pages, with an agent killed or leases expiring at random

These are recorded benchmark results, not live counters. Click a number for its source. For the latest completed run, open the Results tab in mission control.

  • Memory lives on the graph. A confirmed fix becomes a playbook pinned to the service that failed.
  • Recall is a walk. Agents find playbooks by walking the dependency graph from the alerting services.
  • Nothing is trusted unchecked. Each playbook's checks run against live evidence first; trust moves only on verdicts.
  • A crash is a handover. Leases, fencing epochs and checkpoints in the graph let a second agent continue at the next step.
Service graph· s04 · Payments DB saturatesreplay of a recorded run · same code as the app
Watch it run

See memory and handoffs in action.

In shortThe public portal runs a real agent loop: learn a fix, kill a worker, resume its task, and reject a memory that does not apply.

The agents and FalkorDB are running on our VPS. Incident inputs come from a synthetic company; this is a working system, not production monitoring. The recorded clips below also show the local persistence test.

The Blast Radius landing page Open the live portalReal workers · shared graph state · synthetic incident inputs
Part 1

The night

What follows is the recorded demo run (scenario s04), told second by second. The clock times are ours; the order of events, the checks, the kill and the numbers are exactly what the agents did. On a wide screen, the graph on the right follows you down the page.

The page

The payments database runs out of connections.

In one lineAlerts fire on the payments gateway. Last week the team fixed the same failure on a different database, and wrote it down where nobody will look.

When production breaks at 3 a.m., the on-call engineer usually starts from zero. Somebody fixed something very similar last month, and it is written down in a postmortem that nobody will find in time. We wanted agents that help with incidents, and quickly found that they have the same problem, plus two of their own.

  • They start from scratch. Every incident is a fresh investigation, every tool call paid for again.
  • They trust old memory blindly. Give an agent notes from past incidents and it will happily follow a note that does not apply.
  • They crash. An agent dies mid-investigation and either the work is lost, or a second agent redoes it and the engineer gets paged twice.
The fix from last month is in a postmortem nobody reads. We wanted it to be the first thing the next agent checks.
Four agents wake up

Four agents, one graph, nothing else shared.

In one lineTriage turns the alerts into an incident and a task; nobody talks to anybody directly, everything goes through the graph.

Blast Radius is four kinds of agent that never talk to each other directly. Everything they know and everything they hand over is a node or an edge in one FalkorDB graph per company.

FalkorDB · company_a services · incidents · tasks · checkpoints playbooks · facts · messages · decisions the only thing the agents share triagealerts → incident + task inv-1 · inv-2recall, check, investigate responderwho to page, how; confirm learnerverdicts, trust, playbooks investigate taskclaim · checkpointrespond tasklearn task

The loop runs in this order, every time:

  1. Triage turns alerts into an Incident and an investigate task.
  2. An investigator claims the task, recalls playbooks near the alerting services, and checks each one against live evidence without calling the model. If one holds, that is the diagnosis. If none does, it investigates step by step with Claude Haiku 5.5, behind a rule-based guard rail. Every tool call is checkpointed to the graph before the next one starts.
  3. The responder finds who owns the failing node and who is on call at that moment, notifies them the way they asked, and asks them to confirm in the war room.
  4. The learner records what each memory did (helped, misled, or not applicable), updates trust, and turns a new confirmed investigation into a playbook.
Which memory?

The wording is new. The shape of the failure isn't.

In one lineText search misses last week's fix because the alerts use different words; the dependency graph finds it one hop away.

Our first instinct was a vector store of past incidents. The trouble is that two outages with the same cause are often described differently. When the orders database saturated last week the alerts said “p95 latency above SLO” on the cart service. Tonight, on the payments gateway, they say “slow responses breaching SLO”. Text similarity sees two unrelated incidents.

The dependency graph sees the same thing twice: an alerting service, one hop upstream of a postgres datastore that is out of connections. So we pin each playbook to the node where it was learned, and recall by walking the graph. Try it on tonight's alert:

Same alert, two ways to recall
payments-gw: slow responses breaching SLO

An illustration of the idea, with four notes. The measured numbers over 20 incidents are in Results: text similarity found the right memory 55% of the time, the graph walk 100%.

Recall, then check

Recalled is not trusted.

In one lineThe investigator finds last week's playbook by walking the graph, then tests its three conditions against live evidence before believing it.

Recall is one Cypher query. It walks one or two hops downstream from each alerting service (a service fails because something it depends on failed) and returns the playbooks pinned there, with the path it took. A second query finds playbooks learned on another datastore of the same kind, at half weight. That is how a playbook learned on postgres-orders gets recalled for postgres-payments.

Recall playbooksmemory/queries/recall.cypher

Each playbook carries checks: what must be ruled out first, and what we expect to observe. For the database playbook that means three questions, answered by real tool calls: was there a deploy on the alerting service in the last two hours, does the caller see the datastore as unhealthy, and is the datastore using at least 90% of its connections. The investigator stops at the first check that fails. Tonight, the first check holds.

Checking the playbook: one of three checks passed
The investigator was killed; its lease is draining
The other investigator took over at step 4
The playbook helped; trust 0.50 to 0.67
An agent dies

A crash is just a handover.

In one lineWe SIGKILL the investigator after step 3; six seconds later its lease runs out and the other investigator carries on at step 4, not from scratch.

Agents coordinate only through tasks in the graph. Three rules make that safe:

  • Fence every write. A claim starts a new epoch and a lease measured on FalkorDB's clock, not the worker's. Every later write checks status, owner, epoch and lease_until >= timestamp(). A worker that lost its lease is refused and stops.
  • Checkpoint synchronously. Each tool call is written before the next one starts, so a replacement continues at step n + 1.
  • Key every action by intent. incident:<id>:initial-notification can be sent once, whoever sends it.

Kill the working agent and watch:

Two investigators, one tasklease 6 s, sped up hereinteractive
The real thing, recorded. The investigator is killed after step 3 with a real SIGKILL; its lease drains; the other claims the task at epoch e2 and resumes at step 4; one ticket goes out; trust counts up from 0.50 to 0.67.
Text version of the clip
  1. The payments playbook is recalled and its checks start ticking.
  2. After step 3 the working investigator receives a real SIGKILL; its card shows the lease draining.
  3. Six seconds later the lease expires and the other investigator claims the task under epoch e2.
  4. It resumes at step 4 from the stored checkpoints, not from scratch.
  5. One ticket goes to Priya, and the playbook's trust counts up from 0.50 to 0.67.
Claim a taskcoord/queries/claim.cypher
One ticket, the way Priya asked

What the engineer said last week is memory tonight.

In one lineExactly one ticket goes to Priya, with the raw slow-query lines, because that is what she asked for after the last database incident.

Last week, Priya, on call for the data platform, confirmed the first diagnosis and added: “For DB saturation, don't ping me on Slack. Open a ticket and paste the raw slow-query lines.” The agent turned that into two conditional facts, read them back, and saved them only after she said yes. The approved version supersedes the agent's reading; both stay in the graph. Tonight she gets a ticket with raw lines, and the ticket says why.

The war room asks Priya to confirm
The agent reads back what it understood
Engineer preferences as versioned facts
The next ticket cites her request
Priya confirms

Trust is earned per playbook, and lost the same way.

In one linePriya confirms the diagnosis, so the playbook helped: its trust goes from 0.50 to 0.67.

Each time a playbook is used, the learner gives it one verdict. Helped means it was followed and the diagnosis was confirmed. Misled means it was followed and the engineer rejected the diagnosis. Not applicable means a check failed, so it never steered the investigation, and nothing changes. A playbook that misleads twice with trust under 0.5 retires and is never recalled again. Play with it:

One playbook's trustagent-written, prior 1 / 1interactive
0.50helped 0 · misled 0
trust = (0 + 1) / (0 + 0 + 2) = 0.50
Active. Recalled when the graph walk reaches it.

This is also where FalkorDB taught us our most surprising lesson. Our first version of the update did all three steps in one query with back-to-back SET clauses. Trust always lagged one verdict behind, so a misleading playbook was never retired. The fix is a WITH between the clauses:

Apply a verdict, update trust, maybe retirememory/queries/feedback.cypher
Same symptoms

Same symptoms, different cause.

In one lineCheckout errors look like the database outage again, but the playbook's first check fails: there was a deploy. Ruled out, trust untouched, bad deploy found.

The interesting case is when memory is wrong. The checkout errors look just like the database outage, and the database playbook is recalled again. Its first check asks whether checkout-api was deployed recently. It was. The playbook is ruled out on that evidence, its trust is left alone because it never steered anything, and the agent finds the bad deploy instead.

Recorded from the running system. The checking card on the graph: No deploy on checkout-api in the last 2 h ✕ observed 1 deploy. Not applicable, trust unchanged.
Text version of the clip
  1. The checkout services raise alerts that look like last week's database outage.
  2. The database playbook is recalled again and its checking card appears on the graph.
  3. First check, no deploy on checkout-api in the last 2 h: it fails, one deploy was observed.
  4. The playbook is marked not applicable; its trust stays where it was.
  5. The investigation continues and finds the bad deploy on checkout-api.
Maintenance window

Restart the database. Nothing is lost.

In one lineFalkorDB goes down and comes back; every playbook, incident and preference is read back from disk.

The memory has to outlive the process that wrote it. We restart FalkorDB in the middle of the demo: the agents log one line and wait, the page dims, and when the database is back everything is still there. Our acceptance test does the same with a full docker compose down && up, then kills a worker mid-investigation for good measure.

Recorded. FalkorDB shuts down and comes back; the toast reports what survived, read from the graph.
Text version of the clip
  1. FalkorDB is shut down; the page dims and says it is waiting for the database.
  2. The database comes back and loads its snapshot from disk.
  3. A notice lists what survived: playbooks, incidents and preferences, read from the graph.
Part 2

The morning after

One good night proves little. Here is what we measured over thirty incidents, how the graph is laid out, and what the build taught us.

Results

Same answers, a third of the work.

Recorded benchmark · Oct 16, 2026

In shortSame model, same tools, only the memory changes: graph memory needs 71% fewer model calls and finds the right memory every time.

We replayed 30 synthetic incidents in time order through three setups, each in its own graph, with the same model (Claude Haiku 5.5), the same tools, the same step budget and the same learning. Only how memories are retrieved differs: no memory, text memory (embedding similarity, FalkorDB's vec.cosineDistance) and graph memory.

The model already solves this benchmark from scratch, so memory could not raise accuracy here; what it changes is the work. Graph memory made 71% fewer model requests and needed no model call at all for 20 of 30 incidents. When a memory with the right answer existed, text similarity put it in the top five 55% of the time; the graph walk, every time.

Checking is not free. Every recalled memory costs tool calls to check, and text memory once ran out of its 12-step budget checking five wrong memories. On new kinds of incident, graph memory takes a couple more steps than no memory, though fewer model calls.

Many agents at once

Two companies, every agent in its own claim loop, 20 incidents fired at once on a laptop:

Worker pairsStalled workersIncidents / minWrite p50 / p95DuplicatesTakeovers
1none6,6330.33 / 1.01 ms00
2none9,1850.63 / 1.82 ms00
4none9,0621.22 / 3.20 ms00
4lease expired every 50 ms8,7120.91 / 2.58 ms01

Throughput levels off beyond two pairs because FalkorDB runs one graph's writes one at a time and every agent step is a short write. The number we care about is the : nothing was ever sent twice.

The graph

Everything is a node you can query.

In shortWorld, incidents, coordination, conversations and memory live in one graph per company, so “what did the agent believe, and why?” is a query.

One graph per company, identical in shape. The world (services, owners, on-call, deploys), incidents and immutable decisions, the coordination state, the conversations, and the memory all live side by side, so “what did the agent believe then, and why?” is a query, not a guess. Hover a node:

Each Decision has a USED edge to every memory it considered, with the path, the trust at use, every check's result, and later the verdict.

All 76 queries are .cypher files, indexed in docs/QUERIES.md, and the app shows the Cypher behind every panel. A few rules kept it honest: the graph is chosen from trusted config and never by the model; leases use the database clock while evidence uses scenario time; memory is never edited, only superseded; and ground truth for the benchmark lives outside the graph, with a test that fails if agent code imports it.

What we learned

What FalkorDB, and the build, taught us.

In shortTest against the real database, choose persistence deliberately, wait for constraints and restarts, give memory a way to be wrong, and never animate a lie.
Lesson 1

Test doubles lie politely.

We wrote every integration test twice: against FalkorDB, and against an in-memory double that checks each call against the real .cypher file. The double was great for speed and caught dozens of contract bugs. It could not catch Cypher semantics. In FalkorDB 4.18, a later SET clause reads the values from before an earlier one:

GRAPH.QUERY g "MATCH (m:M) SET m.h = m.h + 1 SET m.t = m.h RETURN m.h, m.t" -> m.h = 1, m.t = 0 GRAPH.QUERY g "MATCH (m:M) SET m.h = m.h + 1 WITH m SET m.t = m.h RETURN m.h, m.t" -> m.h = 2, m.t = 2

Only the live run caught it. Now every suite runs on both backends.

Lesson 2

Choose persistence like you choose a lock.

After a long day of deleting and re-creating graphs, FalkorDB 4.22.1 refused to start: replaying the append-only file diverged and the server shut itself down on every start.

# <module> GRAPH.EFFECT references label/relationship-type 'Memory' (id 0) which doesn't match local schema state # Diverged applying GRAPH.EFFECT on graph 'company_a' while loading from disk ... shutting down.

We moved to RDB snapshots (every 30 s and on shutdown), wrote down the trade-off, and our restart test passes on 4.22.1. We also noticed each unique-constraint creation is written to the AOF three times. Both are written up with reproductions in the repo.

Lesson 3

Constraints are asynchronous, and restarts take a moment.

Unique constraints build in the background (PENDING, then OPERATIONAL), so our schema step polls db.constraints() before any worker starts. Right after a restart every command fails with LOADING until the data is in memory, so the agents and the UI wait for it instead of crashing.

Lesson 4

Memory needs a way to be wrong.

The feature we are proudest of is the one that says no. Checks before trust, verdicts per use, retirement after two misleading uses: we tested it by planting a deliberately wrong playbook. It , and the next incident was diagnosed correctly.

Lesson 5

Never animate a lie.

For the demo it was tempting to script a smooth animation. Instead, every animation in the app fires when the event it shows first appears in the graph: a checkpoint, a claim, a sent ticket, a trust change. The replay buttons rebuild any incident from its stored steps. What you see is what the agents did.

Limits

What this does not show.

  • A synthetic world. One company of 15 nodes, three failure families, a seeded simulator. Real incidents are messier.
  • No accuracy gain to show. The model solves every benchmark incident from scratch; memory's measured benefit is less work. A harder benchmark or a smaller model would test accuracy.
  • A modest text baseline. Text memory uses a hashed bag of words by default; a neural embedder is supported and would be a stronger opponent.
  • Kind transfer is datastore-only, and workers are processes, not containers.
Try it

Run it on your laptop.

You need Docker and Python 3.10+. The landing page opens at localhost:8000; click Enter mission control, open Director, and break something.

git clone https://github.com/Genziitian/Blastproof && cd Blastproof python3 -m venv .venv && source .venv/bin/activate make install && cp .env.example .env make db schema seed # FalkorDB in Docker, indexes, the synthetic company make ui # http://localhost:8000

Everything runs without a model (a rule-based planner). Add an Anthropic key, or any OpenAI-compatible endpoint, to .env to let Claude plan the investigations. make acceptance-1, a database restart, then make acceptance-2 run the full restart-and-kill test; make eval reruns the benchmark.

Credits

Thanks.

Built in four days for Graph Hacks by WeMakeDevs and FalkorDB, track Agent Memory and Coordination, by team Blastproof: sri ram and Aditi Akansha. All data is synthetic.

AI assistance, disclosed: Claude helped with pair-programming, tests, verification and the translations of this page; ChatGPT and Gemini reviewed the implementation plan; Claude Haiku 5.5 runs inside the product, planning investigations and reading the war-room messages. The design, the graph model and every decision were ours, and we can walk through any query.

Code, queries, findings and the full benchmark: github.com/Genziitian/Blastproof.