Local-First Analytics for Multi-Tenant AI Agents

By Arshad Ansari

The first thing to settle when you put an AI agent in front of analytics for many customers is that the agent is not your security boundary. It can be asked nicely, it can be given a long system prompt full of rules about which tenant it is serving, and it will still be a program that turns text into queries. Text can be manipulated, whether by a user who is curious, a document that carries an injected instruction, or a model that simply gets a clause wrong on a Tuesday afternoon. If the only thing standing between tenant A and tenant B's numbers is the agent remembering to add a filter, you do not have isolation, you have a hope. I made the narrower version of this point in how to connect an LLM to your database safely, where the advice was to enforce row level security in the database and not in the prompt. This post takes that one step further and asks what the whole architecture looks like when you design for many tenants from the start, and when you want to keep it small and local rather than reaching for a warehouse.

It helps to pull apart three concerns that tend to get blended together in a design review. The first is data isolation, which is the question of what this tenant is allowed to see. The second is compute isolation, which is whether one tenant's work can affect another's, through memory, CPU, a runaway query, or a noisy neighbour that makes everyone slow. The third is agent reasoning, which is the question of what queries to run given the data that is allowed. Only the third belongs to the agent. The first two belong to the platform, and they have to hold even when the third goes badly wrong. A useful test is to imagine the agent is fully compromised and writing the worst SQL it can. Data isolation means that worst SQL still returns only that tenant's rows. Compute isolation means that worst SQL can slow down that tenant's own session and nobody else's. If both of those are true, the agent is free to be as clever and as fallible as it likes.

That leads to where the tenant identity should come from. It should come from the authenticated session, resolved by your own code before the agent ever runs, and never from anything the model says or anything a user types into the chat. The user logs in, your service knows which tenant they belong to, and that fact is attached to the connection or the environment the agent's queries will run in. Then it is enforced deterministically below the agent. In a shared database that usually means row level security policies keyed on a session setting that only your service can set, or tenant scoped credentials and views, so the role the agent connects as simply cannot name another tenant's data. In a different shape it can mean a tenant specific analytical environment, where the thing the agent talks to only contains that tenant's data in the first place. Which of these you pick matters less than the property they share, which is that the check happens in code that does not read the model's output to decide who the caller is. AWS has published a write up from June 2026 on multi-tenant LLM analytics that leans on row level security for exactly this reason, and it is worth reading alongside their guidance on building multi-tenant architectures for agentic AI, which frames the same problem at the level of the whole system and not only the database. I would read both for the pattern and then check the specifics against your own stack.

This is where the local first angle gets interesting. Row level security on one big shared database is a fine answer, but it is also a fairly heavy one, and the isolation you get is a promise made by policies that all tenants live behind. The local first instinct is to ask for the smallest analytical environment that can answer this tenant's questions, and to make that environment cheap enough that you can have one per tenant. A good shape for that is to keep the data as immutable Parquet files in object storage, laid out so that each tenant's data sits under its own prefix, and to run small DuckDB workers that are given access to one tenant's files and nothing else. The files are shared infrastructure that nobody edits in place, so there is no write contention and no question about which version a worker sees. The worker is tiny, starts quickly, and has only the credentials and the paths for a single tenant, so the boundary is the storage permission and the worker's scope, not a clause in a query. Data isolation then stops being something the agent has to respect and becomes something it cannot violate, because the other tenants' files are not reachable from where its queries run. MotherDuck has written about this style of design under the name Hypertenancy, giving each tenant its own small database engine instead of packing everyone into one big shared one, and I find it a useful vocabulary for the idea even if you run your own workers rather than theirs.

Concurrency is a separate problem from row level security, and it is the one that quietly breaks demos once real users arrive. It is tempting to have the agent open the DuckDB file directly, and that works nicely for one person. When twenty sessions each start their own process and all try to open the same DuckDB database file, you are asking for lock conflicts and unpredictable behaviour, and no amount of careful policy writing fixes that, because it was never a policy problem. DuckDB is an excellent engine for a single process working on its own data, and it is not a multi writer server. So treat concurrency as its own design question. One answer is a pool of analytical workers that your service owns, where a request is routed to a worker scoped to the right tenant and the agent never touches a file at all. Another is isolated compute per tenant or per session, a fresh worker that reads the shared immutable Parquet and is thrown away afterwards. Because the underlying data is immutable and shared, spinning up another reader is cheap and there is nothing to coordinate. The same thinking applies to compute isolation. Give each worker a memory limit and a query timeout, so a runaway query from one tenant uses up its own allowance and not the neighbour's. I went through the production side of this in is DuckDB safe for production and the SaaS framing in DuckDB as a SaaS warehouse, and the practical Parquet setup is in local first analytics in practice.

Put together, the picture is a clean division of labour. The platform authenticates the user and decides the tenant. The platform hands the agent an environment that physically contains only that tenant's data and has hard limits on what it can consume. The agent does the part it is actually good at, which is reading the schema it was shown, deciding what to ask, writing the SQL and explaining the result. If it writes something silly, the worst outcome is a bad answer or a timeout for that one tenant. That is a failure you can live with, and it is a very different thing from a leak.

This approach has limits, and it is honest to say so. If you have thousands of tenants with very small data, or one tenant with data far larger than a single worker can handle, or a need for heavy concurrent writes, the balance shifts toward a shared warehouse with row level security doing the isolation work. I wrote about where that line sits in when local first runs out of road. The point of this post is not that local always wins. It is that the agent should never be the thing you are relying on to keep tenants apart, and that a small, immutable, per tenant environment is a surprisingly cheap way to make that true.

Does DuckDB fit your system?

The 16-question production-fit checklist I run before putting DuckDB on a critical path — writers, working set, durability, memory, and who talks to it. Each question comes with what a bad answer sounds like.

The whole checklist, sent at once. No confirmation step.

What breaks and what it costs — pipelines, warehouse bills, and the failures that only show up in production. A few a month, never padded to hit a schedule. No sequence, no pitch deck. Reply 'stop' once and you're off — it reaches me, not a queue.

Want the whole playbook?

If this was useful, the long version is my book. Local-First Analytics — 313 pages, runnable code for every chapter — is the full build: DuckDB, Parquet and Arrow, from install to production. On Amazon, and chapter 1 is free to read here.

Get the book

Not ready to buy? Read chapter 1 free — the whole chapter, no email required.

Rather talk it through? Book a free 30-minute call. No slot that suits your time zone? Email info@hikmahtech.in.