NetBird shipped Agent Network: autonomous agents join a WireGuard overlay and call a generated endpoint hostname with no API key, while the gateway attaches the peer's identity, enforces a model allowlist and budget caps, injects the provider key server-side, and writes an attributed access log. I wanted it as a Package Skill, a deterministic module an agent can converge from a colors.yml, with one opinion added: the agent container sits on an internal Docker network with no internet route at all, so the governed endpoint is the only path to anything. The isolation is the demo.
This article walks the six steps that turned that sentence into a repository, a live deployment, a catalog entry, and finally a second, smaller artifact that may matter more than the first.
1 · Clip the sources
The run started with reading material, not prompts. Three NetBird videos about Agent Network went through the Obsidian Web Clipper into the workspace as markdown files: transcript, title, source URL in the frontmatter. The agent added the other two tiers itself during reconnaissance: the full official docs tree, and shallow clones of the pinned upstream sources (the management server at v0.77.1, the dashboard at v2.91.1).
Three tiers, because they disagree. The ranking inverted during the build. The transcripts supplied intent, the docs supplied vocabulary, and the source code turned out to be the only authority: the prose docs were wrong about how the endpoint gets created and wrong about how tunnel DNS reaches it, and both times the build trusted the function it could read over the sentence it could quote. If your workflow cannot cite source files, it will faithfully implement the documentation's mistakes.
2 · Load two skills
Two skills compose, and neither knows about the other. claudex-loop governs how: reconnaissance before any question, an assumptions ledger, a structured interrogation, then an adversarial review loop in which a rival model attacks the locked plan until it approves or a round cap trips. The rival is Codex, a different vendor's model entirely, and after the build a fresh session of it inspects the diff. create-package-skill governs what: the phased workflow, boundaries, and conventions a Colors Package Skill and its deployment must satisfy. That means desired state in colors.yml, credentials only as environment variables, a build/dry-run boundary before any provider call, guarded deletion, and golden fixtures.
The division matters. Process skills transfer across domains; domain skills transfer across sessions. Neither has to be re-explained, which is the point of writing them down as skills at all.
3 · Describe it, and interrupt it
The interrogation is a conversation, and the most valuable message in it was a correction mid-sentence. My first description had two Vultr nodes. Halfway through explaining it I stopped: no. One node, Docker on it, and Docker networking makes sure the container running the agent cannot reach the internet, only the gateway on the same host. The workflow is built for exactly this: the plan was not yet locked, so the correction cost one sentence instead of a rebuild.
What got locked, one load-bearing question at a time: repository names; the payload (headless Claude Code inside the isolated container, a real workload on the governed path, not curl); the provider and models (Anthropic, two models claimed, only Haiku allowlisted, so both denial classes are demonstrable at zero cost); the acceptance gates; and DNS (a base name and a wildcard, because the endpoint hostname is a label the server mints at bootstrap and nothing knows it earlier). Then the authorization boundary, stated once and logged: I authorized repository creation, pushes, DNS records, paid compute, and real converges in advance, and deliberately did not authorize delete. Destruction stays a separate, per-invocation human decision.
4 · Provide the credentials, one deliberately fake
Credentials never enter the conversation or the repository; they live in a gitignored environment file the launcher overlays onto desired state at run time. The agent assembled the file itself from sibling deployments in the same workspace, and verified each credential against its provider rather than assuming. That caught two dead tokens left over from a revoked account before they could fail a converge halfway through.
One credential I chose to make fake: the Anthropic API key. That sounds like a compromise and is actually a mode. The keyless call from the isolated agent then produces the upstream's own 401, relayed back through the proxy, which proves every link NetBird owns: the isolation held, tunnel DNS resolved the endpoint, the policy authorized the peer, and server-side key injection reached Anthropic. Everything except the billable completion, verified for free. The two denial gates never reach upstream at all. Swapping in a real key later is a one-line edit and a re-converge; the same gates then require real completions.
5 · The loop builds it
The plan survives a rival first
Before any code, the locked plan went to Codex for adversarial review. Round one returned eighteen findings. Not style notes. Findings like: your extra_hosts mapping routes the metered path around the identity boundary; your denial probe can never produce the reason you expect, because routing rejects the model before the guardrail sees it; your setup key is an unbounded reusable credential; internal: true does not justify the phrase "physically cannot reach the internet" for a container holding NET_ADMIN. The loop accepted sixteen outright, two partially, and logged each disposition. Four rounds converged to VERDICT: APPROVED. The plan that entered the build bore little resemblance, in its load-bearing details, to the plan I had signed off on in conversation. That gap is the argument for the loop.
Eight converges against the real platform
Unit tests and golden fixtures catch regressions; they cannot catch a platform that behaves differently from its documentation. That takes converging against the real thing, and it took eight runs. The provider catalog id is anthropic_api, not anthropic. A 422 says so. Docker Compose refuses to exec into any service while an unrelated service's env file is missing, which breaks the token bootstrap until placeholder files exist. The proxy's embedded client hairpins trying to reach its own host's public hostname. Traefik dials a backend over an arbitrary shared network, which breaks PROXY-protocol trust pinned to one source address. Each fix landed in the package, so no future converge meets these again.
The third converge failed like this, for two of those reasons at once:
agent-network-smoke: gate 3: keyless call through willing-nightfall.agent-network...
agent-network-smoke: attempt 1: HTTP 000 (endpoint TLS or route may still be settling)
agent-network-smoke: attempt 2: HTTP 000 (endpoint TLS or route may still be settling)
...
agent-network-smoke: FAIL: the keyless call neither completed nor returned the upstream 401The expensive lesson was TLS. The pinned reverse proxy's per-name ACME is defective: every certificate order dies with no viable challenge type found against authorizations that demonstrably offer the challenge it speaks. Every failed order also burns Let's Encrypt's failed-authorization rate limit, which locks the hostname out of issuance in sliding one-hour windows. Retrying makes it worse. This one cost about two and a half hours. The fix deviated from the reviewed plan: endpoint TLS now comes from a wildcard certificate issued at converge time via DNS-01. The workflow's rule is that deviations get logged in the review log with their evidence, not silently absorbed.
The rival inspects the diff too
After the gates passed, a fresh Codex session inspected the diff: cold, without the plan-review context. Its best finding deserves quoting, because it corrected the meaning of a passing test:
The external probe accepted the relayed upstream 401 as proof the endpoint is tunnel-only. It is the opposite: a 401 from Anthropic means the gateway attached its server-side key and forwarded an unauthenticated outsider's request upstream. With a real key, that would have been billable. The correct external outcome is the proxy's bare pre-identity 403, which also writes no access-log entry.
Accepted, fixed, validated live: the probe now requires exactly that 403, fail-closed on everything else. Two inspection rounds, nine findings, seven accepted, two partially rejected with logged reasons. The final converge, on the final pinned SHA, exited zero with every gate passing: isolation probes with a success control, the tunnel, the keyless call, both denial classes, headless Claude Code on the governed path, per-peer attribution, limits read back against desired state, isolation re-proven after a Docker restart and a reboot, and the external probe. Then publication, which by comparison is clerical: two repositories pushed, the library pinned by SHA, the catalog recipe merged, the featured entry live.
6 · Distill the lessons
The last step is the one most workflows skip. Everything the build learned the hard way got distilled into a Context Skill, agent-network-single-node: the defective ACME and its rate-limit trap, the endpoint-minting API contract the docs get wrong, the hairpin, the Traefik network pinning, the credential crash windows, and the acceptance gates that would have lied. It holds a symptom-indexed failure catalogue, the API contract as the source defines it, the pinned version set with the rules that generated it, and the verification doctrine. Deliberately no file copies. The package repository stays the single tested implementation; the skill carries the why.
This is the step that turns a one-off build into a compounding one. The next run that touches this stack, whether a variant, a rebuild, or someone else's deployment, starts by loading a skill whose description matches the error message on the screen, instead of re-earning two and a half hours of rate-limit lockout. We have measured what that is worth: a curated skill cut a comparable deployment's agent effort by more than four times, and it carried the checks the baseline never thought to run.
What this cost, and what it does not claim
Honest ledger: one working day end to end; eight converges where a documentation-faithful platform would have needed three; roughly 2.5 hours lost to a certificate authority rate limit triggered by an upstream defect; one provider API outage ridden out mid-run; four review rounds and two inspection rounds of a rival model's time. The deployment is disposable: no backups, by decision rather than omission. Fake-key mode has, by construction, never proven a billable completion. The threat model covers a workload that must not reach the internet, not an adversarial payload attacking its own container. The acceptance gates state what they check; the article should too.
The result is live, but the workflow is the product: clip the sources, load a process skill and a domain skill, lock intent under interrogation, hand over scoped credentials, let a rival model attack the plan and then the diff, prove the result against the real platform, and distill what it cost you into the input of the next run.
