Inspiration

We kept watching coding agents contradict themselves. You say "use Bun," and twenty turns later it runs npm install. Playwright fails to install, so it pivots to nmap — a port scanner — as if that were a browser. It re-runs the exact command that just failed, unchanged. The agent isn't stupid; it just has no memory of its own commitments. Conversation history stores what was said, not what was decided.

What it does

Graphene records everything an agent does as a graph: goals, decisions, attempts, failures, and fixes, with typed edges for what caused what. Before the agent runs anything, the proposed action is checked against that history and gets one of three answers:

allow — nothing on record conflicts warn — you already tried this and it failed block — this contradicts a decision you already made The dashboard shows the same task twice, once unguarded and once guarded, so you can see exactly what the guardrail caught and why. There's also a live chat mode where you give a real model a task and watch the graph draw itself as it works.

How we built it

FastAPI backend holding the graph, with runs journalled to SQLite and replayed on startup. A React + Vite + TypeScript dashboard using React Flow. An adapter that drives either scripted scenarios or a real LLM through the same guardrail.

Two design calls we'd defend: the two rules that need judgement — is this the same command? and does this tool serve the goal? — live in one shared module imported by both the backend and the offline mock, with a parity test asserting they answer identically. And the dashboard has no sample mode — every sentence on screen comes from a run the backend actually recorded, with verdicts produced by running the real reasoner server-side.

Challenges we ran into

The backend and the offline mock quietly disagreed: the backend called git push a repeat of a failed git commit because they share a base tool. That's the bug that made us extract the shared rules module and write parity tests.

The dashboard also once described a warned command as blocked, because it inferred the verdict from the graph's shape — warns and blocks are recorded identically. The fix was to stop guessing and re-run the actual reasoner.

Getting false positives right mattered more than true ones. A guardrail that warns about unrelated commands gets ignored, and an ignored guardrail is worse than none.

Accomplishments that we're proud of

The guardrail is plain string processing — no model, no embeddings, no network call — so the same input always produces the same verdict, and the verdict can be read by the person it just interrupted. nmap is blocked under a browser-automation goal and allowed under a security-audit goal from the same table, with no special-case rule. And when the model gets refused, we hand it the reasoner's own sentences rather than a scolding we wrote — so if it corrects itself, that recovery is real.

What we learned

Agent memory is usually framed as a recall problem. We think it's a consistency problem: the agent already has the information, it just doesn't check it before acting. Recording why something was abandoned turned out to matter more than recording what happened.

We also learned to be honest about what's real. Scripted demos are deterministic and safe on stage, but scripted drift is drift we wrote down in advance — which is why we built the live chat mode.

What's next for Graphene - Decision Provenance Graph

Execution. Chat currently proposes commands and never runs them. Running them behind the guardrail is the obvious next step. Semantic similarity. Matching is lexical today, so make test and pytest don't match — we traded coverage for explainability and would like both. Cross-session memory. Right now each run starts cold; distilling resolved runs into reusable strategies is where this gets genuinely valuable. Editor integration. The guardrail belongs in the loop of a real coding agent, not beside it.

Built With

Share this project:

Updates