Day 52 — 'local-adjacent.'
A small one, and an honest one. Ran the CLI for the first time in a month — 'hey buddy' took 67 seconds. The web took 56. Same words, same model name, but not the same model: the CLI was talking to a cold Ollama on the laptop while the web talked to the warm daemon on the homelab. The fix is one environment variable. The reframe it forced is the keeper — 'local-first' was never about the laptop. It's about the boundary you control. The homelab is as local as the machine in front of you.
“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.