Point his mind at a model you run yourself. Your hardware does the thinking, and nothing is metered here.
Thanks to olie-o from our Discord for the original guide. This page follows its steps.
This is one of two ways to bring your own brain, and they are different things:
Your phone reaches your computer over Tailscale, a private network between your own devices. Tailscale Serve gives the computer an https address that only your devices can open, and hands each request to the model server running on it.
phone, running GrowBot │ https, inside your tailnet ▼ https://your-pc.your-tailnet.ts.net │ tailscale serve ▼ http://127.0.0.1:PORT on your computer └─ llama.cpp, Ollama or LM Studio
The phone needs an https address because growbot.dev is https, and the browser refuses to call plain http from an https page. Serve gives you one with a real certificate, keeps the same address tomorrow, and carries the stream his spoken turns use.
/v1/chat/completions API works the same way, vLLM included.Get the server answering on the computer first. Every later step depends on this one.
llama-server -m your-model.gguf --port 8080 --reasoning off
This line sets port 8080. Recent llama.cpp builds use 9931 when you leave --port out (older ones used 8080), so keep it in, or use the port llama-server prints when it starts. --reasoning off is step 2, done ahead of time.
Port 11434. The Ollama app starts the server for you. ollama list shows the model names you will type into GrowBot later. Ollama also needs step 3.
Port 1234. Load a model, then start the server from the Developer tab with Enable CORS switched on, or from a terminal:
lms server start --cors
Open this in the computer's own browser, with your port:
http://127.0.0.1:PORT/v1/models
You should see a short list of your models. If you do not, fix that before going on: nothing later can work around it.
Thinking models (qwen3, deepseek-r1 and others) write a hidden draft before they answer. GrowBot needs the answer straight away. The connection test allows only a few words, a thinking model spends all of them on the draft, the test fails, and he stays on our brain. olie-o found this one.
The thinking off box in GrowBot (ticked for a new setup, so tick it yourself if you saved a server before it existed) asks your server to skip the draft. It sends two fields, reasoning_effort and chat_template_kwargs. Current llama.cpp, Ollama and vLLM accept both, going by their source code. vLLM 0.10.1 to 0.17 and Ollama 0.11.5 to 0.12.3 refuse the first one. The test then asks once more with chat_template_kwargs alone, and saves the form that worked. We have not checked every version on real hardware, so if the test still says the model spent the whole answer thinking, turn it off on the server too:
--reasoning off. --reasoning-budget 0 does the same job.Skip this for llama.cpp and LM Studio.
Out of the box, Ollama listens only on 127.0.0.1 and refuses any request addressed to a name it does not recognise. Tailscale Serve passes your tailscale name straight through, so Ollama answers every request with a blank 403. In the phone's browser that shows up as HTTP ERROR 403. OLLAMA_ORIGINS does not fix that one. OLLAMA_HOST does.
Two settings, then restart Ollama:
OLLAMA_HOST=0.0.0.0:11434 makes Ollama listen beyond 127.0.0.1, and that also switches the name check off. The Ollama app's own setting Expose Ollama to the network does the same thing. Anyone on your home network can then reach Ollama, so only do this on a network you trust.OLLAMA_ORIGINS=https://growbot.dev lets this page call it. Ollama allows cross-origin requests from 127.0.0.1 and 0.0.0.0 by default, and anything else has to be listed in OLLAMA_ORIGINS as a comma separated list of origins, scheme included. Type it exactly as shown: a value without the https://, or with a space after a comma, stops Ollama from starting at all.Quit Ollama from the tray, run these in a terminal, then start Ollama again from the Start menu. setx only reaches programs started after it.
setx OLLAMA_HOST 0.0.0.0:11434 setx OLLAMA_ORIGINS https://growbot.dev
Or set the same two in Settings, under "Edit environment variables for your account".
Ollama's docs say to set each variable with launchctl setenv and then restart the Ollama application:
launchctl setenv OLLAMA_HOST "0.0.0.0:11434"
launchctl setenv OLLAMA_ORIGINS "https://growbot.dev"
# then quit and reopen Ollama
We are not going to tell you that survives a reboot, because Ollama's documentation does not say so. If the door stops working after you restart the Mac, set it again. The Expose Ollama to the network setting is stored by the app itself.
sudo systemctl edit ollama.service # add these two lines under [Service]: Environment="OLLAMA_HOST=0.0.0.0:11434" Environment="OLLAMA_ORIGINS=https://growbot.dev" # save, then: sudo systemctl daemon-reload sudo systemctl restart ollama
That form only applies if you start the server yourself. Quit the Ollama app first.
OLLAMA_HOST=0.0.0.0:11434 OLLAMA_ORIGINS=https://growbot.dev ollama serve
Ollama has no password. Listening on 0.0.0.0 means anything on your home wifi can use it at http://YOUR-PC-IP:11434. On a home network you trust, that is usually fine. On shared wifi, at a café or in a dorm, it is not.
A tighter option is to listen on the computer's tailscale address only (OLLAMA_HOST=100.x.y.z:11434, the address tailscale ip -4 prints) and serve that address in step 4 with the line below. We have not tested that route, and Tailscale's Mac apps (the App Store and standalone builds) refuse a target that is not localhost.
tailscale serve --bg http://100.x.y.z:11434
tailscale status should list both devices.tailscale serve --bg 8080 # llama.cpp tailscale serve --bg 11434 # Ollama tailscale serve --bg 1234 # LM StudioA bare port means
http://127.0.0.1 on that port. --bg keeps it running after you close the terminal, and Tailscale brings it back by itself after a restart. The first time, Tailscale may print a link to turn on HTTPS for your tailnet: open it, approve, and run the line again.tailscale serve statusIt should show your https address marked (tailnet only), with a proxy line to
127.0.0.1 and your port. That address, https://YOUR-PC.YOUR-TAILNET.ts.net, is what the phone and GrowBot use.To undo it: tailscale serve reset. On Linux, run these with sudo, or run sudo tailscale set --operator=$USER once. With the Mac App Store build, the command line tool is /Applications/Tailscale.app/Contents/MacOS/Tailscale. Older guides, including an earlier version of this page, show a serve https / form. Current Tailscale refuses it and prints the new form instead.
Use Serve, not Funnel. Serve is private to your tailnet. Funnel puts the address on the public internet, and a model server has no password of its own, so anyone who finds the address can use your graphics card.
Turn Tailscale on in the phone's app, so it says Connected. Then open this in the phone's browser:
https://YOUR-PC.YOUR-TAILNET.ts.net/v1/models
You should see the same short list of models as in step 1. That is the Tailscale part done.
/v1/models, shows the llama.cpp chat page for llama.cpp, and the words Ollama is running for Ollama.In the app: Brain → Settings → your own key → open the key panel. Scroll past the OpenRouter key to 🖥 DIY: YOUR OWN SERVER.
https://YOUR-PC.YOUR-TAILNET.ts.net/v1. It must end in /v1. GrowBot adds /chat/completions and nothing else, so without /v1 Ollama and LM Studio answer HTTP 404. llama.cpp works either way./v1/models. For Ollama that is the name ollama list shows, like qwen3:8b.Authorization: Bearer.Do not give GrowBot http://127.0.0.1:8080 or http://localhost:1234. On the phone, those addresses mean the phone itself.
"my model can see" is OFF until you tick it. Leave it off and he keeps talking normally while every camera frame is thrown away before it reaches your model. He does not go quiet. He guesses, and he sounds confident doing it.
He will still report light and dark correctly, because brightness reaches him as a number rather than a picture. So "he can tell the lamp is brighter but never sees the room" is the signature of this exact setting.
Tick it only if your model genuinely accepts images. If it does not, he is honestly better off telling you he cannot see.
The test quotes what went wrong. Find the words here.
| What you see | What it means, and the fix |
|---|---|
| HTTP ERROR 403 in the phone's browser, or the test says your server answered HTTP 403 with an empty message | Ollama is refusing the tailscale name. Do step 3: OLLAMA_HOST (or Expose Ollama to the network), then restart Ollama. OLLAMA_ORIGINS alone does not fix this. |
| The models list opens on the phone, but the test says "the browser would not tell us why, so this one is unresolved" | Most often your server is not allowing this page (CORS). When a browser blocks a cross origin request it hides the reason from the page, so a blocked origin, a closed tunnel and a wrong address look the same from here. Ollama: OLLAMA_ORIGINS=https://growbot.dev, then restart it. LM Studio: switch on Enable CORS. llama.cpp allows any page by default. Started with --tools, --agent or MCP servers, it allows only localhost pages, because those flags put file and shell tools on its API. Start a plain llama-server for GrowBot instead of opening that one up. |
| "the model spent the whole answer thinking and never replied" (older versions of the app said "your server accepted the request and then sent nothing back") | A thinking model. Do step 2. |
| HTTP 400 that mentions reasoning, chat_template_kwargs or think value, and "untick thinking off and test again" | Your server does not accept the thinking off request. Untick thinking off, test again, and turn thinking off on the server instead (step 2). Ollama's version says "invalid think value". That Ollama is older than 0.12.4, and updating it fixes this. |
| HTTP 404 | The address is missing /v1 at the end, or Serve points at the wrong port. Check tailscale serve status. |
| HTTP ERROR 502 in the phone's browser | Serve is running, but nothing answers on the port it points at. Start your server (step 1) and check that tailscale serve status shows the same port. In the test this one shows up as unresolved. |
| HTTP 401 | Your server or your proxy wants an api key. Put it in the server api key box. |
| The phone says the site can't be reached, or the test runs out of time | The phone is not on your tailnet, the computer is asleep, or Serve is not running. Check that Tailscale says Connected on both devices, and run tailscale serve status on the computer. |
| "streaming: failed" and then "buffered: worked" | Your server and your model answer, and something between them and the phone is not carrying the stream. A Cloudflare quick tunnel is one known cause, and a buffering proxy is another (see the proxy section). |
| It worked once and never came back | A quick tunnel hands out a new random address every time it starts, so the saved one goes stale the moment you restart it. Use Tailscale Serve (step 4). |
| He talks but never sees | The my model can see box, or a model without eyes. |
| Everything is slow | Local rigs think slower than our proxy, and he waits longer before giving up. That is expected. The test itself waits twenty seconds, and a smaller model answers sooner. |
If you already run a web server with a real certificate, it can stand in for Tailscale. Tahuna from our Discord runs Ollama this way, through Apache on an internet facing box that forwards to the computer with the graphics card (his config is below). GrowBot only needs an https address that reaches /v1/chat/completions and passes the stream through unbuffered.
OLLAMA_HOST=0.0.0.0:11434, or its LAN address). That also turns its name check off. It still needs OLLAMA_ORIGINS=https://growbot.dev.Host: localhost:11434. Ollama's own nginx example does exactly this. It also needs OLLAMA_ORIGINS=https://growbot.dev.proxy_buffering off and a longer proxy_read_timeout (the default is 60 seconds between reads). Caddy flushes event streams by itself. Apache's mod_proxy_http already flushes as the stream arrives, so it needs no flush settings (flushpackets only applies to AJP and FastCGI), and ProxyPreserveHost is off by default, which is what a loopback Ollama wants./v1/ only, so the parts of Ollama's API that pull, push and delete models stay unreachable.openssl rand -hex 32). Paste the token alone into GrowBot's server api key box (step 6). GrowBot adds the word Bearer itself and sends it as Authorization: Bearer followed by the token.OPTIONS requests through without the token. That is the browser's preflight, and it never carries one.Access-Control-Allow-Origin header at the proxy when the server already sends one.An nginx sketch for Ollama on the same machine. We have not run this exact config, so treat it as a starting point:
location /v1/ {
set $gb_ok 0;
if ($request_method = OPTIONS) { set $gb_ok 1; }
if ($http_authorization = "Bearer YOUR-LONG-RANDOM-TOKEN") { set $gb_ok 1; }
if ($gb_ok = 0) { return 401; }
proxy_pass http://127.0.0.1:11434;
proxy_set_header Host localhost:11434;
proxy_buffering off;
proxy_read_timeout 300s;
}
Apache, built on Tahuna's config: Ollama on another computer on the home network, the https box in front. This version does what the card says. It forwards /v1/ only, refuses any request without the token, lets OPTIONS through, and still streams. We ran it on Apache 2.4.66 in front of a stand-in server, not a real Ollama. Put your Ollama computer's address in place of 192.168.1.24, and your token in place of YOUR-LONG-RANDOM-TOKEN. It needs mod_proxy, mod_proxy_http and mod_ssl.
<VirtualHost *:443>
ServerName YOUR-HOST
SSLEngine on
SSLCertificateFile /etc/letsencrypt/live/YOUR-HOST/fullchain.pem
SSLCertificateKeyFile /etc/letsencrypt/live/YOUR-HOST/privkey.pem
<Location "/ollama/v1/">
ProxyPass "http://192.168.1.24:11434/v1/"
ProxyPassReverse "http://192.168.1.24:11434/v1/"
<If "%{REQUEST_METHOD} != 'OPTIONS' && %{HTTP:Authorization} != 'Bearer YOUR-LONG-RANDOM-TOKEN'">
Require all denied
</If>
</Location>
</VirtualHost>
GrowBot's server address is then https://YOUR-HOST/ollama/v1, and the token goes in the server api key box. A request without the right token gets 403 from Apache.
A 401 or 403 from the proxy carries no CORS header, so a missing or wrong token shows up in the test as unresolved. To check the token itself, run this on any computer (for the nginx sketch, leave out /ollama):
curl -H "Authorization: Bearer YOUR-LONG-RANDOM-TOKEN" https://YOUR-HOST/ollama/v1/models
Tahuna's original, which he runs today, is below for reference. Do not copy it as it is: it forwards all of Ollama's API, including pull and delete, to the internet with no token. It works because his Ollama is on another computer, so it listens beyond loopback and its name check is off (the first bullet at the top of this section). With ProxyPreserveHost Off, Apache sends the ProxyPass address as the Host, so the RequestHeader set Host line changes nothing here. That line only takes effect with ProxyPreserveHost On.
# Tahuna's original: open to anyone, do not copy
<VirtualHost *:443>
ServerName YOUR-HOST
SSLCertificateFile /etc/letsencrypt/live/YOUR-HOST/fullchain.pem
SSLCertificateKeyFile /etc/letsencrypt/live/YOUR-HOST/privkey.pem
<Location "/ollama/">
ProxyPass "http://192.168.1.24:11434/"
ProxyPassReverse "http://192.168.1.24:11434/"
ProxyPreserveHost Off
RequestHeader set Host "localhost:11434"
</Location>
</VirtualHost>
His spoken turns stream. Cloudflare's own documentation says, of the free trycloudflare.com quick tunnels: "Quick Tunnels do not support Server-Sent Events (SSE)." A quick tunnel will happily pass an ordinary request, so a simple connection check can look perfect while every real turn dies.
The same page notes a quick tunnel gets a random subdomain each time you launch it. That is "it worked once and never came back": the first tunnel carried an ordinary check, it closed, and the new one had a different address.
So skip it. A quick tunnel pointed at Ollama would also put all of Ollama's API on a public address with no token.
A Cloudflare named tunnel, if you already own a domain. It keeps one hostname and it carries the stream. Cloudflare offers two forms: remotely managed, created in the dashboard and installed with a token, or locally managed from your own machine with a config file. The one below is locally managed. Either way, Cloudflare requires a domain added to your Cloudflare account before a tunnel can publish anything.
cloudflared tunnel login cloudflared tunnel create growbot cloudflared tunnel route dns growbot ollama.yourdomain.com cloudflared tunnel run growbot
Anyone who finds the hostname reaches whatever the tunnel points at, and a tunnel cannot check GrowBot's token by itself. So never point it straight at your model server. Point it at a proxy on the same computer that checks the token (the proxy section), and pass /v1/ only.
The routes go in ~/.cloudflared/config.yml:
ingress:
- hostname: ollama.yourdomain.com
path: ^/v1/
service: http://localhost:8081
- service: http_status:404
Port 8081 stands for your token checking proxy, for example the nginx sketch above listening on 127.0.0.1:8081. The proxy sends Ollama the Host it wants, so the tunnel needs no host header. Ollama still needs OLLAMA_ORIGINS from step 3. If that is more than you want to run, use Tailscale Serve.
ngrok also gives a free address that stays put, but its free plan meters requests and puts a warning page in front of browser traffic, and this page cannot add the header that skips it. If you already use ngrok, point it at a proxy that checks a token (like the ones above), never straight at your model server. If you do not, Tailscale Serve is less trouble.
Small models are fine for talking and poor at seeing. If you want him to see, you need a vision model, and a 7B class one will be slower than what he is used to. Vision costs more than people expect on local hardware.
If your model has no eyes, that is a real choice and not a failure. Tick nothing, and he will say plainly that he cannot see rather than inventing a room.
The public agent harness runs him from the command line with no browser in the way, so no tunnel and no CORS:
BASE_URL=http://localhost:11434/v1 MODEL=qwen3:8b node reference-loop.mjs
It boots a fresh creature from seed. It cannot yet load a soul file grown in the app.
Stuck? Discord or info@growbot.dev. Tell us the server, the model, whether the vision box is ticked, and the exact words the test showed.
Sources: Ollama FAQ (OLLAMA_HOST, allowed web origins, setting environment variables, the nginx and Cloudflare examples), Tailscale Serve and the tailscale serve command, llama.cpp server README (--reasoning, --reasoning-budget, --cors-origins), LM Studio server settings, Cloudflare, Quick Tunnels (no SSE, testing only, random subdomain), Cloudflare, create a locally-managed tunnel (the domain requirement), Cloudflare, origin parameters (httpHostHeader), nginx proxy module and Apache mod_proxy.