“local-adjacent.”

Ran werbos from the command line for the first time in a month. “hey buddy” — sixty-seven seconds. Then the same message into werbos.chat in the browser — fifty-six. Same five words. Same model name on both ends. So why the gap?

Because they weren’t the same model. The CLI defaulted its --host to http://localhost:11434 — a cold Ollama on the laptop, no keepwarm, paying the whole disk-into-memory load on the first turn. The web goes through werbos serve on the homelab, where the model has been resident and tuned since Day 31’s latency hunt. Two instances. One warm, one cold. No amount of CLI-side tuning closes that the right way, because the right answer isn’t to tune the cold one — it’s to stop using it.

So the fix is small: --host now resolves through _config.default_host(), which reads $WERBOS_HOST before falling back to localhost. Export it once at the homelab and the CLI becomes a thin client to the same brain the browser already uses. An explicit --host still wins, so going offline is one flag away (e15965b).

export WERBOS_HOST=http://100.83.4.49:11434

And then the part worth keeping. Mid-fix, the observation: “since the model is on my homelab… technically my local-first is… local-adjacent.” Which sounds like a hedge and is actually the opposite. Local-first was never about the laptop. The unit of trust is the boundary you control — your hardware, your weights, data that never goes through someone else’s inference API. The homelab is as local as the machine in front of you. The laptop and the homelab are two windows onto one mind you own. That’s the same point ARENA already makes (“the brain runs on the player’s box”) and the work-brain wedge already sells (“companies trust a box that never phones out”). Trust lives at the boundary, not the chassis.


Then, while the suite was already warm, a second pass — the unglamorous kind. Fifteen modules had each open-coded the same three-line ladder for finding a file: check my own env var, else $WERBOS_DATA_DIR/thing, else ~/werbos/thing. Fifteen byte-for-byte copies of one policy, which means fifteen places to edit the day you want to add (say) XDG support. Collapsed them into one module, werbos/paths.py, with two helpers: resolve_data_path for the relocatable tree and resolve_home_path for the handful of DBs that predate WERBOS_DATA_DIR and must keep ignoring it. Seventeen call sites across twelve files now point at one ladder; the chat.py _resolve_*_db_path cluster shrank to five one-liners (eecdb64). Behavior-preserving to the byte — every leaf name and precedence rung identical to what it replaced, the 5,924-test suite green as the net.

Worth saying plainly: the discipline that makes the next refactor cheap is the one that looks like it shipped nothing today. Then we went looking for the God functions — server/sessions.py::create at 447 lines, build_app at 1,018 — and named the seams. That work is for a later day; today it’s enough to know where the knives go.

Not much shipped. But the CLI matches the web now, the path policy lives in one place, and the thesis got a little more honest about where it actually lives.