We Built a Database for Agents
We wanted agents to reason over complex data. Ad hoc tools were not enough. So we built a database for agents, which gave them full SQL over a federated, sandboxed semantic schema so they could join, filter, and aggregate across sources in one query.

By Preetam Jinka
Co-founder and Chief Architect
Mar 25, 2026
6 min read
Last Updated: Mar 25, 2026
There's a lot of discussion right now about giving agents filesystem access: a simple, well-understood interface that abstracts away complexity underneath. The instinct is right. Agents need stable, expressive interfaces that hide implementation details and let them focus on the task. The question is which interface fits the problem.
For agents doing analytical work across structured data, the answer is a database, not a filesystem. Here's why we built one, and how.
We started with tools. If an agent needs to check an account, give it an account tool. If it needs conversations, give it a conversations tool.
This breaks down quickly. Tools are narrow and procedural. When a question cuts across data sources, no single tool can answer it:
A growing list of product-specific interfaces means the model has to discover, sequence, and orchestrate them correctly. In practice, it doesn't.
So we built a different interface: a database.
A semantic database and query engine for agents.
Here's a representative query an agent might run:
SELECT
a.account_id,
a.name,
COUNT(t.id) AS open_ticket_count,
MAX(p.churn_risk) AS max_churn_risk
FROM accounts a
JOIN tickets t
ON t.account_id = a.account_id
AND t.status = 'open'
LEFT JOIN prediction_account_history p
ON p.account_id = a.account_id
AND p.as_of_date >= date('now', '-90 days')
WHERE a.health_band IN ('at_risk', 'critical')
GROUP BY a.account_id, a.name
HAVING MAX(p.churn_risk) > 0.6
ORDER BY open_ticket_count DESC
LIMIT 25;
Three virtual tables, each backed by a different system:
accounts: entity and segment fields merged with JSON enrichments, filtered by session visibility.tickets: open ticket rows pulled from whichever help desk integration or internal store owns them for that workspace.prediction_account_history: churn/risk scores over time from a scoring pipeline or history table.account_id is a stable join key in the semantic schema regardless of how each backend stores its data. Aggregates run in the SQLite engine. From the agent's perspective: one query, three named tables, no tool orchestration.
Models are already good at SQL. This isn't incidental. SQL has decades of documentation, examples, and training data behind it. Models write and debug SQL more reliably than they use large sets of proprietary tool APIs.
The alternative, teaching an agent our internal interfaces, scales poorly. A clean relational surface in a language the model already knows is a better fit. This also connects to a point I made in Beyond Search: Why Customer Success Demands Reasoning: agents doing real analytical work need joins, aggregations, and cross-dataset comparisons. Simple RAG lookups don't support that.
No custom query language. A bespoke DSL is another surface to document, evaluate, and keep aligned with model behavior. Standard SQL gives us syntax validation, tooling, and existing training signal for free.
No direct warehouse access. Pointing agents at the multi-tenant warehouse raises credential scope, blast radius, and the risk of unbounded analytic queries against tenant data. The warehouse is a data source we read from when hydrating virtual tables, not the database the agent connects to.
The stack is Go. We run SQLite in-process and expose remote data through virtual tables so it presents as ordinary relations. I covered the Go/SQLite plumbing, per-connection modules, multitenancy, sql.OpenDB with a custom connector, in Virtual Tables in SQLite with Go. Here I'll focus on what happens when those tables are backed by remote systems.
SQLite only supports nested-loop joins (query planner docs). For each row from the outer table, it calls into your virtual table implementation to fetch matching inner rows. With local data this is fine, the inner probe is a page read. With a virtual table backed by a remote system, the inner probe is a network call.
for each row A in outer_table:
for each row B in inner_table where B joins to A:
emit (A, B)
If the outer table has 10,000 rows and the inner virtual table does one SQL query to a remote database per outer row, that's 10,000 round trips. This is as bad as it sounds. EXPLAIN QUERY PLAN still shows "nested loop" because the plan itself is valid, the cost is hidden inside the virtual table callbacks.
This is the structural problem: nested loops are efficient for an in-process engine with local storage. They compound badly when the inner side touches the network, especially when the query author is an LLM that won't always write tidy join patterns.
Batching with in-memory caching. Virtual table cursors pull remote data in batches into a small in-memory SQLite instance mirroring the virtual schema. When SQLite runs its nested loops, inner probes hit local pages instead of the network.
Hash-bucketed join keys. We partition on join keys so one remote fetch loads an entire partition. Repeated inner iterations in the same bucket reuse cached data. This bounds the number of remote I/O operations relative to loop count and keeps latency predictable on agent-generated SQL, which won't always be optimally shaped.
The filesystem analogy for agents captures something real: the best interfaces for agents are ones that feel simple and familiar but have serious machinery underneath. A filesystem gives agents a stable, hierarchical interface for navigating and manipulating files without exposing the block device. A database gives agents a stable, relational interface for querying and reasoning across structured data without exposing the backends.
They serve different problems. For agents that read files, write outputs, or manage state across sessions, filesystem access makes sense. For agents that need to join, aggregate, filter, and reason across structured business data, a database is a stronger primitive. The expressiveness of SQL and the decades of training signal behind it are hard to match with a file tree.
What we built sits behind that interface: federated query execution across remote backends, per-session caching to keep nested-loop joins from becoming 10,000 round trips, tenant isolation, visibility enforcement, and a semantic schema that stays stable as backing systems change. None of that is visible to the agent. It writes SQL, gets rows back, and can answer hard analytical questions reliably.
If this is interesting to you, we're hiring (especially focused on AI/agent infrastructure). There's a lot left to build!