Our agents ran as tasks on AWS ECS. Not because ECS is special. They needed the company's databases and APIs, and a single run took minutes. Neither of those fits in a browser tab.
That is most of the answer to "server-side or client-side?". It's the old question of backend code vs frontend code, with the same trust rules. What changes with agents is that the code can decide for itself what to call next, so the rules matter more.
The two kinds
A server-side agent runs on your infrastructure. Your keys, your data, your logs. The user sends a request, and your server runs the loop, calls the model, calls the tools, and sends back the result.
A client-side agent runs on the user's machine or in their browser. It uses what's on that machine: the user's files, the user's own credentials, often the user's own API key. A coding agent in your terminal is the common example today.
Why ours ran on the server
Two reasons, and both would have broken on the client.
The first is credentials. The agents worked on the company's own databases and internal APIs. You can't put database credentials in a browser. Anything you send to the browser, the user can read.
The second is time. A run took minutes, not milliseconds. A browser tab can be closed, a laptop can go to sleep, a phone can lose signal. So the work ran as tasks on ECS, picking up jobs from a queue, and the browser only showed progress.
Running on the server brings its own failure. In one pipeline we built, a deploy or a lost container left jobs marked "running" forever, because nothing noticed the worker had gone. The fix was a heartbeat every 30 seconds and a reaper that hands a job to another worker after five minutes of silence, at most three times. If your agent runs for minutes, treat it as a background job, not a web request. More on that in why AI features need background jobs.
Where the keys and permissions live
On the server, credentials live in the server's environment. The agent itself should never hold them. It gets narrow tools instead: "get this customer's open orders", not "run any SQL". The tool holds the credential and checks who is asking. OWASP's entry on Excessive Agency gives the example of an extension that only needs to read data but connects with an identity that can also update and delete. Narrow tools, acting as the user who asked, are the fix.
On the client, the agent uses the user's own credentials, or none. That is fine, because the user is only reaching their own things.
What you never do is put your API key in the client. OpenAI's own API key safety guide says not to deploy keys in browsers or mobile apps, and to route requests through your own backend. Anyone who opens the developer tools can copy the key and spend your money.
There is a middle path when the browser really must talk to the model directly, for example for low-latency voice. OpenAI's Realtime API guide has your server mint a short-lived key for each session and hand only that to the browser. The real key never leaves the server.
Who pays for the tokens
Server-side, you pay. Every user's agent runs on your account. So you need limits per user: steps per run, tokens per day, runs per hour. OWASP lists runaway spend as a risk in its own right, Unbounded Consumption. An agent in a loop is the easiest way to get there, because one request from a user can turn into fifty model calls.
Client-side, the user can bring their own key. That is why so many open-source agent tools work that way: the author doesn't run a server and doesn't carry anyone's bill. The user pays the model provider directly.
This changes your pricing completely. A server-side product has a cost for every user, so you need a price that covers tokens. A client-side tool with the user's own key can be free to give away, because you have no cost per user.
The trust rule underneath
Anything that runs on the client can be changed by the user. That is true of JavaScript, and it's true of an agent. A rule enforced in client code is a suggestion, not a rule.
So the split follows from what the agent needs to touch:
- If it touches your data, your systems or other users' data, it runs on the server, and the checks live there.
- If it only touches the user's own things, it can run on the user's machine.
When client-side is the right call
A coding agent working on your own files is the clearest case. The files are on your laptop, the tests run on your laptop, and the agent acts with your rights, not more. The same goes for a tool for data that should never sit on someone else's server. More on why so many of these ship in the terminal in agents as CLI tools.
The new QuizzaHut, which I am building as a CLI, is client-side for that reason. Your resume and your applications stay with you, and you bring your own key.
One honest caveat. "Stays on your machine" means your files are not stored on our server. The parts the agent sends to the model still go to the model provider. If the data can't leave the laptop at all, you need a model that runs on the laptop too.
What this means for your design
Decide where the agent runs before you write the loop. It decides where the keys live, who pays, which checks are real and what happens when the user closes the tab. Most products end up with both: a server-side agent for anything that touches shared data, and a thin client that shows progress and asks the user to approve. If you are moving an agent from a laptop demo to a server that many users share, that is the step we help with in prototype to production.
One thing to do this week
Open your app's frontend bundle in the browser's developer tools and search it for your model provider's key prefix. Then list every credential your agent can use, and next to each one, write where it lives.
If any of them lives where a user can read it, move it today.