Back to blog
FILE 0x99·NEVER BUILD NEW THINGS INSIDE THE ASSISTANT

Never build new things inside the assistant

May 20, 2026 · architecture, fastapi, homelab

I built a small water-tracker for myself: pick a bottle preset, tap a button, get a daily total. First version landed as a few /water/* routes inside the main assistant backend's main.py. That lasted about an hour before I tore it out.

What was happening

It felt natural at the time. The assistant already had auth, DynamoDB, and a deploy pipeline. Adding /water/log and /water/summary was four functions and a template. Done in an evening.

The problem showed up immediately: any restart of the assistant — for an unrelated chat-side change — restarted the water tracker. Any code lock the assistant had (the file is chattr +i'd to defend against an unrelated sync bug) was a lock the water tracker inherited. The blast radius of "I want to ship a one-line fix to the water UI" was every other surface the assistant served.

Worse, the assistant's main process was now responsible for an unrelated app's correctness. A bug in the water module could crash the chat surface. That's the wrong coupling.

What I found

The right architecture for personal projects on a homelab is boringly conventional: each app gets its own subdomain, its own systemd unit, its own port, its own nginx vhost. They share infrastructure (DynamoDB, the SSO provider, the certbot setup), not the same process.

The fix

Re-platformed in one evening:

DynamoDB table is shared, but with a pk=WATER#… namespace, so there's no collision with any other surface.

The systemd unit is the boring part:

[Unit]
Description=Water tracker
After=network-online.target

[Service]
Type=simple
User=chester
WorkingDirectory=/opt/water
EnvironmentFile=/opt/water/water.env
ExecStart=/opt/assistant/venv/bin/uvicorn main:app \
  --host 0.0.0.0 --port 8090
Restart=always
RestartSec=5s

[Install]
WantedBy=multi-user.target

Restart now only restarts the water app. The assistant has no opinion about it. The blast radius of a water-side bug is the water app.

What I'd do differently

This is now a standing rule for me: every new personal project gets its own subdomain + systemd unit + nginx vhost on day one, even if it's a five-route app. The marginal cost of standing up a new service is small once you've done it a few times. The cost of unwinding "I'll just add it to $existing_app" later is embarrassingly high — I'd already done the same thing once before with another small tool, and apparently needed to do it again to learn.

There's a corollary for things bigger than just systemd. If a new feature has its own users, its own state, or its own failure modes, it should have its own runtime boundary. Conway's Law also applies to "me, alone, at home" — the units of independent change want to be the units of independent deploy.