Recall Desk: three AI support agents that share one memory, and the memory is a graph.
Title Recall Desk: three AI support agents that share one memory, and the memory is a graph. Published true Description How I used FalkorDB as the memory and coordination layer for a multi-agent support desk (Graph Hacks
Title
Recall Desk: three AI support agents that share one memory, and the memory is a graph.
Published
true
Description
How I used FalkorDB as the memory and coordination layer for a multi-agent support desk (Graph Hacks, Track 02).
Tags
Falkordb, AI, GraphDatabase, Hackathon
Cover image
https://github.com/Pratheeksh-7S/Recall-Desk-/raw/main/docs/graph-path.png
Every support chat I've been in has the same weak spot. You explain your problem to the first person, they transfer you, and you explain it again. Most AI agent demos copy this behaviour. Each agent has its own prompt and its own chat history, and "memory" is a long text log passed from one agent to the next.
For the WeMakeDevs Γ FalkorDB Graph Hacks hackathon (Track 02: Agent Memory and Coordination), I built Recall Desk, which tries something different. Three agents (triage, billing and technical) answer customers together, and they never hand context to each other in prompts. Everything an agent learns goes into a FalkorDB graph, and every other agent reads it from there, in the same session or a later one.
Live demo: https://recall-desk.onrender.com (free tier, so the first load can take a minute)
Code: https://github.com/Pratheeksh-7S/Recall-Desk-
Video: https://github.com/Pratheeksh-7S/Recall-Desk-/raw/main/docs/demo.mp4
The story the demo tells
Session 1. Priya, on the Team plan in eu-west, writes, "I was charged twice this month."
Triage reads her memory from the graph and opens a ticket.
The graph says (billing)-[:CAN_HANDLE]->(billing topic), so triage hands the ticket off. The handoff is a node, not a message.
The billing agent finds two paid $30 invoices for the same month, refunds the duplicate (a graph write), and stores a promise memory.
She then adds, "Bulk Export has been stuck for hours. " The technical agent follows a 7-hop path from her plan to an open incident in her region, finds the other affected customers and the other tickets with the same root cause, and links her ticket CAUSED_BY the incident.
Session 2. Priya comes back the next day. Before she types anything, triage says, "Welcome back." Last time: Billing issued a $30 refund for INV-5003β¦" Nobody asks her to explain again.
Every answer in the UI comes with the graph path behind it, so you can see exactly why the agent said what it said.
Why does the graph have to be there?
My rule for the hackathon was simple: if the app still works without FalkorDB, the graph is only decoration. Recall Desk has no SQL tables, no vector store and no chat history kept in Python. Remove FalkorDB, and nothing works.
- Root cause with variable-length paths "Bulk export is stuck" is a question about connected data. The feature depends on services, which depend on other services, and somewhere down that chain there may be an incident in the customer's region:
MATCH (c:Customer {id:$customer_id})-[:IN_REGION]->(r:Region)
MATCH p=(c)-[:SUBSCRIBES_TO]->(:Plan)-[:INCLUDES]->(f:Feature)
-[:DEPENDS_ON*1..6]->(s:Service)<-[:ON]-(i:Incident {status:'open'})-[:IN_REGION]->(r)
WHERE toLower(f.name) = toLower($feature)
RETURN f.name AS feature, s.name AS root_service, i.id AS incident, length(p) AS hops, p
ORDER BY hops LIMIT 3
In SQL this would be a recursive CTE and several joins. In Cypher it's one pattern, and the path p it returns is what the UI draws.
The same idea answers "Is it just me?" (walk back from the incident to every customer whose plan reaches the broken service) and "Who else has this?" (tickets that share a transitive dependency). algo.pageRank('Service','DEPENDS_ON') shows which services matter most.
- Memory as nodes, not text There are three kinds of memory, all in the graph:
Episodic: (:Session)-[:HAS_TURN]->(:Turn)-[:NEXT]->(:Turn), with FOLLOWED_BY linking one session to the next.
Semantic: (:Customer)-[:HAS_MEMORY]->(:Memory), plus LEARNED_IN, WRITTEN_BY and ABOUT edges, so every fact knows where it came from and which agent wrote it.
Procedural: (:Procedure {successes, failures}). When a customer marks a ticket "not resolved", the playbook's failure count goes up. The agent ranks playbooks by a smoothed success rate, so after enough failures it switches to a better one by itself:
MATCH (p:Procedure)-[:FOR_TOPIC]->(:Topic {name:$topic})
WITH p, (p.successes + 1.0) / (p.successes + p.failures + 2.0) AS rate
RETURN p.name AS name, round(rate * 100) AS success_rate
ORDER BY rate DESC LIMIT 1
- Coordination with graph writes When triage passes a ticket to billing, it creates a Handoff node with FROM, TO and FOR edges, plus a CARRIES edge to each of the customer's memories. The specialist reads those memories instead of asking the customer again.
Two agents may try to claim the same ticket at the same moment. A single atomic statement settles it:
MATCH (t:Ticket {id:$tid})
SET t.owner = coalesce(t.owner, $agent)
WITH t MATCH (a:Agent {name:t.owner})
MERGE (t)-[:ASSIGNED_TO]->(a)
RETURN t.owner AS owner
I tested this with 30 agents racing for one ticket: one owner and one ASSIGNED_TO edge every time. Memory writes use MERGE, so a load test of 16 threads Γ 200 operations (about 390 ops/s) produced zero duplicate memories.
- One graph per company Each tenant gets its own graph (recalldesk:acme, recalldesk:globex). I seeded the same customer email into both companies and checked that nothing leaks across.
Keeping the LLM honest
The LLM (Claude, GPT or Gemini, set by an environment variable, with a rules-only offline mode) does two jobs: it works out what the customer wants, and it words the reply. It never writes Cypher. It picks from a fixed catalogue of parameterised tools, and the customer id is always forced to the real customer.
Every reply is path-checked. If the LLM mentions an invoice, incident, article or ticket id that isn't in the graph results or the recalled memory, the reply is thrown away and an answer built straight from the graph rows is sent instead. The facts come from the graph, and the LLM only does the writing.
What I learned
Graphs make memory explainable. A memory node with a LEARNED_IN edge to the session and a WRITTEN_BY edge to the agent answers "Why do you think that?" for free.
Coordination is a data problem. Routing (CAN_HANDLE), handoffs and ticket ownership are rows and edges, not prompt engineering. That makes them testable.
Variable-length paths are the killer feature for support. Real root causes sit several hops away from the symptom.
Stack
FalkorDB (Docker locally, FalkorDB Cloud in production), Python + FastAPI, vanilla JS with vis-network for the graph view, and GitHub Actions running the end-to-end tests against a real FalkorDB. It's deployed on Render.
I built this with help from an AI coding assistant (Claude Code), as disclosed in the README.
If you try the live demo, pick Priya Raman and type "I was charged twice". Then open the Memory graph tab and watch the agents write to it.
Originally published by Dev.to AI. Aggregated on AIWithGhost for educational purposes β full credit and traffic to the original publisher.