← All posts

The assistant lives in the database too

The usual way to add an assistant to a business application goes something like this. Export the schema into a vector store. Write a document explaining the domain to the model. Give it a database connection, then add a paragraph to the prompt asking it politely never to run an UPDATE. Ship it, and watch the support queue.

Two things are wrong with that, and they are the same two things wrong with the frontend-backend split we wrote about last time. The truth about the application now exists in two places, and one of them goes stale the moment someone edits a button. And the only thing standing between the model and your production data is a sentence written in English.

If the application already lives in the database, the assistant can live there too — and then its limits are enforced by the database engine instead of by a paragraph of instructions.

In TSQL.APP that assistant is ask_ai: a set of stored procedures, tables and views inside the Solution's own project database, one per Solution. Not a service running beside the app. There is no separate process to deploy, and nothing to keep in sync.

Talking to her

Users reach her from a button in the app. But because she is just objects in a database, plain T-SQL is a first-class way in:

sql
EXEC dbo.sp_ask_ai N'How many cards does this application have?';

That is the whole interface — from SSMS, from sqlcmd, from an Agent job. It is deliberately not a second implementation of the agent: the call posts to the same Service Broker queue the app posts to, and everything downstream is the same code a user gets in the chat modal. Same worker, same conversation history, same tools, same fence. A test path that exercised a private copy would prove nothing.

What she can do from there is read: the catalog, the metadata that describes every card, field and button, the body of any action script, and the business tables themselves through SELECT. She can keep lessons about what she learns. And she can write a button.

The obvious objection

Nobody sane wants a language model holding a connection to their production database. So it does not have one.

Every query she writes is executed impersonated as a dedicated login with read-only rights, and DENY on the things she must never see — the API key settings, the token tables, and the raw conversation table. She reads her own threads through a view scoped to the caller, so one user's conversation cannot surface in another's.

The interesting part is what happens when she needs to create something. She owns exactly one schema, called ai, and has ALTER on nothing else. So when she issues DDL, SQL Server refuses anything outside that schema on its own authority. There is a string check in front of it as well, but its job is only to give her a readable error message — if the check and the permission ever disagree, the permission is right.

The fence is ownership, not parsing. That distinction matters, because a fence made of string inspection is a fence you have to be cleverer than the caller to maintain, forever. Ownership also has a useful second effect: it deliberately breaks ownership chaining into dbo, so a view she creates cannot read anything she could not have read directly.

Writing a button is fenced differently again, because an action script runs with the application's own rights — far more than hers. So that path is: refuse anything protected, snapshot the current version, write, compile-check the result, and automatically put the snapshot back if it does not compile. Every version is kept and restoring one is a tool she has. A failed write costs nothing and returns SQL Server's own error message, phrased as an instruction, and she is expected to fix it herself on the same turn.

And a button she wrote does nothing at all until a human presses it. Compiling is not the same as being correct, and we do not pretend otherwise.

The part that surprises people

Here is the mechanism most people do not guess, and it is the one that decides whether an assistant like this is still useful in a year.

Structure is read live. Meaning is stored.

Nothing about which cards, buttons and fields exist is ever copied into her memory. The tool that lets her explore an application reads the metadata tables straight off the live database, every time she asks. Same for object definitions, which come back with their immediate callers and callees attached, derived from the catalog at the moment of reading.

That is a deliberate refusal of the standard pattern. A stored copy of a schema is wrong as soon as someone adds a column, and it fails silently — the model answers confidently from the copy. Worse, a "mind map" of a database with a node per table and an edge per foreign key is not a mind map at all. It is a second, worse copy of something the catalog already holds perfectly.

So what does get stored is only the part no catalog can produce: what a screen is for, what this particular business calls a concept, which button actually does the job someone described in their own words. Her graph is meaning, and the procedure that writes relationships into it will actively refuse an edge the catalog could have derived itself.

The consequence is that her knowledge of your application cannot go stale, because she does not have any. She has a way of looking, and a store of what looking has taught her. Rename a column and she is not wrong — she is simply looking at the new name.

The same honesty applies to the limits of what the catalog knows. Action scripts are dynamic SQL, which dependency tracking cannot see, so her tooling reports "nothing static references this" rather than "nothing uses this". A tool that overstates its own certainty is how you get a confident, wrong answer.

Why build it this way

The design test we apply to every change is whether it lives in the structure or in the wording. Ownership, permissions, and what a tool deterministically returns hold no matter which model arrives next year. Tool descriptions and prompt phrasing are persuasion — useful, but the next model may simply ignore them.

Which sets the goal: make the harness good enough that even a modest model gives excellent answers. When results depend heavily on which model is behind it, that is a gap in the harness, not a problem with the model.

There is documentation and a programming reference online, and you can always book a demo.