Concepts · 01 macOS only

Proxy model

How Halton Meter intercepts HTTPS traffic without breaking your tools — the edge process, the daemon, and the loopback chain that connects them.

macOS 12+ · Python 3.11+ Reading time 2 min Updated May 11, 2026

Halton Meter is a local HTTPS proxy. Your LLM clients send their requests to a loopback port; Halton Meter terminates the TLS connection with a self-generated CA, parses the request and response, writes one SQLite row per call, and forwards the original request unchanged to the provider. No SDK changes. No code wrapper. The wire format the provider sees is identical to the one your client sent.

The architecture splits into two long-running processes, deliberately:

  • The edge (com.haltonlabs.meter.edge) owns the user-facing port 8081. It is always on, has minimal dependencies, and stays bound across every daemon restart, crash, and upgrade.
  • The daemon (com.haltonlabs.meter) owns the actual mitmproxy listener on internal port 8090 plus the FastAPI HTTP API on 8765. This is the heavyweight process that does TLS interception, adapter parsing, cost computation, and SQLite writes.

This split is what makes the proxy safe to run on a developer’s machine. A daemon crash never breaks your tools; the edge falls through to a raw TCP tunnel. Read Fail-open behaviour for the full guarantee.

Three TCP listeners

┌─────────────────────────────────────────────────────────────┐
│  edge          127.0.0.1:8081   ← apps connect here         │
│  daemon mitm   127.0.0.1:8090   ← edge chains here          │
│  daemon api    127.0.0.1:8765   ← dashboards / curl read    │
└─────────────────────────────────────────────────────────────┘

All three bind on 127.0.0.1. Nothing listens on a public interface. The chosen tuple at last start is persisted to ~/.halton-meter/effective-ports.json so every component (edge, watchdog, status, doctor) reads the same source of truth — important when port discovery picks a fallback.

8080 is not used. The legacy single-process default before v0.1.6 was 127.0.0.1:8080; current defaults are 8081/8090/8765.

A request, end to end

  1. Your tool inherits HTTPS_PROXY=http://127.0.0.1:8081 (the edge) at spawn time — either through halton-meter run, your shell rc, or launchctl setenv.
  2. The tool issues CONNECT api.anthropic.com:443 to the edge.
  3. The edge consults a cached /health probe of the daemon (asymmetric TTL: 1000ms when healthy, 200ms when unhealthy, invalidated immediately on ConnectionRefused).
  4. Daemon healthy → the edge chains through to 127.0.0.1:8090. mitmproxy terminates TLS, the matching adapter parses the request and response, the policy engine evaluates rules, and one row lands in ~/.halton-meter/db.sqlite.
  5. Daemon unhealthy → the edge opens a raw TCP tunnel directly to api.anthropic.com:443, replies 200 Connection Established, and shuttles bytes both ways without decryption. No metering, no blockage.

The edge never decrypts in passthrough mode. It is, by design, a dumb shuttle.

How traffic gets to the edge

halton-meter init chooses one of three routing strategies — none of which use pf packet filter rules. Routing is entirely env-var and networksetup:

ModeWhat’s setCatches
env-only (default)halton-meter run <cmd> onlyThe command you wrap, nothing else
--appsShell rc + launchctl setenv user domainNew terminals, Spotlight/Dock-launched IDEs
--fullAll of the above + networksetup -setsecurewebproxy per active interfaceBrowsers and any GUI app that ignores HTTPS_PROXY

Per-service bypass domains are preserved in --full; the pre-init state is captured into ~/.halton-meter/system_state.json so halton-meter uninstall can restore it exactly.

Why two processes

A single-process design — daemon listens directly on 8081 — was the shape until v0.1.6. It had two failure modes that a developer-facing tool cannot afford:

  1. Daemon crash breaks every tool. Anything with the proxy baked into its environ — a long-running Claude Code session, a Cursor window — would fail every subsequent request until the daemon came back. The fix-by-restart workflow contradicts the cardinal “zero workflow disruption” rule.
  2. Daemon upgrade breaks every tool. Same shape, different cause. pipx upgrade momentarily releases port 8081 and any CONNECT that arrives in that window fails.

Splitting the user-facing port into a separate, minimal KeepAlive=True edge process makes both failure modes invisible to your tools. The edge is the architectural answer to fail-open.

Inspecting state

~ — confirm the listeners are bound
$ halton-meter status                                # overview
$ lsof -nP -iTCP:8081 -sTCP:LISTEN            # edge
$ lsof -nP -iTCP:8090 -sTCP:LISTEN            # daemon mitmproxy
$ lsof -nP -iTCP:8765 -sTCP:LISTEN            # FastAPI

If a row is missing, halton-meter doctor is the next stop — it walks every layer (cert, certifi, edge, daemon, watchdog, system proxy) and prints a copy-pasteable next-action per failure.

What’s next

  • Fail-open behaviour — the full set of invariants that keep your tools running when the daemon is not
  • Project tagging — how each intercepted request gets attributed to a project
  • SQLite schema — what actually lands on disk for every captured request