TL;DR — Relay is a shared memory layer for teams of AI agents, built on FalkorDB: the agents never message each other, yet never contradict each other, because everything one learns lives in one graph — with who wrote it, when it was true, and what it replaced. We wrapped it in WiFight, a fictional fiber ISP with a three-agent support desk. Same model and prompt for both systems, Relay scored 99.4% (159/160) vs 61.3% for a plain vector store (+38.1 pts, 95% CI +30.6 to +45.6) — and we publish the one external test we lose. Try it: wifight.vercel.app · watch it run itself · code.
Until recently I didn't really know what a graph database was. Then Graph Hacks (WeMakeDevs x FalkorDB) happened, and my team (Dharani, Thanushrie and me) decided to find out. This is how we went from "what even is a graph DB" to shipping Relay — a shared memory for teams of AI agents, running live inside a fake internet company called WiFight — built for Track 02, Agent Memory and Coordination.
How it's built, in one line: FalkorDB Cloud holds the memory (Cypher, vector and full-text indexes, one graph per tenant, algo.maxFlow on the network), a Python 3.12 + FastAPI backend runs the agents, open models on Groq (gpt-oss, qwen) do extraction and replies with local fastembed embeddings, the front end is React + TypeScript, an MCP server lets Claude or any MCP client share the same memory, and it all runs on Vercel and Render.
flowchart LR
U[Customer / judge] --> FE[React + Vite<br/>Vercel]
FE <-->|REST + WebSocket| API[FastAPI backend<br/>Render]
subgraph AGENTS [Agents]
FIX[Mr.Fix-It · intake]
RES[Resolver]
OPS[Network Ops]
end
API --> AGENTS
AGENTS -->|Cypher| DB[(FalkorDB Cloud<br/>one graph per tenant)]
LLM[Groq gpt-oss / qwen<br/>+ fastembed] -.-> API
MCP[MCP clients, e.g. Claude] -->|same memory| DB

Watch: a judge presses one button and a whole case runs itself — reported, handed off, measured, fixed, re-checked — with nobody typing.
What a graph database actually is
In a normal database you store rows and join them to find how things relate. In a graph, the relationships are the data. A customer LIVES_IN a city. A case was HANDED_OFF between agents. A fix RESOLVED a problem. You don't reconstruct those links at query time, you just walk them. "If this street cabinet fails, which customers lose internet?" is a pile of joins in SQL; in a graph it's "start at the cabinet, follow the wires down." FalkorDB gave us that plus three things we leaned on hard — vector search, full-text search, and graph algorithms like max flow — all in one place. We didn't just store the network; we asked it real questions ("how much bandwidth can reach this home?") and the database answered, not some Python loop we wrote at 2am.
Why an internet provider
The easy demo — hook a few coding assistants to one memory server and show one remembering another — would have looked fine in a video. But coding assistants work alone, and nothing breaks if they forget. I wanted a case where shared memory is the job and forgetting costs someone something. Healthcare, logistics, education all had graph-shaped bits, but none where memory was the whole job.
Customer service fit. Think of the worst support call you've had: you explain the problem, get transferred, explain it again. That's not a people problem, it's a memory problem — and it gets worse with AI agents, because each keeps its own notes and none agree. So we studied how ISP desks actually work. A good agent needs the same things in order: who you are, where you are (which exchange and cabinet, and any known outage), when it started, how much is affected, and Wi-Fi or cable. Then a ladder: check for outages, try safe fixes, test the line, send a technician — writing down what they find at each step.
That "write it down" runs on a ticket: a record with a reference number holding what's wrong, who you are, what's been tried, and its state. When one person can't fix it, they pass the ticket on and the next reads the whole history. A ticket is just shared memory with a reference number — exactly what we were building for agents. And an ISP fits it perfectly: people hate repeating themselves ("tell us once" is the test of shared memory); it's naturally multi-agent; facts change constantly (append-only text will say a customer lives in two cities at once); systems disagree (billing vs the customer); and the network itself is a graph.

Watch: a one-line complaint becomes a ticket, gathers facts, and ends as a verified fix — every step a node or edge in the graph.
Giving it a personality
We could have shipped a grey dashboard with "Agent A" and "Agent B." Instead we made WiFight — a slogan ("Fiber fast. Game hard. Binge harder."), plans in 39 countries, and a desk with characters. Mr.Fix-It does intake: chats, picks up your name, asks only what he doesn't know, opens the ticket. The Resolver measures your line, tries a fix, re-measures before calling it done. Network Ops warns you about an outage before you notice.
The rule we set: the agents never message each other. There's no chat channel between them in the code — the only way one sees another's work is by querying the graph. Giving it a personality wasn't just fun; it forced us to build for real customers who type "my wifi is slow upstairs" and give an address as a suburb nobody's heard of.

Watch: the graph grows as the customer talks, and the scrubber replays it — faded edges are facts that used to be true.
The parts that were harder than they looked
Time. A fact is true from a date to a date, and separately we learnt it on some date. "Alice lived in Mumbai until the 10th; we found out on the 12th." Each fact is its own node with four timestamps — valid_from/valid_to (world time) and recorded_at/expired_at (when we learnt or retired it). Nothing is overwritten: when Alice moves, the old fact just gets a valid_to. So "where did she live on 5 September, and who told us?" is a filter plus a walk back to the agent:
MATCH (f:Fact)-[:ABOUT {role:'subject'}]->(:Entity {name_key: $customer})
WHERE f.valid_from <= $t AND (f.valid_to IS NULL OR f.valid_to > $t)
MATCH (f)<-[:DERIVED]-(:Episode)<-[:WROTE]-(a:Agent)
RETURN f.predicate, f.object_value, collect(DISTINCT a.agent_id) AS told_by
Disagreements. When billing and the customer disagree, "latest write wins" is lazy. Each agent carries a trust weight; a conflict is a Conflict node linking both claims with their source agents. At read time we rank by the writing agent's trust (recency breaks ties) and surface the top one while keeping the other visible. A human clicking "this one is right" nudges that agent's trust up, so the next similar conflict leans the right way on its own. Sometimes the right answer is to not decide yet — and to learn who to believe.
Bugs you only find by talking to it. An evening trying to break our own bot — jokes, "ignore all previous instructions," asking for another customer's number, Tamil and Hindi mixed with English — it held up. But it caught a real bug: "slow internet" and "wifi slow upstairs" look almost identical to an embedding, so the system merged two customers and told one about "upstairs" Wi-Fi they never mentioned. Unit tests would never have found that.
Free tiers are real. Everything stopped once — our graph backups had quietly filled the 100 MB limit and it refused every write. Measure what you store, and clean up after yourself.

Watch: the Resolver walks one customer's path — core to home — and runs algo.maxFlow to see how much bandwidth actually reaches the house.

Watch: inject a fault the Resolver can't see, it diagnoses blind, and it's graded against the hidden truth.
We didn't want a plain submission
99.4% vs 61.3% — 159 of 160 questions for graph memory, against 98 of 160 for a vector store, same model and prompt, +38.1 points (95% CI +30.6 to +45.6). On conflict questions it's 100% vs 6%. Why so lopsided there? A vector store retrieves the most similar text; when two contradictory facts both match, it has no notion of which superseded which or which agent to trust, so it often answers with the stale one. A graph keeps both, dated and attributed, and picks the current, trusted claim by construction.
How the benchmark was built, because a skeptic should ask: the 160 questions come from seeded storylines (planted moves, plan changes, outages, disagreements) across 40 customers and 556 turns; gold answers are fixed by the storyline before any system runs, scored exact-match, so we can't grade in our favour. Both systems use the same model and prompt — only the memory differs — and the vector baseline is fair: customer-filtered, dated, recency-ranked, top-10. The honest caveat: this data is synthetic and in-distribution, which is why we also ran an out-of-distribution extraction check and one public LoCoMo conversation as outside tests. We reported confidence intervals, paired significance tests, and ablations (remove the two clocks, lose ~19 points).
And we published the test we lost. On LoCoMo (open-ended chat) we score worse on word overlap — token F1 0.08 vs 0.27 — because we store facts, not transcripts. We did refuse all 15 false-premise questions to the vector store's 4. A result you're willing to show your losses on is worth more than one you're not. None of the building blocks are new alone — two clocks, memory updates, one graph per tenant; what we added was combining them for agents that never talk, and measuring it properly.

Watch: every line is one agent's fact being read by another — the handoff happening entirely through the graph.
Limitations and what's next
- Scale. Tested to ~11K facts; the free tier caps around 15–75K nodes, so a large tenant needs the paid tier or a compaction policy for old episodes.
- Open-ended chat. The LoCoMo loss is real; a hybrid that also keeps short transcript snippets would help.
- Trust cold-start. Per-agent trust starts from a fixed prior and only sharpens once humans confirm conflicts.
- External validity. Most numbers are on our own synthetic data; the OOD check and one LoCoMo conversation are the only outside tests so far.
What I learnt
- Graphs aren't niche: if your question has "who," "which" or "connected to" in it, there's probably a graph hiding in your data.
- The easy demo and the right use case are rarely the same thing.
- Learn how the real people do the job before you automate it.
- Personality isn't fluff — it made us build for real customers.
- Measure honestly, including where you lose. And break your own thing on purpose; your users will.
Win or lose
Whether we win or not, I'm glad we did this. Not long ago "graph database" was a buzzword to me; now it's the first thing I'll reach for whenever a project touches relationships, history or "who's connected to what." Thanks to Dharani and Thanushrie for the late nights, and to WeMakeDevs and FalkorDB for Graph Hacks.
It's live — go chat with Mr.Fix-It, tell him your internet is broken, then come back later and see if he remembers you. He will.
- Live demo: https://wifight.vercel.app
- Watch it run on its own: https://wifight.vercel.app/?judge=1
- Code: https://github.com/tharunkumardeveloper/The-Final-Boss
Tharun, for team The Final Boss
Top comments (0)