<?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[CerebrumKit]]></title><description><![CDATA[CerebrumKit]]></description><link>https://cerebrumkit.hashnode.dev</link><image><url>https://cdn.hashnode.com/res/hashnode/image/upload/v1593680282896/kNC7E8IR4.png</url><title>CerebrumKit</title><link>https://cerebrumkit.hashnode.dev</link></image><generator>RSS for Node</generator><lastBuildDate>Sun, 04 Oct 2026 15:13:04 GMT</lastBuildDate><atom:link href="https://cerebrumkit.hashnode.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[I moved the agent topology out of my code and into the database]]></title><description><![CDATA[The same month, every time
The brief is always some version of "the agent should answer customer questions about their orders". Before I can write a single line about orders, I need: an agent loop tha]]></description><link>https://cerebrumkit.hashnode.dev/i-moved-the-agent-topology-out-of-my-code-and-into-the-database</link><guid isPermaLink="true">https://cerebrumkit.hashnode.dev/i-moved-the-agent-topology-out-of-my-code-and-into-the-database</guid><category><![CDATA[AI]]></category><category><![CDATA[llm]]></category><category><![CDATA[Open Source]]></category><category><![CDATA[architecture]]></category><category><![CDATA[Python]]></category><dc:creator><![CDATA[Islomkhon Nizomkhonov]]></dc:creator><pubDate>Thu, 24 Sep 2026 12:11:54 GMT</pubDate><content:encoded><![CDATA[<h2>The same month, every time</h2>
<p>The brief is always some version of "the agent should answer customer questions about their orders". Before I can write a single line about orders, I need: an agent loop that calls a model and runs the tools it asks for, a registry of those tools, somewhere to keep the customer and order data, a rule for which agent answers first, and a panel so the client can change the wording without calling me.</p>
<p>That is a month, and it is invisible in the invoice. So I built it once.</p>
<h2>The decision: agents are data</h2>
<p>The version of this I had built before kept the topology in source code. Which agents exist, what each one is told, which tools it can call, who answers first - all of it in Python, so every change was a deploy.</p>
<p>The new version keeps all of it in the database:</p>
<table>
<thead>
<tr>
<th>Thing</th>
<th>What it is</th>
<th>Where it lives</th>
</tr>
</thead>
<tbody><tr>
<td>Tool</td>
<td>OpenAI function spec + Python body</td>
<td><code>tools</code> table</td>
</tr>
<tr>
<td>Skill</td>
<td>Instructions + the tools they need</td>
<td><code>skills</code> table + <code>skill_tool</code></td>
</tr>
<tr>
<td>Agent</td>
<td>Description (its system prompt) + skills</td>
<td><code>agents</code> table + <code>agent_skill</code></td>
</tr>
<tr>
<td>Workflow</td>
<td>start / group / stop graph</td>
<td><code>projects.workflow</code> JSON</td>
</tr>
<tr>
<td>Storage</td>
<td>Your own tables + column descriptions</td>
<td>real tables in the same database</td>
</tr>
<tr>
<td>Context tool</td>
<td>A tool run <em>before</em> the agent reads the message</td>
<td><code>agent_context_tools</code></td>
</tr>
</tbody></table>
<p>Editing a tool body in the panel takes effect on the next message. The compiled body is cached by (tool id, body hash), so the cost of that is a dictionary miss.</p>
<h2>What it buys</h2>
<p><strong>The domain expert can change the agent.</strong> The valuable sentence in a support agent is not <code>def lookup_order(...)</code>. It is "never quote a delivery date you have not read from the order". That belongs to the person who owns the refund policy, and it should be editable on a Tuesday afternoon.</p>
<p><strong>Multi-agent without a framework in your imports.</strong> A workflow graph decides which agent or group answers and in what order; a second group can review what the first one produced. One agent can delegate a self-contained task to another (depth capped at 3).</p>
<p><strong>The model-facing contract and the implementation are one row.</strong> When the tool description and the Python body can drift apart, they do. Here they are edited on the same screen.</p>
<h2>What it costs (do not skip this section - it is why engineers trust you)</h2>
<p><strong>You give up code.</strong> Complex conditional routing becomes a canvas with groups, not an <code>if</code>. If your agent's control flow is genuinely a program, this is the wrong tool and a library is the right one.</p>
<p><strong>Tool bodies are code execution.</strong> They run with full builtins and are not sandboxed - deliberately, because the shipped tools import <code>requests</code>, SQLAlchemy and app internals, and one passes model-authored SQL to the database. So tool authoring is admin-only, and a tool body is reviewed like a commit.</p>
<p><strong>No transcript in the prompt.</strong> Earlier chat messages are not replayed; memory (a note keyed by agent and user) and tools carry the facts. Runs get cheaper and more predictable, and the tools have to be good enough to compensate.</p>
<p><strong>One process.</strong> The websocket registry and in-flight tasks are in process memory, so the documented deploy is one uvicorn worker behind nginx.</p>
<h2>The detail I did not expect to matter: column descriptions</h2>
<p>When the agent decides which tool to call, what it reads is the text you wrote. The description on a database column - "the delivery date quoted to the customer at checkout" - is a prompt. Schema design for an agent is prompt engineering with a type system.</p>
<p>The panel makes you write a description on every table and every column for that reason, and the tools read them.</p>
<h2>Two agents, one catches the other</h2>
<p>Here is the run that convinced me the workflow was worth building.</p>
<p>A customer asks why order AC-10477 has not arrived. The first agent finds the customer, reads the order, searches the help articles, and answers: the order is two days past its promised date, so the late-delivery credit applies.</p>
<p>The second agent - a different brief, same tables - reads the same order and points out that the credit only applies while the status is <code>shipped</code> or <code>packed</code>. This order is <code>delayed</code>, so the first agent's promise was wrong.</p>
<p>I did not write that check as an <code>if</code>. I wrote it as a second agent with a different instruction and put it in the next group of the workflow.</p>
<h2>How to try it</h2>
<p>Postgres via <code>docker compose up -d</code>, <code>seed_all.py</code>, <code>npm run dev</code>, and the admin and client accounts that the seeder creates from your <code>.env</code>. Two commands and a seed, roughly two minutes.</p>
<p>Repo: <a href="https://github.com/islomkhon/CerebrumKit">https://github.com/islomkhon/CerebrumKit</a></p>
<h2>Closing</h2>
<p>If you are building agents for businesses, I would like to hear where you draw the line between data and code - and whether you moved it after your first production incident. That is the question I am still chewing on.</p>
]]></content:encoded></item></channel></rss>