Agents keep acting on engineering decisions that were reversed years ago. A 48-hour HydraDB build, the bugs I hit, and an honest baseline.
Every engineering team writes its decisions down. Then it changes its mind and writes another one. Two years later, search finds the old decision as easily as the new one, and an AI coding agent will happily build on a choice the team reversed long ago.
For the HydraDB hackathon I built DecisionGraph to fix that. It links each architecture decision record (ADR) to the records that replaced it. When an agent asks "what did we decide about X?", it walks those links to the decision that still stands and hands back that decision and its history, in about 130 tokens instead of 590. When someone corrects an answer, the correction is written into the graph, so the next question on that topic lands right even in different words.
- Live app: https://decisiongraph-latest.onrender.com
- Code: https://github.com/0xNexuz/decisiongraph
- Video walkthrough: https://youtu.be/ChWL-frv5v8
The problem in one picture
Asked "Does each business unit get its own Cloud Platform cluster now?", vector search returns the closest text: a 2019 decision to run one shared cluster, which has been replaced three times since. DecisionGraph starts at the same record, then follows the "replaced by" links to the decision that stands today.
The data is about 130 real, public ADRs from three UK government engineering teams: MoJ Cloud Platform, MoJ Modernisation Platform and GOV.UK on AWS (all MIT licensed). The test set is 50 questions I wrote, scored before each correction is applied.
How it works
- Find the decision. Vector similarity, distinctive terms and hints from past corrections each vote for where the question starts.
- Walk to what replaced it. One HydraDB path query follows trusted "replaced by" links until it reaches a decision nothing has replaced.
- Learn from the correction. The links on the right path are confirmed, the wrong ones rejected, and the question is stored as a node so the next one like it starts in the right place.
Where HydraDB earned its place
Trust lives in the path cost
Every REPLACED_BY link has a cost: 1 if trusted, 100 if not. Walking only trusted links is then a single algo.MSpaths call with maxCost set to the path length, so one untrusted hop rules the path out:
CALL algo.MSpaths({ sourceLabel: 'Decision', sourceProperty: 'key', sourceValues: ['cloud-platform:12'], targetValues: [...], relTypes: ['REPLACED_BY'], relDirection: 'outgoing', maxLen: 5, pathCount: 1, pairwise: false, weightProp: 'cost', costProp: 'cost', maxCost: 5 }) YIELD path RETURN path "Follow only what we trust" stopped being a loop in my Python and became one query inside the database.
Learning is a write, not a retrain
Links come from rules: an ADR's status line naming its replacement, a body that cites an older ADR next to "replace" or "migrate", and so on. Each rule has a trust score (a Beta count). When a correction proves a rule right or wrong, the new trust flows to every link that rule proposed that nobody has judged yet, in one statement:
MATCH (x:Decision)-[r:REPLACED_BY]->(y) WHERE r.rule = $rule AND r.state = 'candidate' SET r.w = $w, r.cost = $c The graph that answers the next question is already the corrected one.
Memory is part of the graph
Every corrected question becomes a Question node with a RESOLVED_TO edge to the right decision, an ABOUT edge to the topic and IN_QUESTION edges from its distinctive terms. "Has someone asked this before?" is a two-hop walk from the new question's terms, not a separate cache.
Two cells, two worlds
The public demo runs on one HydraDB cell. The "replay all 50 corrections" demo runs on a second cell, so a visitor pressing replay never resets anyone else's graph.
Self-hosted for $0
HydraDB is built from source into one Docker image with the app. GitHub Actions builds the image and pushes it to GitHub's container registry, and it runs on Render's free tier.
What broke, and how I fixed it
1. Building from source. My Rust toolchain was a version behind what the build asked for, and a dependency (LAGraph) wouldn't download as a release archive. I built LAGraph from a git clone, pinned the HydraDB commit, and used a two-stage Docker build so the final image carries only the graph-node binary.
2. A 500 on every startup. On a small (512 MB) config, graph-node refused to serve until the unflushed-write limit was larger than the first-level SST file size:
GRAPH_L0_SST_SIZE_BYTES=8388608 # 8 MB GRAPH_MAX_UNFLUSHED_BYTES=33554432 # 32 MB, must be bigger than the line above Obvious once you know it, painful until you do.
3. Restarts broke writes. HTTP query ids start again from 1 in each new process, so a restarted node pointed at an old store hit idempotency-key conflicts. My graph is derived from the records anyway, so the fix was to start from an empty store on every boot and rebuild it (about 20 seconds).
4. Writing the queries. HydraDB speaks a subset of OpenCypher. What made loads fast and predictable: batch everything through UNWIND, merge nodes by id and then set labels, and create edges between ids.
UNWIND $rows AS row MERGE (n {id: row.id}) SET n:Decision, n.key = row.key, n.title = row.title 5. One bad correction poisoned the graph. Early on, a single wrong correction confirmed a whole chain of wrong links, and answers for unrelated questions got worse. Now a correction only confirms a link if the two decisions are actually related (similar text or a shared distinctive term) or the link was stated in the record itself. A brand-new link the rules never proposed stays "proposed" until a second correction agrees.
The numbers
Test-then-train over 50 questions, 5 question orders, with 95% intervals from resampling the questions:
| Right | Outdated | Tokens | |
|---|---|---|---|
| Vector search | 52% | 28% | 588 |
| DecisionGraph, learning off | 74% | 18% | 135 |
| DecisionGraph, learning on | 80% | 10% | 129 |
- When a topic comes back in new words, learning adds 23 points over the same graph with learning off (interval +7 to +40).
- On 24 held-out questions written by an independent reviewer: 96% against 42% for vector search.
- It says "no decision on record" for 92% of out-of-scope questions instead of guessing.
The hardest lesson: be honest about the baseline
My first "plain store" baseline was too easy to beat. An outside reviewer built a much stronger one with no graph database: the same starting points, chasing each record's "superseded by" note in a Python dictionary, plus a memory of corrections. It scored 78% to DecisionGraph's 80%. On about 130 records that mostly state their own replacements, you don't need a graph database to win on accuracy.
What the graph does give you is everything it learned, in the open: every link, how far it trusts it, and the question that changed it, all queryable by an agent (/api/learned).
That matters more as the records get bigger and messier, where replacements have to be worked out rather than read off a status line. I put the stronger baseline and its numbers in the README, because a result nobody else can reproduce isn't a result.
Use it from an agent
The repo ships an MCP server with three tools: current_decision(question), find_decisions(search) and correct_decision(question, correct_decision_id).
pip install -r agent/requirements.txt claude mcp add decisiongraph -e DECISIONGRAPH_URL=https://decisiongraph-latest.onrender.com -- python agent/decisiongraph_mcp.py Try it
- Live app: https://decisiongraph-latest.onrender.com (free host, so the first visit after a quiet spell takes about a minute to wake up)
- Code: https://github.com/0xNexuz/decisiongraph
- HydraDB: https://github.com/hydra-db/hydradb
If your team keeps ADRs and lets agents work in your repos, I'd like to hear whether the "outdated decision" problem bites you too.




