Can an agent that browses and calls tools run entirely on a local model?

Casey Holt

Casey Holt

October 7, 2026

Can an agent that browses and calls tools run entirely on a local model?

No. An agent that browses the web and calls tools cannot run the whole job on a local model and still finish the way a frontier model does. A local model can take a short tool call — open this file, hit this URL, write this note — if the tool list is tiny and the page is short. The moment the job is “look at a live page, decide the next tool, fill the arguments, and keep going until the original request is done,” the local model drops the request, invents a detail, or emits a tool call the runtime cannot parse. The browser and the tools can live on your machine. The model that has to steer them usually cannot, not for the full loop.

That answer is about the model, not about whether the weights sit on your disk. Plenty of local models chat well. This question is whether one of them can be the only brain in an agent that both fetches pages and acts.

What “entirely on a local model” has to include

People mix up three different setups and then argue about the wrong one.

The first is weights on your machine. Ollama, llama.cpp, MLX, or vLLM loads the model. Your prompt does not go to OpenAI, Anthropic, or Google. The browser, if you give the agent one, still fetches pages from the public web. That is the setup this question is about. “Local model” means the decisions are local. It does not mean the internet is off.

The second is an air gap. No network at all. Then browsing is impossible, because there is no page to read. A local model can still edit files and run commands on that computer. That is a different job, and it is an easier one. Do not use a successful offline file edit as evidence that the same model can drive a browser.

The third is a hybrid people quietly run and then call local. The small model drafts, a hosted model is called when the tool call looks hard, and the logs still say the agent is “on your hardware.” If a frontier model sees the tool step, the agent is not running entirely on a local model. It is running a router with a local chat window in front of it.

If you want a yes, you have to survive the first setup: one local model, a real browser or a real tool list, no second model waiting to catch the bad call.

The browser is software. The model is the part that fails

Playwright, a browser extension, or whatever tool wrapper you use is not a model. It clicks, scrolls, and returns text. The model decides which of those to do, with which arguments, after reading what came back. That split is why a fast local machine can still host a bad agent. The computer is fine. The next decision is not.

A tool call is a structured message, not a paragraph. The runtime expects a name from the list you gave it, plus arguments that match the schema. A chatty local model will often do one of three things instead. It writes the call as prose (“I would navigate to the docs”). It wraps a JSON blob in an explanation the parser throws away. Or it emits JSON that is almost right: the right tool, a missing required field, a string where the schema wanted a number.

Frontier models still do this. They do it less often, and they recover when the runtime sends the error back. Recovery is the part local models lose. The first bad call is a nuisance. The second turn, where the model has to read the error and try again without abandoning the original request, is where a 7B or 14B model starts narrating a success it did not earn.

A laptop on a desk with a terminal and a browser open side by side while someone pauses at the keyboard

The short job that actually stays local

I keep one local agent path because it works, and I do not pretend it is the other path.

The tool list is four names: read a file, list a directory, write a file, and search inside the project. No browser. The request is concrete: find the function that builds the invoice PDF and add a one-line comment naming the library it calls. The model is a mid-size instruct model with a tool-calling template the runtime already understands, not a general chat finetune I hoped would “figure out JSON.” Context stays small because I never paste the whole repo. The model calls read_file twice, then write_file once. I check the diff. Most days that works.

That is a local model calling tools. It is not an agent that browses. The moment I add a fifth tool, “open this URL and quote the install command,” the same model gets worse in a specific way. It navigates. The tool returns a long accessibility snapshot or a slab of HTML. The model summarizes from the first screen, misses the command that sat below the fold, and writes a flag the docs page never mentioned. The file on disk now contains a command that will fail. The model’s last message says the task is done.

Nothing about that failure required a cloud. The weights never left the box. The page was real. The quote was not.

Why the live page is the step that stops feeling local

A web page is a bad prompt. It is long, repetitive, and full of chrome: cookie banners, nav, related links, and a docs sidebar that mentions three versions of the same command. A hosted frontier model has the context length and the habit of tool use to skim that and ask for the next slice. A local model on hardware you actually own usually has a smaller useful window, and it has spent less training on “call the snapshot tool again instead of guessing.”

I watched this on a docs task that should have been boring. Tool list: navigate, snapshot, write a note. First call: navigate to the install page. Correct. Second call: snapshot. The tool returned a few thousand characters of page structure. The model’s next message was not another tool call. It was a finished note containing --gpu as if the page had required it. The page had an optional GPU section further down. The model had promoted the optional flag into the required command and stopped.

I sent the error back, once: the note does not match the page, look again. The model apologized and wrote the same command with the flag moved. It did not call snapshot a second time. That is the behavior that matters. A local model that browses will often treat the first tool result as the whole world, because asking again costs context it does not want to spend, or because its tool habit is one call and then a paragraph.

You can paper over some of this without a bigger model. Truncate the snapshot. Give the browser tool a “return the main text only” mode. Put the goal in a short system line that stays pinned. Those are harness fixes. They make a weak model less likely to drown. They do not turn it into a model that can keep a ten-step browse honest. If your success depends on the harness throwing away most of the page, say that. You are not running a browsing agent. You are running a scraper with a local model writing the last paragraph.

Tool choice is a separate failure from filling the arguments

On a two-tool list, choosing the tool is easy and filling the arguments is where it breaks. The model knows it should “write the file.” It puts the path in the contents field and the contents in the path field, or it invents a path one directory above the project. The runtime either errors or, worse, writes the file somewhere you did not mean.

On a fifteen-tool list, choice breaks first. Search, grep, read, browse, click, type, screenshot, shell — the names overlap. A small model picks shell because the request said “run,” then tries to curl a page the browser tool was supposed to open. Or it picks a tool that sounds right and was not in the list. Some runtimes reject unknown names. Some local setups are sloppy and let the model’s text fall through into a chat reply, so you think it browsed when it only described browsing.

I do not trust a local agent until I have read the raw tool trace for a job I already know the answer to. The trace should show the tool name, the arguments, and the tool’s reply, not the model’s summary of what it claims it did. If you only have the summary, you have a story. The story is the part local models are good at.

A small home server on a wooden shelf beside a notebook and an ethernet cable

What still leaves the machine when the model stays

Even a clean local setup leaks, and the leaks are not the weights.

The browser fetches URLs. Those requests come from your IP, with whatever cookies are in that profile. If the agent uses your daily browser profile, it is you, logged in, walking the site. A local model does not make that private. It makes the decision local and the session yours. For a research job I use a browser profile with no accounts. The model can still be wrong. It cannot spend my sessions.

Tool results go back into the prompt. If a tool returns an API key, a customer row, or the body of a private page, that text is now in the context the local model will see on the next turn, and often in a log on disk. “Local” does not mean “forgets.” It means the copy lives on your computer, which is better than a vendor log and still a copy you have to delete on purpose.

If any tool calls a hosted API — search, a second model, a captioner — write that down in the same sentence as “the model is local.” Otherwise you will describe the agent as local to someone who is asking whether their pages left the house. The pages might have. The model did not.

The setup I would actually run

I would run a local model as the only model for file tools on one repo, with a short schema, a pinned goal, and a human reading the diff. I would not run it as the only model for a browse-and-act loop that has to finish unattended.

If the job truly needs the web, I split it. A script or a narrow browser tool fetches the page and extracts the main text, with a character cap, before the model sees it. The local model then answers from that extract or writes a file from it. That is one tool result, not an agent wandering a site. It is less impressive. It finishes.

If the job is “keep clicking until the form is submitted,” I use a frontier model for that loop, or I do not automate it. The cost of a wrong submit is higher than the cost of the API call. A local model that is right on chat benchmarks and sloppy on the third tool call is the wrong place to save that money.

Hardware changes the size of the model you can load. It does not change the shape of the failure. A larger local model, given enough memory, chooses tools more reliably and holds a longer page. It still needs a trace you can read, a schema smaller than a product catalog, and a stop when the tool result does not support the sentence it wants to write. If you cannot afford that larger model, do not “solve” it by letting a small model browse with your cookies and a shell.

When the answer would flip

The answer flips if you shrink the job until it matches what local tool-calling already does. One model. A handful of tools with obvious names. Arguments you can check in one look. A page that has already been reduced to the paragraph that matters, or no page at all. A human on the last write. Under those constraints, yes: the agent can run on a local model, because you removed the part that was an agent wandering the web.

The answer stays no if you mean the demo people actually want. Give it a goal in a sentence, a browser, a pile of tools, and a long task. Leave the room. Come back to a finished result that matches the page and the original request. That loop still belongs to a frontier model, or to a local model you are willing to watch fail in the trace and correct by hand. Calling the second one “entirely local” is fair. Calling it done is not.

More articles for you