Delegating the Feed, Part 3: Twingate, Tailscale & Telegram - A Tale of Three T's

This post is part of a series on my attempts to delegate tasks to AI agents run on local architecture.


My self-hosted AI agent - Hermes running in Docker on a small mini PC I originally acquired to work as a Roon music server - talks to Qwen (3.6) running on my MacBook Pro. Being a laptop its IP kept changing and therefore breaking the connection. I set up Tailscale to give the Mac a stable address — and then spent an hour debugging a connection that still wouldn’t work, chasing three plausible-looking causes that were all wrong, before finding the real one-line bug.

The setup

  • Hermes running on Docker on the mini PC;
  • Hermes talks to Qwen (using LM Studio) on the MacBook Pro;
  • Telegram is the interface;
  • Hermes forwards the requests to Qwen;

It’s cheap. It’s free.

It works great as long as the Mac’s IP address doesn’t change.

It’s a laptop. It changes.

The symptom

Telegram messages stopped getting replies. First instinct: check the Telegram connection.

That turned out to be a red herring — Telegram was fine, connected and receiving messages the whole time. The real failure was one hop further down the chain: every call from Hermes to LM Studio was timing out.

Because I was taking my MacBook to the office, to the cafe, etc., the IP kept changing. This is normal behavior but it meant the connection would keep breaking every time I took the Mac somewhere else.

The real fix (attempt one): Tailscale

The obvious fix is a mesh VPN — Tailscale gives every device a stable private IP that works no matter what network it’s actually on. I added it as a sidecar container next to Hermes in Docker Compose, generated an auth key, installed the Tailscale app on the Mac, and pointed Hermes at the Mac’s new stable 100.x.y.z address.

It didn’t work. Connection timeouts, identical to before.

Chasing ghosts

What followed was a genuinely confusing hour, because every symptom looked exactly like a real, plausible cause:

Suspect #1: a second VPN. The Mac also runs Twingate (a zero-trust VPN client) for work. Both Tailscale and Twingate happen to use the same private IP address range (100.64.0.0/10, officially reserved for “carrier-grade NAT”) for their virtual networks. Two VPN clients fighting over the same address space is a real, documented conflict — a very reasonable theory. My first debugging step was to quit Twingate entirely.

Still broken.

Suspect #2: the Mac’s firewall. Maybe the OS was blocking the inbound connection. Checked System Settings — firewall was off.

Not it.

Suspect #3: Tailscale’s own client-side setting. Tailscale’s Mac app has a toggle called “Allow incoming connections” that can silently block peer traffic even with everything else configured correctly. Checked it — already enabled.

Not it.

At each step, the diagnostic signal was the same: tailscale ping between the two machines succeeded instantly, but any real connection (curl, raw TCP tests to multiple ports) timed out completely — even to ports that definitely had nothing listening, which should return an instant rejection, not silence. That mismatch was the actual clue, and it pointed somewhere none of the three suspects could explain.

The actual bug

tailscale status --json on the home-server side had the answer the whole time, one field down: "TUN": false.

Tailscale can run two different ways: normally, it creates a real virtual network interface at the operating-system level, and every program’s traffic flows through it transparently. But it can also run in a fallback “userspace” mode, where the Tailscale process itself can reach the network fine — which is exactly why tailscale ping kept succeeding — but nothing else on the machine gets routed through it at all. No interface exists for other programs to use.

The official Tailscale Docker container defaults to this fallback mode unless you explicitly tell it not to. I hadn’t. So Tailscale was healthy, connected, and completely useless to everything except itself.

The fix was one environment variable: TS_USERSPACE=false, forcing Tailscale to create a real network interface. The moment that was set, LM Studio was reachable in under 100 milliseconds.

Lessons

  • When a fix doesn’t work, verify the tool is actually doing what you assume, not just that it’s “running.” A healthy-looking status line (tailscale ping succeeding) can mask a completely different code path underneath.
  • A symptom that looks identical across several plausible causes is a sign you’re testing the wrong layer. Every one of my three suspects (VPN conflict, OS firewall, app-level toggle) would produce the same “connection times out” symptom — so ruling them out one by one wasn’t actually narrowing anything down until I found a different kind of signal (the closed-port timeout instead of an instant rejection).
  • Turns out both VPNs coexist fine. Once the real bug was fixed, I turned Twingate back on and tested again — no conflict at all. The CGNAT overlap theory was reasonable on paper but never actually the problem.
  • Laptops moving between networks is a solved problem — solve it once, properly. Chasing “what’s the Mac’s IP today” is not a sustainable troubleshooting loop. A stable mesh-VPN address, set up correctly, made the whole class of problem disappear for good.