<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[ORBIT Dev Blog]]></title><description><![CDATA[ORBIT Dev Blog]]></description><link>https://orbit-graph-agent.hashnode.dev</link><generator>RSS for Node</generator><lastBuildDate>Mon, 02 Nov 2026 12:06:12 GMT</lastBuildDate><atom:link href="https://orbit-graph-agent.hashnode.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Giving an AI agent a graph for a brain: how we built ORBIT on FalkorDB]]></title><description><![CDATA[How we built ORBIT, a local-first AI agent that uses FalkorDB code and memory graphs, Cypher traversals, and shows the query behind every answer.]]></description><link>https://orbit-graph-agent.hashnode.dev/giving-an-ai-agent-a-graph-for-a-brain-how-we-built-orbit-on-falkordb</link><guid isPermaLink="true">https://orbit-graph-agent.hashnode.dev/giving-an-ai-agent-a-graph-for-a-brain-how-we-built-orbit-on-falkordb</guid><category><![CDATA[falkordb]]></category><category><![CDATA[WeMakeDevs]]></category><category><![CDATA[graphdatabase]]></category><category><![CDATA[AI]]></category><category><![CDATA[graphhacks]]></category><dc:creator><![CDATA[Chirag Pandey]]></dc:creator><pubDate>Sun, 18 Oct 2026 17:23:12 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/6ad4fdd586db26e08cad44ec/5db24d08-edad-4ce7-b219-234de71828df.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><em>Built for Graph Hacks: Context for AI Agents (WeMakeDevs x FalkorDB).</em></p>
<p>Most coding assistants see your project as a pile of text chunks. They guess which files matter, forget what you told them yesterday, and can't tell you <em>why</em> they answered the way they did.</p>
<p>We wanted the opposite: an agent whose working context is a <strong>graph</strong>. So we built <strong>ORBIT</strong>, a local-first personal AI agent that uses <strong>FalkorDB</strong> as its context layer. Every project you authorize becomes a code graph the agent traverses, every memory is a node linked to its concepts, and every answer shows the Cypher behind it.</p>
<img src="https://raw.githubusercontent.com/SinghCharanjeet11/orbit/main/docs/images/code-graph.png" alt="ORBIT code graph" style="display:block;margin:0 auto" />

<p><strong>Repo:</strong> <a href="https://github.com/SinghCharanjeet11/orbit">https://github.com/SinghCharanjeet11/orbit</a></p>
<p><strong>Demo video (2 min):</strong> <a href="https://youtu.be/jOIPAibwpO8">https://youtu.be/jOIPAibwpO8</a></p>
<h2>The problem with "chunk and retrieve"</h2>
<p>Ask a typical assistant "What breaks if I change <code>execute_tool</code>?" and it searches for files that mention the name, reads a few, and guesses. It misses the file that imports the module that imports the function, because that file never says <code>execute_tool</code> at all.</p>
<p>That isn't a text-search question. It's a <strong>graph traversal</strong>: follow the <code>IMPORTS</code> and <code>CALLS</code> edges backwards, a few hops deep. So we put the code into a graph database and let the agent traverse it.</p>
<h2>Step 1: Turn a project into a graph</h2>
<p>When you authorize a folder, ORBIT parses it <strong>locally</strong> (Python with <code>ast</code>, JS/TS imports and definitions with patterns) and writes one isolated FalkorDB graph per project:</p>
<pre><code class="language-cypher">(:File {path, lang, lines})-[:DEFINES]-&gt;(:Symbol {name, qual, kind, line})
(:File)-[:IMPORTS]-&gt;(:File)
(:File)-[:USES_PACKAGE]-&gt;(:Package {name})
(:Symbol)-[:CALLS]-&gt;(:Symbol)
</code></pre>
<p>Only <strong>structure</strong> goes into the graph: paths, names, line numbers and links. File contents and secrets never do, and <code>.env</code> files aren't even read.</p>
<p>Building is a handful of batched writes, one per node or edge type:</p>
<pre><code class="language-cypher">UNWIND $rows AS e
MATCH (a:File {path: e[0]}), (b:File {path: e[1]})
CREATE (a)-[:IMPORTS]-&gt;(b)
</code></pre>
<p>On ORBIT's own backend this takes about a second: <strong>52 files, 373 symbols, 107 imports, 433 resolved calls and 7 packages</strong>. FalkorDB keeps a separate graph per project (<code>orbit_code_ID</code>), so projects never bleed into each other.</p>
<h2>Step 2: Questions become traversals</h2>
<p>"What depends on this file, directly or indirectly?" is one query:</p>
<pre><code class="language-cypher">MATCH p = (d:File)-[:IMPORTS*1..4]-&gt;(f:File {path: $path})
WHERE d &lt;&gt; f
RETURN d.path AS path, min(length(p)) AS hops
ORDER BY hops, path
</code></pre>
<p>"How does a request get from the API route to the tool executor?" is a shortest path:</p>
<pre><code class="language-cypher">MATCH (a:Symbol {name: 'agent_loop'}), (b:Symbol {name: 'execute_tool'})
WITH shortestPath((a)-[:CALLS*1..8]-&gt;(b)) AS p
RETURN [n IN nodes(p) | n.file + '::' + n.qual] AS call_chain
</code></pre>
<p>It returns:</p>
<pre><code class="language-text">routes.py::agent_loop → loop.py::run_loop → loop.py::run_tool → registry.py::execute_tool
</code></pre>
<p>In our app most of these queries finish in <strong>1 to 5 ms</strong>. That matters: it's fast enough to run on <em>every step</em> of the agent loop, not just once up front.</p>
<img src="https://raw.githubusercontent.com/SinghCharanjeet11/orbit/main/docs/images/cypher-console.png" alt="Cypher console" style="display:block;margin:0 auto" />

<h2>Step 3: Let the agent choose graph tools</h2>
<p>The graph agent works in small steps. Each turn the model returns one JSON action, and ORBIT runs it. The tools are graph-first:</p>
<ul>
<li><p><code>find_symbol</code>: where a function is defined, who calls it, what it calls</p>
</li>
<li><p><code>impact_of</code>: every file that depends on a file, up to 4 hops</p>
</li>
<li><p><code>call_chain</code>: the shortest call path between two functions</p>
</li>
<li><p><code>file_context</code>: a file's imports, importers, packages and impact</p>
</li>
<li><p><code>package_users</code>: which files use a third-party package</p>
</li>
<li><p><code>cypher_query</code>: the model writes its own <strong>read-only</strong> Cypher</p>
</li>
</ul>
<p>It reads a file only when the graph says that file matters. Asked "What depends on <code>execute_tool</code>?", the model called <code>find_symbol</code>, then wrote its own variable-length <code>CALLS</code> query, and answered with the exact callers. No guessing, and every step in the UI shows the Cypher it ran.</p>
<img src="https://raw.githubusercontent.com/SinghCharanjeet11/orbit/main/docs/images/graph-agent.png" alt="Graph agent" style="display:block;margin:0 auto" />

<p><strong>Safety:</strong> model-written Cypher runs through FalkorDB's <code>GRAPH.RO_QUERY</code>, which <strong>rejects any query that would write</strong>, plus a timeout and a row cap. The agent has no write tool at all. Proposed file changes wait for the user's approval.</p>
<h2>Step 4: Memory is a graph too</h2>
<p>Personal agents need memory, but dumping everything into a vector store makes it impossible to say why something was recalled. In ORBIT, memories live in a graph:</p>
<pre><code class="language-cypher">(:Memory)-[:MENTIONS]-&gt;(:Concept)
(:Memory)-[:ABOUT]-&gt;(:Project)
(:Conversation)-[:HAS_TURN]-&gt;(:Turn)-[:RECALLED]-&gt;(:Memory)
</code></pre>
<p>Recall is a query: concepts in your message lead to confirmed memories, ranked higher when they are about the active project:</p>
<pre><code class="language-cypher">UNWIND $concepts AS c
MATCH (k:Concept {name: c})&lt;-[:MENTIONS]-(m:Memory {confirmed: true})
WITH m, collect(k.name) AS via
OPTIONAL MATCH (m)-[:ABOUT]-&gt;(p:Project {id: $project})
RETURN m.id, size(via) + CASE WHEN p IS NULL THEN 0 ELSE 1 END AS score, via
ORDER BY score DESC
</code></pre>
<p>Save "I deploy my projects with Docker Compose on a Hetzner VPS", then ask "How should I deploy my next side project?". The answer builds on that memory and shows which memory it used. ORBIT also stores a <code>(:Turn)-[:RECALLED]-&gt;(:Memory)</code> edge, so later you can see which answers used which memory. That is <strong>provenance</strong>, not just retrieval.</p>
<img src="https://raw.githubusercontent.com/SinghCharanjeet11/orbit/main/docs/images/memory-graph.png" alt="Memory graph" style="display:block;margin:0 auto" />

<h2>Architecture</h2>
<pre><code class="language-text">React UI --(local token proxy)--&gt; FastAPI --&gt; FalkorDB (code graphs + memory graph)
                                    |--&gt; SQLite (chats, proposals, audit)
                                    |--&gt; any OpenAI-compatible model (Groq gpt-oss-120b in our demo)
</code></pre>
<p>FalkorDB holds every relationship the agent reasons over. If it's down, ORBIT says "FalkorDB offline" and refuses graph features instead of quietly falling back to guessing. The graph isn't decoration. The product runs on it.</p>
<h2>Things that surprised us</h2>
<ol>
<li><p><strong>Reasoning models sometimes hide the answer.</strong> <code>gpt-oss-120b</code> occasionally returned an empty <code>content</code> field and put the final JSON at the end of its reasoning. We now recover the last valid JSON object from the reasoning text.</p>
</li>
<li><p><strong>Free tiers limit tokens per minute, per model.</strong> A multi-step agent hits that quickly. Since the limit is per model, ORBIT switches to a fallback model on a 429 before waiting.</p>
</li>
<li><p><strong>Store structure, not content.</strong> Keeping only names and edges in the graph made privacy simple <em>and</em> made queries fast. The agent reads actual file text only when it needs it, after redaction and after the user consents.</p>
</li>
<li><p><strong>Showing the Cypher builds trust.</strong> Once every answer showed the query behind it, wrong answers became easy to debug. It's also the best demo of what the graph is doing.</p>
</li>
</ol>
<h2>Try it</h2>
<pre><code class="language-bash">git clone https://github.com/SinghCharanjeet11/orbit.git &amp;&amp; cd orbit
cp .env.example .env        # add any OpenAI-compatible key (we used Groq)
docker compose up -d        # FalkorDB
python scripts/dev.py       # backend + frontend
</code></pre>
<p>Open <a href="http://localhost:5173">http://localhost:5173</a>, authorize a folder (ORBIT's own <code>backend</code> folder is a good first one), and ask the graph agent what depends on what.</p>
<hr />
<p>Built by Charanjeet and Chirag. Thanks to the FalkorDB team for a graph database fast enough to sit inside an agent loop.</p>
]]></content:encoded></item></channel></rss>