We Gave Our AI Agent Its Own Database. Here's Why That's Safe.
Letting an AI agent touch a database sounds risky. Scoped correctly, with a hard read/write split and zero access to real business data, it's actually what makes multi-step tasks reliable.
"We gave our AI agent database access" is the kind of sentence that should make anyone paying attention nervous, and it's a fair reaction. Most of the AI horror stories that actually make the news involve a model touching data it shouldn't have, in a system with no meaningful boundary around what it was allowed to do once it got there. So it's worth being precise about what we actually built, because the honest version isn't "the agent can reach into a database," it's "the agent has its own database, structurally walled off from anything real, with a hard split between what it can read freely and what needs a human's sign-off." That precision is the entire point.
The problem: some tasks need real structured state, not prose memory
We've written separately about the memory system every agent on our harness gets, prose-style facts it can save and search across conversations. That's the right shape for preferences, context, things worth remembering about a person or a relationship. It's the wrong shape for a task that genuinely needs structured, queryable state: a running list with several fields per item, a set of records being built up incrementally across a multi-step task, something you'd actually want to filter or count rather than just recall as a paragraph of text. Trying to force that into prose memory works for a handful of entries and falls apart past that, the same way trying to run a business off a stack of sticky notes works until it doesn't.
The rule: never anywhere near production data
The database the agent gets is entirely its own: a completely separate file from the harness's own conversation and session storage, and nowhere near any actual business system, CRM, or customer record. It can create whatever tables a given task needs and write to them freely, once approved, with zero risk to anything else the harness or a client's actual business runs on. If something goes wrong, the failure is contained to a sandboxed file the agent made for itself, not to anything that matters. That containment is the actual safety mechanism, not a promise about the model's good behavior.
Read vs write: two tools, two different trust levels
The capability is split into two separate tools on purpose, because reading and writing carry genuinely different risk. One tool lets the agent run a read-only query against its own database with no approval required, the same reasoning behind exempting memory search from approval: requiring a human to bless every single lookup would make the tool useless without adding any real safety, since a read can't change anything. The other tool handles every kind of write, creating a table, inserting a row, updating or deleting one, altering a schema, and every one of those goes through the same approval gate as any other consequential action elsewhere in the harness. Reads are free. Writes wait for a yes.
Structurally enforced, not just described
The same standard we hold for every approval exemption applies here too: the read tool doesn't just promise to only read, it structurally rejects anything that isn't a SELECT or a read-only query before it ever reaches the database, so there's no path where a cleverly crafted input turns a supposed read into a write. And every query, read or write, goes through parameterized values rather than a string built by concatenating the model's own text together, the same SQL-injection discipline you'd expect from a human-written backend handling untrusted input. The model's output is untrusted input, as far as the database is concerned, exactly the same way a form submission from a stranger on the internet would be.
Stopping table sprawl before it starts
A subtler failure mode with giving an agent freedom to create its own tables is drift: ask it to track something across several separate conversations and, without any coordination, it might create three near-duplicate tables under slightly different names instead of reusing the one it already made last week. The tool's own instructions address this directly, telling the agent to check what already exists, quite literally query the database's own schema first, before creating anything new, and to insert into an existing table rather than spinning up a near-duplicate if one already covers the same data.
We don't just rely on the model following that instruction perfectly every time, though, because instructions in a prompt are a nudge, not a guarantee. A separate self-monitoring pass we run over the agent's audit trail specifically checks for tables that look like the same thing under different names and flags it as a finding if it spots one. That's a belt-and-suspenders approach worth naming explicitly: the prompt tries to prevent the mess before it happens, and an independent check catches it afterward if the prevention didn't fully work. Neither layer is trusted to be sufficient on its own.
What this actually unlocks
With that scoping in place, an agent can genuinely track progress on something across a long, multi-step task, building a structured checklist as it works through a batch of items, recording what it's found so far while researching something, keeping a running tally that would be awkward to hold as prose, all without ever touching a business's actual systems to do it. That's a meaningfully more capable agent than one limited to unstructured memory alone, for exactly the class of tasks where "remember roughly what happened" isn't precise enough and "query exactly what's been recorded so far" is what's actually needed.
- Tracking which items in a batch task have been processed, skipped, or still need attention, queryable rather than re-read from a paragraph every time
- Building up a structured set of findings during a multi-step research or lookup task, with fields the agent can filter and count
- Maintaining a working checklist across a long task that spans more turns than comfortably fit in prose memory
- Recording intermediate state for something that will eventually get summarized and handed to a human, without polluting the harness's own conversation history with scratch work
A concrete example: tracking a multi-item task
Say an agent is asked to work through a batch of forty leads, checking each against a set of qualification criteria. Holding that as prose memory, forty entries deep, each one needing to be re-read and re-parsed on every turn to figure out what's left to do, gets unwieldy fast and is exactly the kind of task structured storage is built for instead. On its first turn, the agent checks whether a table for this already exists, finds none, and creates one, approved, with columns for the lead's identifier, its qualification status, and a note. As it works through the batch across several turns, it inserts and updates rows, each write approved individually, and can query at any point, freely, without needing anyone's sign-off, to check exactly how many are still pending versus already qualified.
By the time it's done, there's a queryable, structured record of the entire batch, not a long paragraph the agent would have had to hold in its head and hope it summarized accurately. And because every write went through approval, there's a full trail of exactly what was created and changed along the way, checkable after the fact by anyone who wants to see how the agent actually got from forty unprocessed leads to a finished list.
"Isn't this just SQL injection waiting to happen?"
It's a fair question to ask directly, since handing a language model the ability to generate SQL sounds, on the surface, like exactly the setup that causes SQL injection vulnerabilities in human-written code. The answer is that the same defenses that stop injection in ordinary application code stop it here too, applied consistently rather than assumed. Every value the model wants to use in a query is passed as a named parameter, never spliced directly into the SQL string, which is the standard fix for injection regardless of who or what generated the query. On top of that, the read tool structurally can't execute a write no matter what string it's handed, and every write, regardless of what it contains, waits for a human before it touches anything. The model generating the SQL isn't the risk. Trusting generated SQL to run unchecked and unparameterized would be, and that's precisely the part we didn't build.
What we still wouldn't do with it
It's worth being equally clear about what this isn't. This is not how an agent gets access to a client's actual CRM, invoicing system, or customer records, that still goes through a specific, human-reviewed integration built for that exact system, scoped to exactly the actions a given Skill needs and nothing more. The agent's private database is for its own working state on a task, full stop, and we'd treat any suggestion to blur that line, letting an agent's scratch database somehow also double as a path into real business data, as exactly the kind of shortcut that turns a safe, well-scoped capability into the AI horror story this piece opened with. The whole value of the design is that the two are never allowed to touch.