The agent's harness is a database.
Most LLM agents hide their memory and control flow in a JavaScript object or a server. Tables does the opposite: the entire agent — its memory, its tool calls, its artifacts, and the loop that drives it — lives inside an in-browser SQLite database. You can SELECT * FROM messages and read the agent's whole mind.
The ReAct loop is not a while loop in JavaScript. It is a cascade of SQLite triggers. Drop a user message into messages and the database reacts to itself — thinking, acting, observing — until it stops. The only JavaScript that runs is the thin layer that does browser I/O and network requests. Everything else is declarative SQL living in the database.
Trace one turn, step by step.
Press play or click a step. Watch a single user message ripple through the trigger cascade, and watch the messages table fill in as each trigger fires.
What happens
SQL that runs
Live messages table
| id | role | content / tool_calls |
|---|
Everything is a table.
Every piece of agent state is a row in a table you can query. Click any object to see what it is, why it exists, and the real DDL that creates it. Filter by category to focus.
← Select an object to inspect it.
Rewind the data. Compact the memory.
Two independent systems give the agent a sense of time — one for undoing what it did to your data, one for forgetting what it no longer needs in context.
⟲ Rewind — undo a turn
Every turn runs inside an atomic SAVEPOINT turn_sp. Per-table capture triggers (cap_*) write a pre/post row image for every change into turn_changesets; DDL goes to turn_ddl_log with a pre-image. Every journaled write also carries a source — who made it (agent, scratchpad, app, engine, host) beside the turn id's when. Click the ⟲ on any message and the inverse of that conversation's own writes is replayed — data and schema return to that turn, the chat rewinds, and writes made by the interface are left alone (the confirm names them first). A 20-turn ring buffer bounds the cost.
◐ Compact — shrink the context
When token usage nears the model's window, the oldest turns are summarized into a compactions row and a watermark advances. The v_active_context view simply stops reading rows below the watermark. The messages table is never touched — it stays a complete, immutable audit log.
The agent can't break its own harness.
A powerful agent that can run arbitrary SQL could also DROP TABLE messages and destroy itself. Tables draws a hard line: the app's own tables are protected from destructive DDL, so the agent can analyze your data without ever corrupting the machinery that runs it.
✓ Your data — fully writable
- sample_data
- any table you create
- CSV imports
- materialized tool output
- your views
- artifacts · artifact_styles
⛔ The harness — read-only to the agent
- messages · sessions · session_context
- system_config · system_files · llm_profiles · tools
- turn_changesets · turn_ddl_log · compactions
- tool_approvals · dashboard_cards (retired)
- documents · documents_fts
The guard lives in two places — the agent's run_dynamic_sql and the direct-SQL console's write gate — both call the same isProtectedObject() check. You can still query the harness (that's the whole point); you just can't drop or alter it.
Why this is even possible: JSPI.
The crux of the whole design is a single capability: a SQL function that can wait on the network and then resume. That's what JavaScript Promise Integration (JSPI) adds to WebAssembly — and it's what lets an LLM call happen inside a trigger.
When agent_think calls the ask_llm UDF, the UDF makes an HTTP request to your LLM provider. Without JSPI, the WASM module would block the whole browser thread. With JSPI, the fiber pauses — the trigger is genuinely suspended mid-statement — and the browser is free to render, stream tokens, and handle clicks. When the LLM's JSON arrives, the fiber resumes and the trigger continues, inserting the assistant row and re-triggering the cascade.
-- inside the agent_think trigger, this one call suspends the whole WASM fiber: SELECT ask_llm( (SELECT json_group_array(json_object(...)) FROM v_active_context), (SELECT json_group_array(json(schema)) FROM tools) ) AS llm_response; -- ⏸ fiber parks here while the network round-trip happens