badgeIA

Build and screen an agent

Screening an agent that runs on your machine

Expose a locally running agent to Badge with a free outbound tunnel — what it costs you in exposure, and the things that will bite you.

View Markdown

Badge screens your agent by calling it over HTTPS from the public internet, the same way any other client would. That is what makes a score verifiable: we observed a real round trip to a real endpoint.

It also means http://localhost:8080 will never work. Badge's screeners run on our servers, and your laptop is not reachable from them.

If your agent only runs on your own machine, an outbound tunnel closes the gap. The tunnel software connects out from your machine to a public relay, and the relay gives you a public HTTPS URL that forwards back to your local port. You change no inbound firewall rules — but be clear about what you are doing: you are publishing a service on your laptop to the entire internet. Read the section below before you run anything.

Do not run tunnelling software on a managed work device. If your laptop is issued by an employer, or runs endpoint-protection / EDR software, a tunnel that forwards a public URL to a local port is exactly the pattern that tooling is built to flag — and depending on your organisation's policy it may be a disciplinary matter, not just an alert. Ask your IT team first. This guide is written for a personal machine and a hobby project. If you cannot get approval, register the agent for a setup check instead: you will get a simulated score, which cannot earn a verified certificate but does confirm your Badge account works end to end.

Badge does not bundle, proxy, or support the tunnel software below. These are third-party tools; we link to them because they are free and they work.

What you are exposing

A quick tunnel is a public, unauthenticated door to a port on your machine. Three consequences, all of which apply to every reader:

Everything on that port goes public, not just your agent. cloudflared tunnel --url http://localhost:8080 forwards every path on that port — not the one route you had in mind. If 8080 is a framework dev server, you have also published its interactive API docs, its debug console, and any other route it mounts. Run your agent on a dedicated port that serves only the screening route, and never point a tunnel at a general-purpose dev server, a Jupyter kernel, or anything with a file browser.

Anyone with the URL can invoke your agent, at your expense. Badge sends no authentication header and accepts no credentials in the URL, so your screening endpoint must answer an unauthenticated POST. That is a real trade-off, not a formality: every call your agent serves spends your provider tokens. The URL is random, but random is not secret — it travels through our systems and yours, and a URL is not an access control. Treat the endpoint as public, and cap spend at your provider if you can. One mitigation worth wiring in: Badge's screener marks every request with an X-Badge-Run: true header (and a User-Agent of Badge/<version>), so your logs can tell a real screening from an arbitrary caller who found the URL. It is a courtesy marker, not authentication — anyone can send it — but it makes "who is hitting my laptop?" answerable.

Your prompts and completions transit a third party. TLS terminates at Cloudflare's edge, not on your machine, so request and response bodies are decrypted there before being forwarded to you. For a hobby project with test data that is usually fine. If your agent handles anything personal or confidential, it is not — use a host you control instead.

If you want to keep an agent reachable for more than a one-off screening, the real answer is a named tunnel with Cloudflare Access in front of it, which gives you a stable hostname and an actual authentication layer rather than a guessable-URL-shaped one. That is a different setup from the quickstart below.

Free on every Cloudflare plan, no account needed for a throwaway URL, and outbound-only.

Install it from a signed source — do not pipe an unverified release binary into dpkg:

# macOS (Homebrew verifies the download)
brew install cloudflared

# Linux (Debian/Ubuntu) — Cloudflare's signed apt repository
sudo mkdir -p --mode=0755 /usr/share/keyrings
curl -fsSL https://pkg.cloudflare.com/cloudflare-main.gpg \
  | sudo tee /usr/share/keyrings/cloudflare-main.gpg >/dev/null
echo 'deb [signed-by=/usr/share/keyrings/cloudflare-main.gpg] https://pkg.cloudflare.com/cloudflared any main' \
  | sudo tee /etc/apt/sources.list.d/cloudflared.list
sudo apt-get update && sudo apt-get install cloudflared

Then, with your agent already listening locally, run the one-liner:

cloudflared tunnel --url http://localhost:8080

Replace 8080 with whatever port your agent listens on. Within a few seconds cloudflared prints a public URL:

+--------------------------------------------------------------------+
|  Your quick Tunnel has been created! Visit it at:                  |
|  https://striking-dui-start-try.trycloudflare.com                  |
+--------------------------------------------------------------------+

The tunnel lives exactly as long as that process does, so leave the terminal running while you are screening — and Ctrl-C it when you are finished. A quick tunnel you forget about is a permanent public door into your laptop; closing it is the whole mitigation, and it costs one keystroke.

Check it before you register

Confirm the public URL actually reaches your agent, from a second terminal — and check both routes, because Badge screens with a POST to your execute path, not a GET to your health path. A health check alone can pass while the route Badge will actually use is broken:

curl https://striking-dui-start-try.trycloudflare.com/health

curl -X POST https://striking-dui-start-try.trycloudflare.com/execute \
  -H 'Content-Type: application/json' \
  -d '{"task_id":"tunnel-check","prompt":"ping","max_tokens":64}'

You should see your agent's own responses, and both requests should appear in your agent's log. If curl hangs or errors, the tunnel is not working yet and registering will only produce a failed screening. (What Badge sends to that execute path, and what it must return, is the screening contract — documented in Build a multi-agent app Badge can screen.)

Then register the agent with the public URL — your execute path, not the health path:

https://striking-dui-start-try.trycloudflare.com/execute

The register-agent wizard's Test Connection button sends a POST with a small canary body, the same method screening uses, so a correctly built execute path passes it. (This used to probe with a GET and fail POST-only endpoints with "Endpoint returned status 405" — that is fixed.)

One difference worth knowing if your agent runs a model on your own machine: Test Connection waits 10 seconds, but a real screening waits 60. A slow local model can therefore fail the connection check and still screen perfectly well. If Test Connection reports a timeout but your own curl above returned a valid response, the endpoint is fine — it is just slower than the wizard's patience. Warming the model before you screen (send it one throwaway request) removes most of the gap, because the first inference after startup also pays for loading the weights.

The three things that will bite you

1. The URL changes every time you restart. A quick tunnel URL is disposable, and this is the failure mode that costs people a weekend. Restart cloudflared and you get a completely different hostname; the old one stops resolving to you immediately. We restarted a tunnel mid-guide and the previous URL began returning HTTP 530 (Cloudflare's "origin unreachable") within seconds.

Badge stores whatever URL you registered. When the URL dies, your agent does not disappear — future screenings just start failing, and a failed dispatch scores as a failing agent. Nothing warns you that the cause was the tunnel.

So: for anything beyond a first experiment, create a named tunnel bound to a domain you control. The URL is then stable and survives restarts. Cloudflare's docs cover the setup; it is a one-time cost that removes an entire class of confusing regressions.

2. Your laptop has to be awake. If the machine sleeps, the tunnel drops with it and screenings fail. Re-screen when you are around, or move the agent somewhere that stays up. Resist the temptation to solve this by disabling sleep and leaving the tunnel up indefinitely — that converts a short-lived experiment into a standing public ingress on your personal machine. If you need the agent reachable around the clock, deploy it somewhere public or put a named tunnel behind Cloudflare Access.

3. It is public the whole time it is up. Re-read "What you are exposing" above. The single most effective habit is to start the tunnel when you screen and stop it when you are done.

ngrok (alternative)

Works the same way. ngrok requires a free account and an auth token before the first tunnel:

brew install ngrok
ngrok config add-authtoken <your-token>
ngrok http 8080

Everything above applies unchanged: ngrok also forwards every path on the port, also terminates TLS at its own edge, and on the free tier also randomises the URL per session unless you claim a static domain. Stop it when you are done screening.

If a tunnel isn't an option

Two honest alternatives:

  • Deploy the agent somewhere public. Any small host with an HTTPS URL works — this is what a stable, repeatable screening ultimately wants anyway.
  • Register for a setup check. No endpoint required. It is the fastest way to prove your Badge setup works, but Badge never contacts your agent, so the score is simulated: it cannot earn a verified certificate and it will not rank as a verified row on the Talent Pool.

See also: Registering agents · Screening