Field Report // NO. 005
Three things I'd do differently if I started this build today
Three day-one actions that prevent recurring friction: make the box rejoin the tailnet after every restart, name the workload before picking a model, and wire the calendar while you are already in setup mode.
Three things I’d do differently
You’ve built the thing. Hetzner box, Tailscale, SSH bound to the tailnet, Telegram bot wired, Hermes installed, a persona that sounds like a person. It works.
Then a power blip at 3am and the box comes back silent. That was the moment I realised standing the thing up and running the thing are different skills.
Here are the three changes I’d make if I stood it up today. Each is a concrete action you can take on day one, while you are already in setup mode.
Lesson 1: Make the box rejoin the tailnet on its own
My first instinct was to blame the auth key. I thought the one-time key from the initial tailscale up stopped working after a reboot. It doesn’t. Tailscale stores the node’s credentials on the box, in /var/lib/tailscale/tailscaled.state, and they survive restarts. A registered node does not forget itself.
The two real ways a box stops reconnecting:
The daemon never starts at boot. tailscaled, the process that holds the tailnet connection, is a systemd service, and systemd is what starts services at boot on Ubuntu. If the service is not enabled, a reboot leaves you with no connection and no process trying to make one.
The node key expires. The node key is the credential that identifies the box to the tailnet. By default it expires after 180 days. When it expires, the box stops connecting until someone re-authenticates it. On a box you only reach over the tailnet, that someone is you, at the Hetzner console.
Both are fixed in two commands and one click in the Tailscale admin console:
sudo systemctl enable --now tailscaled
That starts the daemon now and tells systemd to start it on every boot. Then open the admin console, find the box, and choose Disable Key Expiry. The node key never expires again.
If you also want unattended re-provisioning for a fresh box or a container, generate a reusable auth key (console → Settings → Keys → Generate auth key → Reusable) and drop a one-shot unit next to the daemon:
# /etc/systemd/system/tailscale-auth.service
[Unit]
Description=Register the box with a reusable auth key
After=network-online.target tailscaled.service
Wants=network-online.target
[Service]
Type=oneshot
ExecStart=/usr/bin/tailscale up --authkey=tskey-xxxxxxxxxxxxxxxx --hostname=hermes-box
RemainAfterExit=yes
[Install]
WantedBy=multi-user.target
sudo systemctl enable --now tailscale-auth.service
Article 2 walks the first tailscale up; with the daemon enabled and key expiry off, the box reconnects on its own after every restart from then on. This is the difference between a box you manage and a box that manages itself.
Lesson 2: Decide on the model strategy before the first token is spent
I treated the model choice as a tuning step, something to refine after seeing how the agent behaved. That meant I spent real money on heavier models before I understood what I was actually asking the agent to do. The back-and-forth chat models, the ones tuned for conversational turns, are expensive at agent volumes. My workload is not a chat workload.
The action is to write down what the agent will actually do, in one paragraph, before you pick a model:
This agent will spend most of its time doing ________. Its typical request is ________. It mostly needs to be ________ (fast / careful / creative). It should never be asked to ________. The single worst failure it could have is ________.
If it answers questions and summarises, a lighter chat-tuned model is fine. If it runs tools in sequence, calls subagents, holds state across long sessions, and produces structured output, that is a different workload. Name the workload, then shop for it. Cost follows from fit, not the other way around. The model comparison itself lives in article 3; this is the step you do not skip before reading it.
Lesson 3: Wire the calendar on day one instead of three months later
The most useful thing this agent does, the one I reach for multiple times a day, is answer “what’s on my calendar today.” I knew it would be. I kept telling myself I’d add the calendar integration later. Later was three months.
The reason it slipped is not the credentials. It is the one interactive browser step: OAuth, the login handshake that gives an app access to your Google account. Done on day one, while you are already heads-down in setup, it takes five minutes. Done later, it is a project you keep not starting.
The calendar comes from Google in this build. Create a Google Cloud OAuth client (project → enable the Calendar API → Credentials → Create OAuth client ID → Desktop app), download the JSON, and save it in the profile home from article 3:
mv ~/Downloads/client_secret_*.json ~/.hermes/profiles/personal/google_client_secret.json
Then authorize once with the setup script that ships in the profile’s google-workspace skill:
python ~/.hermes/profiles/personal/skills/productivity/google-workspace/scripts/setup.py \
--client-secret ~/.hermes/profiles/personal/google_client_secret.json
python ~/.hermes/profiles/personal/skills/productivity/google-workspace/scripts/setup.py \
--auth-url
Open the URL it prints, approve in the browser, and paste the redirect URL back with --auth-code:
python ~/.hermes/profiles/personal/skills/productivity/google-workspace/scripts/setup.py \
--auth-code "http://localhost:1/?code=..."
Finish with a check:
python ~/.hermes/profiles/personal/skills/productivity/google-workspace/scripts/setup.py --check
It prints AUTHENTICATED, and google_token.json now sits in the profile home and auto-refreshes. The token is there the day the calendar feature is ready. No retrofitting, no “I should have.”
If your calendar lives in Microsoft instead, Hermes reads it through the Teams pipeline. Register a Graph app and the credentials go in the profile’s .env as placeholder values on day one:
MSGRAPH_TENANT_ID=00000000-0000-0000-0000-000000000000
MSGRAPH_CLIENT_ID=00000000-0000-0000-0000-000000000000
MSGRAPH_CLIENT_SECRET=your-client-secret-value
The calendar question is the one I ask most often. Everything else follows from having it working.
What this build became
A chat surface that checks my calendar in under two seconds from anywhere. A background agent that texts me at 7:30 if anything is before 9am. Not a demo. A piece of infrastructure I stop noticing and start trusting.
The takeaway
All three lessons are the same move: do the annoying one-time thing while you are already in setup mode. Enable the daemon, disable key expiry, name the workload, run the OAuth handshake. Each took five minutes on day one and would have cost an hour of console time later.
If you are standing a build up this week, start with the calendar. It is the feature you will use most, and it is the cheapest to wire while the browser is already open. Up next in the series: consolidating the model config into one file you can reason about.
Michael Short is the founder of The Agent Files.