N
Nicholas Lisac
Guest
When you think of an agent, you think of the LLM - the back-and-forth with the model - but for it to be an agent, it has to do stuff. It has to have what is called a 'ReAct loop' in which the agent itself interacts with its environment, calls tools, and decides when its turn is done.
That seems very complicated - you have to create the conditions for the loop, you have to provide tools, you have to interpret the responses and route the messages with logic.
In addition to the ReAct loop, you need other things. You need chat history, the live working transcript, sessions, tool definitions, an environment in which to execute code, a way to compact the conversation, a place to store retrieved information. I wanted to see if I could build all of this in SQL. And I basically did. Its name is Tables.
Tables is tables. Tables runs in a web app, but they don't have to. When you export from the web app, you get a single .sqlite file - a cartridge - and inside that file is the Python code that boots it:
Tables is built on SQLite. SQLite does need a small amount of external piping to come to life - in my case a bit of JavaScript for the web app, or a little Python for the CLI. To be honest about what "almost" means: what lives outside the file is one HTTPS call out to a model, the ferry that carries it, the thing that draws the results on screen, and your API keys. The agent's entire state and control flow are the file. Everything else is the HTTPS transportation layer.
The core loop is a trigger. When a new row is written to the `messages` table, the trigger fires and the cascade starts: it hands the active context to the model, writes the model's answer back into `messages` as a new row, and that insert fires the same trigger again.
If the answer contained a tool call, a second trigger runs the tool and its result lands in `messages` too, which continues the cascade. It repeats until the model stops asking for tools. That's the whole loop, and it needs one flag to exist at all: SQLite doesn't fire triggers from inside triggers by default, so the file sets `PRAGMA recursive_triggers = ON` and calls itself to life.
The model's main tool is `execute_sql` - it thinks and acts inside a SQLite database, which is its whole environment. I did build out the rest of the tool set because I wanted something I could actually use: `search_web` and `fetch_url` because those are table stakes, `materialize` to turn raw JSON from a tool result into a real table with `json_each()` so the data doesn't have to be retyped through the model, and `search_documents` / `ingest_document` backed by an FTS5 table for recall.
Messages are a table. Sessions are a table. The list of tools - a table. Approval rules? Table. The active context is a view that handles compaction. And as mentioned before, the loop is a trigger.
There are some cool things that fall out of this when compared to other agent harnesses. Tables is literally one file. You can take your .sqlite3, send it to another computer, open tables.nicholaslisac.com, load the cartridge, and your agent is good to go - same conversation, same data, same identity - or run the Python that's sitting in the file and never open a browser. The API key never travels in the file; it gets paired once per machine for security.
The chat history is native and easily explorable. Everything the agent has ever done or seen is in the SQLite database it operates from, and nothing is more than a SQL query away. Every write is journaled with who made it - the agent, you at the keyboard, or the interface on your behalf - so a turn can be undone without undoing your own work.
Everything the agent learns ends up in the same place as everything you give it. Drop a CSV on the window, and it becomes a typed table; have it fetch a page and materialize the results, and that becomes a table too. After that they all swim the same way, because for the agent there is no difference between its own history and your data - it's all just rows and columns.
Tables is naturally sandboxed to their database file, with the exception of their web capabilities. The agent can't touch your filesystem, can't install anything, can't notify you. It can't even break its own harness - the internal tables are protected from DDL and DML, on the agent's path and yours.
You also get a scratchpad so Tables acts as your own SQL workbench:
Absolutely not. I wondered if I could build an AI agent loop with SQL as the engine and set out to see if it worked. I got a little carried away and made an entire web app to prove a point. It's part research project, part portfolio piece. And I like it.
Tables is not a coding agent. It doesn't have access to other files on your computer. It can't do most things people want out of their AI in 2026. Tables is just tables. But those tables can talk back to you, do real work in a database, do analysis and gather information from around the internet, and prove that SQL is (almost) as powerful as languages like Python or TypeScript.
The code is open - you can see it on GitHub, and you can check out the explainer here or go right to the app here: tables.nicholaslisac.com. You can use Tables, but you will need to BYOK, whether that is local or cloud.
No guarantee of performance or success - but it will probably be fun to talk to Tables.
That seems very complicated - you have to create the conditions for the loop, you have to provide tools, you have to interpret the responses and route the messages with logic.
In addition to the ReAct loop, you need other things. You need chat history, the live working transcript, sessions, tool definitions, an environment in which to execute code, a way to compact the conversation, a place to store retrieved information. I wanted to see if I could build all of this in SQL. And I basically did. Its name is Tables.
What is Tables?
Tables is tables. Tables runs in a web app, but they don't have to. When you export from the web app, you get a single .sqlite file - a cartridge - and inside that file is the Python code that boots it:
python3 your-agent.sqlite3 "What tables do I have?" and they talk back to you.Tables is built on SQLite. SQLite does need a small amount of external piping to come to life - in my case a bit of JavaScript for the web app, or a little Python for the CLI. To be honest about what "almost" means: what lives outside the file is one HTTPS call out to a model, the ferry that carries it, the thing that draws the results on screen, and your API keys. The agent's entire state and control flow are the file. Everything else is the HTTPS transportation layer.
The loop is just a trigger.
The core loop is a trigger. When a new row is written to the `messages` table, the trigger fires and the cascade starts: it hands the active context to the model, writes the model's answer back into `messages` as a new row, and that insert fires the same trigger again.
If the answer contained a tool call, a second trigger runs the tool and its result lands in `messages` too, which continues the cascade. It repeats until the model stops asking for tools. That's the whole loop, and it needs one flag to exist at all: SQLite doesn't fire triggers from inside triggers by default, so the file sets `PRAGMA recursive_triggers = ON` and calls itself to life.
The model's main tool is `execute_sql` - it thinks and acts inside a SQLite database, which is its whole environment. I did build out the rest of the tool set because I wanted something I could actually use: `search_web` and `fetch_url` because those are table stakes, `materialize` to turn raw JSON from a tool result into a real table with `json_each()` so the data doesn't have to be retyped through the model, and `search_documents` / `ingest_document` backed by an FTS5 table for recall.
Messages are a table. Sessions are a table. The list of tools - a table. Approval rules? Table. The active context is a view that handles compaction. And as mentioned before, the loop is a trigger.
Unexpected Outcomes
There are some cool things that fall out of this when compared to other agent harnesses. Tables is literally one file. You can take your .sqlite3, send it to another computer, open tables.nicholaslisac.com, load the cartridge, and your agent is good to go - same conversation, same data, same identity - or run the Python that's sitting in the file and never open a browser. The API key never travels in the file; it gets paired once per machine for security.
The chat history is native and easily explorable. Everything the agent has ever done or seen is in the SQLite database it operates from, and nothing is more than a SQL query away. Every write is journaled with who made it - the agent, you at the keyboard, or the interface on your behalf - so a turn can be undone without undoing your own work.
Everything the agent learns ends up in the same place as everything you give it. Drop a CSV on the window, and it becomes a typed table; have it fetch a page and materialize the results, and that becomes a table too. After that they all swim the same way, because for the agent there is no difference between its own history and your data - it's all just rows and columns.
Tables is naturally sandboxed to their database file, with the exception of their web capabilities. The agent can't touch your filesystem, can't install anything, can't notify you. It can't even break its own harness - the internal tables are protected from DDL and DML, on the agent's path and yours.
You also get a scratchpad so Tables acts as your own SQL workbench:
!SQL runs your query and shares the result with the agent, !!SQL runs it privately and the agent never sees it. And if you pin a query you like, it becomes an artifact.Is this the future of Agent harnesses?!
Absolutely not. I wondered if I could build an AI agent loop with SQL as the engine and set out to see if it worked. I got a little carried away and made an entire web app to prove a point. It's part research project, part portfolio piece. And I like it.
Tables is not a coding agent. It doesn't have access to other files on your computer. It can't do most things people want out of their AI in 2026. Tables is just tables. But those tables can talk back to you, do real work in a database, do analysis and gather information from around the internet, and prove that SQL is (almost) as powerful as languages like Python or TypeScript.
The code is open - you can see it on GitHub, and you can check out the explainer here or go right to the app here: tables.nicholaslisac.com. You can use Tables, but you will need to BYOK, whether that is local or cloud.
No guarantee of performance or success - but it will probably be fun to talk to Tables.