agent carries: standing restart:unless-stopped SOCKS proxy box for tower reach #291

Closed
opened 2026-06-25 21:45:32 +00:00 by coilysiren · 2 comments
Owner

Problem

Agent carries (ward agent containers on Docker Desktop) have no standing route to the tailnet-only Ollama tower (kai-tower-3026:11434). Today the only path is --ts-sidecar (ward#333/#337), which mints a per-run, ephemeral userspace tailscale sidecar that is torn down when the carry exits. Every run re-mints a key and re-joins a fresh sidecar's netns. A read-only explore carry launched without the flag has no tower reach at all.

Proposal

Stand up a persistent, restart: unless-stopped SOCKS5 proxy box that any carry routes through, instead of a sidecar minted per run. Docker's restart: unless-stopped (or always) is the "phoenix / launch-on-restart" mechanism: the box survives carry churn and host reboots as long as the docker daemon starts on boot.

Precedent already in the tree

infrastructure/ansible/roles/mac-proxy is already exactly this box:

  • files/compose.yaml: tailscale/tailscale:latest, TS_USERSPACE=true, TS_SOCKS5_SERVER=0.0.0.0:1055, restart: unless-stopped, hostname mac-proxy, tag:proxy.
  • Key injected from SSM /coilysiren/mac-proxy/ts-authkey at converge time, never written to argv/disk.
  • Ansible (tasks/main.yml) is the rollout, waits on the :1055 listener.

So the standing-box shape is proven for host-side tools (ssh/curl from the Mac). This issue is about making carries share it.

The wrinkle to solve

A carry reaches a tailscale sidecar by joining its netns (--network=container:<carry>-ts), not by host loopback. The mac-proxy publishes 127.0.0.1:1055 on the Mac host, but a carry's docker daemon lives in a LinuxKit VM, so host loopback is the exact gap --ts-sidecar was built to route around (ward#332/#333). A shared standing box for carries therefore needs either:

  • (a) carries join the standing box's netns (--network=container:mac-proxy), or
  • (b) a docker network shared between the standing box and each carry (carry dials the box by service name on that network),

not the host-port the mac-proxy exposes for host-side tools.

Asks

  1. Decide owner split: the standing-box wiring is likely ward (carry launch + netns/network join) plus infrastructure (the converged box + ACL), with agentic-os holding the convention/skill doc. Confirm or correct.
  2. Pick (a) shared-netns vs (b) shared docker-network for carry -> standing box reach, and capture the trade-offs (netns join is simplest but couples carry lifecycle to the box; a shared network is looser but needs the box on a known network + name).
  3. Validate the live path end-to-end (a carry curling the tower's Ollama through the standing box) - still only unit-tested per ward#337.

Context

Surfaced from a read-only explore session on coilyco-flight-deck/agent-proxy that needs tower reach and found none wired. Reference: ward docs/agent-ts-sidecar.md, docs/agent-host-net.md; tooling-tailscale skill (containerized-setup, sharp-edges).

## Problem Agent carries (`ward agent` containers on Docker Desktop) have no standing route to the tailnet-only Ollama tower (`kai-tower-3026:11434`). Today the only path is `--ts-sidecar` (ward#333/#337), which mints a **per-run, ephemeral** userspace tailscale sidecar that is torn down when the carry exits. Every run re-mints a key and re-joins a fresh sidecar's netns. A read-only explore carry launched without the flag has no tower reach at all. ## Proposal Stand up a **persistent, `restart: unless-stopped` SOCKS5 proxy box** that any carry routes through, instead of a sidecar minted per run. Docker's `restart: unless-stopped` (or `always`) is the "phoenix / launch-on-restart" mechanism: the box survives carry churn and host reboots as long as the docker daemon starts on boot. ## Precedent already in the tree `infrastructure/ansible/roles/mac-proxy` is **already exactly this box**: - `files/compose.yaml`: `tailscale/tailscale:latest`, `TS_USERSPACE=true`, `TS_SOCKS5_SERVER=0.0.0.0:1055`, `restart: unless-stopped`, hostname `mac-proxy`, `tag:proxy`. - Key injected from SSM `/coilysiren/mac-proxy/ts-authkey` at converge time, never written to argv/disk. - Ansible (`tasks/main.yml`) is the rollout, waits on the `:1055` listener. So the standing-box shape is proven for **host-side** tools (ssh/curl from the Mac). This issue is about making carries share it. ## The wrinkle to solve A carry reaches a tailscale sidecar by **joining its netns** (`--network=container:<carry>-ts`), not by host loopback. The mac-proxy publishes `127.0.0.1:1055` on the **Mac host**, but a carry's docker daemon lives in a LinuxKit VM, so host loopback is the exact gap `--ts-sidecar` was built to route around (ward#332/#333). A shared standing box for carries therefore needs either: - **(a)** carries join the standing box's netns (`--network=container:mac-proxy`), or - **(b)** a docker network shared between the standing box and each carry (carry dials the box by service name on that network), not the host-port the mac-proxy exposes for host-side tools. ## Asks 1. Decide owner split: the standing-box wiring is likely **ward** (carry launch + netns/network join) plus **infrastructure** (the converged box + ACL), with **agentic-os** holding the convention/skill doc. Confirm or correct. 2. Pick (a) shared-netns vs (b) shared docker-network for carry -> standing box reach, and capture the trade-offs (netns join is simplest but couples carry lifecycle to the box; a shared network is looser but needs the box on a known network + name). 3. Validate the live path end-to-end (a carry curling the tower's Ollama through the standing box) - still only unit-tested per ward#337. ## Context Surfaced from a read-only explore session on `coilyco-flight-deck/agent-proxy` that needs tower reach and found none wired. Reference: ward `docs/agent-ts-sidecar.md`, `docs/agent-host-net.md`; `tooling-tailscale` skill (containerized-setup, sharp-edges).
Author
Owner

Design locked - splitting into two implementation issues

Designed interactively in an explore session. Decisions:

  • Reuse the existing standing mac-proxy box (one box, two consumers: Mac-host tools via the published 127.0.0.1:1055, carries via a docker network). No second box, one stable tag:proxy tailnet node instead of per-run ephemeral churn.
  • Carry reaches the box over a shared user-defined docker network ward-tailnet, dialing socks5h://mac-proxy:1055. A shared network (not a shared netns) keeps carries isolated from each other and avoids port collisions. The default bridge does no name resolution, so a user-defined network is required - but it is not new infra, just a stable name on the network mac-proxy's compose stack already creates.
  • Convergence stays in ansible (authoring-vs-rollout doctrine). ward only attaches the carry to ward-tailnet and preflights, never stands the box up at launch.
  • The flag loses its --aws implication - with the key owned by the ansible box and the tower FQDN a constant, a tailnet carry has no SSM dependency.

Reach-surface note: carries on ward-tailnet share one bridge segment (ICC on, needed to reach the box), so they are not hermetically sealed from each other. Accepted as the cost of any shared bridge, and strictly better than the shared-netns alternative.

Implementation issues (dispatched headless)

Shared contract between them: network name ward-tailnet, proxy host mac-proxy:1055. They build independently. The feature is opt-in, so ward can land first - its preflight just errors until infra converges.

## Design locked - splitting into two implementation issues Designed interactively in an explore session. Decisions: - **Reuse the existing standing mac-proxy box** (one box, two consumers: Mac-host tools via the published `127.0.0.1:1055`, carries via a docker network). No second box, one stable `tag:proxy` tailnet node instead of per-run ephemeral churn. - **Carry reaches the box over a shared user-defined docker network `ward-tailnet`**, dialing `socks5h://mac-proxy:1055`. A shared network (not a shared netns) keeps carries isolated from each other and avoids port collisions. The default bridge does no name resolution, so a user-defined network is required - but it is not new infra, just a stable name on the network mac-proxy's compose stack already creates. - **Convergence stays in ansible** (authoring-vs-rollout doctrine). ward only attaches the carry to `ward-tailnet` and preflights, never stands the box up at launch. - **The flag loses its `--aws` implication** - with the key owned by the ansible box and the tower FQDN a constant, a tailnet carry has no SSM dependency. Reach-surface note: carries on `ward-tailnet` share one bridge segment (ICC on, needed to reach the box), so they are not hermetically sealed from each other. Accepted as the cost of any shared bridge, and strictly better than the shared-netns alternative. ## Implementation issues (dispatched headless) - **coilyco-flight-deck/infrastructure#402** - name the shared `ward-tailnet` network on the mac-proxy compose stack. - **coilyco-flight-deck/ward#349** - attach carries to the standing box over `ward-tailnet`, drop the per-run sidecar mint/teardown, add the preflight. Shared contract between them: network name `ward-tailnet`, proxy host `mac-proxy:1055`. They build independently. The feature is opt-in, so ward can land first - its preflight just errors until infra converges.
Member

Triage pass on 2026-07-09: the design work is no longer blocked in this AOS tracker. The decision was split into implementation issues:

Closing this parent so the remaining work is tracked where it will actually land.

Triage pass on 2026-07-09: the design work is no longer blocked in this AOS tracker. The decision was split into implementation issues: * coilyco-flight-deck/infrastructure#402 for the standing proxy box / rollout side. * coilyco-flight-deck/ward#349 for carry launch and shared proxy wiring. Closing this parent so the remaining work is tracked where it will actually land.
Sign in to join this conversation.
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
coilyco-flight-deck/agentic-os#291
No description provided.