All posts
macOS

Thuki v0.16: Built-In Web Search for Local AI on macOS

Thuki v0.16 builds web search into the app. No Docker, no API key, no account. Your Mac decides whether a question needs the web before anything leaves it.

Written by

Logan Nguyen

Last updated

Last release, Thuki stopped needing Ollama. This release, it stops needing Docker.

Until v0.16, /search was real but it was homework. The pipeline lived outside the app, so using it meant cloning the repo, running two local Docker services, and keeping them alive before you could ask a single current-events question. Most people, reasonably, never did.

v0.16 builds search into the app. No compose file, no service to babysit, no API key, no account. You type the question. That is the entire procedure.

Thuki (thư kí, Vietnamese for secretary) is a floating AI secretary for macOS. Double-tap Control to summon it over any app, including fullscreen ones, ask, and dismiss. It runs your model on your own Mac with its own built-in engine. As of v0.16, when a question needs something the model cannot know, Thuki goes and gets it.


The promise, kept

The v0.15 launch post ended with a roadmap. One line on it read: built-in /search with no Docker to run yourself. This post is that line coming off the list.

Here is the honest before and after, because it is the whole story.

Before (v0.15 and earlier) Now (v0.16)
To search Clone the repo, run docker-compose up for SearXNG and a Python reader, keep both alive Type your question
Where the pipeline lives Two containers next to the app Inside the app, on the engine you already installed
API key None, but a Docker install was the price None, and nothing to install
Who decides to search You, by typing /search Your Mac, on your prompt, with /search still forcing it

The old sandbox/search-box/ directory is deleted. It shipped as recently as v0.15.9 and it is gone now, along with the last reason to open Terminal to use a feature of a desktop app.

There is a pattern here worth naming. v0.15 killed the setup wall in front of the model. v0.16 kills the setup wall in front of the web. Neither is the point of Thuki. The point of Thuki is that it appears over whatever you are already doing and then leaves. Every release since has been about removing the things standing between you and that.


What makes this different: the decision happens on your Mac

Auto search is on by default in v0.16. That sentence makes people flinch, and it should, so here is exactly what stands behind it.

Before any query leaves your machine, two local checks decide whether the web is needed at all. The first is plain code: no model, no network, just a rule pass over your message. "hi" is not a search. "weather in Tokyo" is. The second, for anything genuinely ambiguous, is one short call to the same local model you already downloaded, constrained by a JSON schema so a small model has to answer in a fixed form instead of rambling. It returns a decision, a route, and a rewritten standalone question.

The routing decision is made on your Mac, before the network is touched. That is the load-bearing claim of this release. Auto search is not a decision to send everything to the internet and let a server sort it out; it is a decision made by your own hardware about whether your own question needs fresh facts. If the answer is no, nothing leaves.

When the answer is yes, the query goes from your Mac straight to the provider. Thuki does not proxy search through Quiet Node or any Thuki backend. There is no Thuki server in the path, because there is no Thuki server.

And the switch is right there: Settings, Behavior, Auto search. Turn it off and plain questions stay local, with /search still forcing a look-up whenever you want one.


What Thuki can now answer

Live questions, in a floating overlay

The overlay was always good at things your model already knew. Now it handles the other half. Ask what happened this morning, what something costs, what the weather is, who won, and the answer comes back with numbered citations and a Sources footer, in the same panel that floats over whatever app you were already in.

Purpose-built routes, not one generic scrape

Not every question is a web search. Weather goes to Open-Meteo, headlines to Google News RSS, definitions and stable facts to Wikipedia, live scores to ESPN's public scoreboard. General questions go to the search engines. Routing to the right source beats throwing every question at the same one.

Two independent engines, fused

For general web questions, DuckDuckGo's HTML endpoint is the primary source and Mojeek is a second, independently ranked one. Both are keyless and both serve parseable HTML. Two rankings that agree are a better signal than one ranking on its own, so Thuki merges the hit lists rather than trusting either alone.

Citations that get audited

The writer streams an answer with markers like [1] and [2], with the retrieved sources fenced as untrusted data so a hostile page cannot issue instructions. Then a separate pass scores those citations against the source text, and can repair them, strip them, or attach an honesty note. A model that says something the sources do not support is the failure mode that matters most here, so it gets its own stage.

Honest when the web comes back empty

If search was needed and nothing usable came back, Thuki says so. It does not quietly fall back to sounding confident from training memory. That is the same reason the rule pass exists: when freshness is obvious, the small model does not get the option to invent a current fact.

Nothing written to disk

Search keeps two caches so a follow-up does not re-scrape the same pages: fetched page text for the current conversation, and a short-lived scrape cache. Both live in process memory only. Quitting the app wipes them. Your queries and the page text they pulled never touch your disk.


How It Works Under the Hood

The pipeline is a fixed set of stages, run in order, entirely in-process on the built-in provider. Ollama and other providers do not run it. Here is the path of one turn.

Decide. websearch/prefilter.rs runs a pure code pass over your message and returns ForceWeb, ForceNo, or Ambiguous. Ambiguous hands off to websearch/prepass.rs, the grammar-constrained classifier: one call to the warm llama-server with a persona-free system prompt and a response_format JSON schema, so the model must emit a valid form rather than prose. It returns no, cached, or web, plus a route and rewritten queries.

Resolve language. One language code is resolved once from your original message, not from the rewritten queries, and threaded into every outbound request.

Retrieve. A cached verdict is a hint, not a shortcut: stored pages get re-ranked against the new question, and the turn escalates to a real search if they cannot ground an answer. A web verdict tries the intent vertical first, and a sufficiency judge can escalate to the engine tier. Engines race, hit lists are fused, top pages are fetched.

Ground. Fetched text gets chunked, ranked, recency-checked, filtered for actual evidence (a stale archive URL or a numberless marketing page is dropped from a "today" question), and assembled into numbered sources under fixed budgets: bounded pages, bounded tokens, bounded repair rounds, at most one requery.

Write and audit. The writer streams the cited answer. websearch/cite_check.rs then scores the citations and repairs, strips, or annotates.

Outbound security. Every request leaves through the SSRF-safe transport in src-tauri/src/net/. Search fetches attacker-influenced URLs by definition: search-result links, redirect chains, model-rewritten queries. Without a guard, a crafted URL could point Thuki at a loopback service, a cloud metadata endpoint like 169.254.169.254, or a private LAN host. net/ssrf.rs classifies every target with two independent checks before net/transport.rs will connect.

The User-Agent wart, said out loud

Thuki identifies itself honestly on the API verticals: Open-Meteo, Google News RSS, and the ESPN scoreboard all get Thuki/<version> (+https://thuki.app), and Wikipedia gets a Wikimedia-compliant descriptive one.

The two search engines do not. DuckDuckGo's /html endpoint blocks an honest bot User-Agent immediately, so DuckDuckGo and Mojeek receive a normal browser User-Agent instead. That is a deliberate tradeoff and it is a real one: browser-like UA for reachability, paired with a cooldown so a blocked engine does not get hammered in a retry loop. It is documented in the repo rather than buried, and you deserve to read it from me rather than find it yourself.

The rest of the limits

Since we are here, the whole list, from the design doc's own Limits and risks table.

  • Public HTML engines can block or rate-limit. Cooldowns, caches, and quota discipline reduce the pressure, but reachability is not a contract.
  • The ESPN scoreboard is an unofficial frontend API. It can break without notice; Thuki falls through softly when it does.
  • Only verified languages get language-specific channel shapes. Others fall back to English.
  • The classifier, the judge, and the writer are small local models and they can err. ForceWeb, evidence filters, and the citation audit mitigate that. They do not eliminate it.
  • The pipeline runs on the built-in provider only.

No accuracy percentage appears anywhere in this post because none exists. There is an eval harness in the repo; it is manual, it is not a merge gate, and it has not been calibrated. When there are real numbers, they will show up here with the methodology attached.


Then why not just use LM Studio or Jan?

Both are good software, both bundle a llama.cpp engine, and both can reach the web. On the feature checklist this is not a landslide, and pretending otherwise would be the kind of claim this blog does not make.

The difference is not the feature. It is the distance to it.

Windowed local AI apps Thuki
To ask about something on screen Switch app, paste, read, switch back Double-tap Control, ask over the app you are in
Selected text Copy it, then paste it Summon with it highlighted; it arrives as a quote
After the answer Close or minimize the window Dismiss; it is gone
Web look-up A feature inside the window you switched to A feature inside the panel that came to you

Reading a paragraph in a PDF and wondering whether the figure is still current is a two-second question. In a windowed app it costs a context switch each way, and you stop asking. That is the actual competition: not LM Studio, but not bothering. Built-in search matters because it puts live facts inside a thing you already summon reflexively, which is the one place a two-second question survives.


Getting it

If you already run Thuki, update the app and search is there.

If you are new, installing is one line:

curl -fsSL https://thuki.app/install.sh | sh

That downloads the latest Thuki.dmg over HTTPS, verifies its RSA-4096 signature with the openssl already on your Mac, installs it to /Applications, and launches it.

Then open Thuki, pick a starter model when onboarding offers one, and ask. The model download is a real step and a real wait: files run roughly 2 to 12 GB and it is connection-bound, though you only do it once. Search itself needs nothing: no key, no account, no service. Install, pick a model, ask.

Thuki runs on macOS 13.4 (Ventura) or later, on Apple Silicon. The Installation Guide has the manual DMG path and the first-launch permission details.


Founder note

Hey, Logan here. Thanks for reading this far.

Search is the feature I was most nervous to ship, because "your private local assistant now talks to the internet" is a sentence that has to be earned rather than announced. I hope the design earns it: your Mac decides, your query goes straight to the provider, nothing is written to disk, and the switch is one click away. If you think I got the tradeoff wrong, I would genuinely rather hear it. Reach out on X, or leave your email and I will reach out personally.


Frequently Asked Questions

Does Thuki send my questions to the internet?

Only when a question needs live facts, and that decision is made on your Mac first. A local rule pass and a local classifier run before any network call, so ordinary chat never leaves. When a search does run, the query goes directly from your Mac to the provider. Thuki has no server in the path.

Can I turn auto-search off?

Yes. Settings, Behavior, Auto search. It is on by default. Turn it off and plain questions stay local, answered only by the model on your machine. The /search command still works when it is off, forcing a web look-up for that one message, so you keep the feature without the default.

Does Thuki's search need an API key?

No. Search is zero-setup: no API key, no account, no service to run, nothing to configure. Every source is keyless, meaning Thuki can call it without you pasting credentials. This is what changed in v0.16. The old pipeline needed two Docker containers you ran yourself; the new one needs nothing.

Is web search private?

Your question stays on your Mac unless the local decision says it needs the web. Then the query goes straight to the provider, never through a Thuki server, and caches live in memory only and are wiped on exit. Providers do see queries from your own IP address. Thuki does not anonymize or mask it.

Which search engines does Thuki use?

DuckDuckGo's HTML endpoint is the primary general-web source, with Mojeek as a second independent ranking, and the results are merged. Specialized questions route elsewhere: Open-Meteo for weather, Google News RSS for headlines, Wikipedia for stable facts, and ESPN's public scoreboard for live scores.

No, and you should not try. The sandbox/search-box/ directory with its docker-compose file, SearXNG, and Python reader is deleted from the repo. Search now runs inside the app on the built-in engine you already have. If you set the old containers up, you can stop and remove them.

Does search work with Ollama?

No. The search pipeline runs on the built-in provider only, because it depends on the local classifier and the structured-output support of the bundled llama-server. Ollama remains a fine optional provider for ordinary chat. If you switch to it, plain chat works and the web pipeline does not run.

There is no published number, and this post will not invent one. The repo has an evaluation harness, but it is manual, not a merge gate, and its thresholds are not calibrated against a real run. What exists instead are structural mitigations: evidence filters, a sufficiency judge, and a citation audit checking the answer against its sources.


The Short Version

Before v0.16, using /search meant cloning the repo and running two Docker containers. That directory is deleted. Search now runs inside the app, on the engine you already installed, with no key and no account.

Auto search is on by default, and the reason that is a feature rather than a concession is that the decision to search happens on your Mac. A local rule pass and a grammar-constrained local classifier decide whether your question needs the web before anything leaves. When it does, the query goes straight to the provider, never through a Thuki server, and caches never touch your disk. The off switch is in Settings, Behavior.

None of which is the point. The point is still that you double-tap Control and it is there, over whatever you are doing, and gone when you are done. Search just means one more class of question survives that reflex.

Next steps: