v0.3.0 · 2026-05-29
Error classification ships end-to-end. The daemon normalises every provider error into seven operator-action buckets; the cloud surfaces them as `error_observations` on the Optimization Overview.
Added
- Seven canonical error buckets —
rate_limit,server_error,bad_request,auth,timeout,network,unknown. Each maps to a single operator action (back off, fix billing, wait it out, fix the request shape) regardless of provider. Anthropic, Gemini, OpenAI, and Grok/xAI all classify in this release. See/docs/concepts/error-classification. - Four nullable wire fields on every log record —
error_class(string),provider_error_code(string),http_status(smallint),retryable(bool). No CHECK constraint or enum onerror_class; the ingest tolerates unknown buckets and ignores unknown top-level fields, so a future daemon never causes ingest rejection. error_observationsarray on the Optimization Overview. Persona-aware remediation copy splitsbilling_exhaustedfrom genericauthfor Solo workspaces and aggregatesserver_error_retryable/server_error_hardinto a provider-health strip for Team and Enterprise. This aggregated surface is cloud-only.
Changed
- The
error_rateKPI predicate is nowerror_class IS NOT NULL OR status != 'success', counting both v0.3.0 classified errors and legacy status-only records. Additive — workspaces with no v0.3.0+ daemon traffic see no shift. - Two judgement calls baked into the classifier: Anthropic HTTP 529 (
overloaded_error) buckets asserver_errornotrate_limit(provider-overload, not a per-key throttle); OpenAI HTTP 429insufficient_quotabuckets asauthnotrate_limit(exhausted billing, not retryable).
v0.2.4 · 2026-05-18
Unified, IDE-agnostic attribution resolver. Closes the edge-side `unattributed` / `edge_store` leak from v0.2.3 and widens the Python pin to `<3.15`.
Added
- IDE-agnostic workspace recovery (Cursor, VSCode, Kiro, Zed, IntelliJ). Two new attribution layers —
ide_env_label(readsCURSOR_WORKSPACE_LABEL/VSCODE_WORKSPACE_LABELfrom the originating process's environment) andide_argv_label(Electron-helper trailing-token regex against argv). Both corroborate against a per-edgeslug → abspathregistry so a stale or moved project cannot be impersonated. - Per-tier
attribution.tier_hittelemetry. Structured event on every tier match AND every strict-slug miss (4b_miss,4c_miss) — lets the operator grep~/.halton-meter/daemon.err.logto confirm which resolver tier won per row.
Changed
- Single attribution code path. The v0.2.3 daemon-side parent-PID walk and the edge-side walk are collapsed into one
attribution.resolver.resolve_unifiedentry point shared by the daemon's bypass path and the edge's pin path. Step 7.25 and its four helpers intagging.pyare deleted; the chromium-helper gate and IDE-family allow-list inedge_attribution.pyare deleted. Attribution policy lives in exactly one place. - Cross-uid / cross-session walk boundary. The parent walk no longer stops on a process-name allow-list — it terminates the moment a parent's uid or controlling session differs from the originating process. Same defensive intent, applies uniformly to every IDE, no list to maintain.
- Python pin widened from
<3.14to<3.15. Plainpipx install halton-meternow succeeds on the system interpreter when macOS's default Python is 3.14. Hard dependencies (pydantic_core,mitmproxy,aiosqlite) ship 3.14 wheels.
v0.2.3 · 2026-05-18
Two correctness fixes. Long-lived CONNECT tunnels now pin attribution past the 5-minute TTL, and macOS plist writes are atomic against same-day reinstall sequences.
Fixed
- Attribution: pin long-lived CONNECT tunnels. Claude Code holds tunnels open for hours during a single session; once 5 minutes elapsed the edge-store row became invisible to the daemon and every subsequent request fell through to
smart_default. The read-side TTL filter onattribution_store.lookupis removed; the write-side prune loop is unchanged but now passesmax_age_s=86400.0(24h) so in-flight tunnels cannot have their rows evicted. - Attribution: daemon-side bounded parent-PID walk for the
claudefamily. When the originating process's cwd resolves toNoneor/(the production failure mode for Claude Code's Mach-O tool-loop workers), the walk consults up to 5 ancestors via psutil and re-runs the rcfile / git / mac_sandbox / workdir-basename layers against the first ancestor's cwd. Cardinal rule preserved: any exception in the walk path is caught silently and the request falls through to the next attribution layer. - macOS plist atomic-write hardening. Every macOS LaunchAgent plist (daemon, watchdog, edge, userenv) now writes via a single
_atomic_write_byteshelper — write to<name>.tmp, fsync,os.replaceover the canonical path. Fixes the same-day-reinstall race that surfaced as an 8-byte<plist/>stub on the operator's edge LaunchAgent on 2026-05-18.
v0.2.2 · 2026-05-12
End-to-end body sync. Daemon now pushes captured request and response bodies to the cloud on the same opt-in posture as metadata — disabled by default, per-project overrides win over the master switch.
Added
[cloud.bodies]config block.enabled(bool, defaultfalse),sync_interval_seconds(default60),max_body_bytes_per_upload(default524288— 512 KiB),per_project(dict of slug → bool). Bodies do not leave the machine untilbodies.enabled = true.halton-meter cloud privacy set bodies.enabled true|false— flips the master body-sync switch.halton-meter cloud privacy set bodies.upload false --project SLUGdrops body uploads for one project while leaving the global switch on.halton-meter cloud privacy showrenders a "Body sync" section with master state, interval, byte cap, and per-project rules.BodyUploaderworker. Sibling to the metadata worker; readsrequest_bodies WHERE uploaded_at IS NULL, POSTs two envelopes per row (request + response) toPOST /v1/requests/{id}/body, stampsuploaded_aton success. Pause/last-error tracked independently incloud_body_sync_stateso a body 401 doesn't pause metadata sync.- Schema migration 5 → 6 adds
request_bodies.uploaded_at DATETIME NULLplus a supporting index. Idempotent — safe on greenfield and upgrade-in-place installs.
v0.2.1 · 2026-05-12
Patch release. Operational bugs and CLI UX polish for the `cloud` subgroup.
Changed
- CLI UX for the
cloudsubgroup matcheshalton-meter status. Rich panels and tables replace raw dicts;cloud statusadds a state banner (ACTIVE / DEGRADED / PAUSED / NOT-CONFIGURED) plus per-field health icons;cloud reconcileadds a daemon-vs-cloud variance line (zero = green check, <0.5% = yellow tolerance, otherwise red).--jsonflag oncloud statusstill emits the machine-readable shape for scripting.
Fixed
cloud connect --base-url <URL>now persists the URL to~/.halton-meter/config.toml. Pre-0.2.1, the flag was a transient override — pairing would succeed but every subsequent command would read[cloud].base_urlfrom TOML, find it empty, and report the daemon as un-paired. Now the connect flow writesbase_urlandenabled=truenext to the 0600 credentials write.halton-meter cloud reconcileno longer 422s. Daemon sentfrom/toquery params; cloud's reconciliation router expectsfrom_date/to_date.
v0.2.0 · 2026-05-12
First minor release. Closes the Phase 2 cloud-sync arc (daemon ↔ halton-meter-cloud) and ships upload-privacy controls. Cloud sync is strictly opt-in — the daemon ships with `cloud.enabled = false`.
Added
halton-meter cloudCLI group.connect,disconnect,status,whoami,sync,reconcile,pause,resume. Pairing handshake (pairing/start→ user approves in dashboard →pairing/poll) mints a single-shothm_sync_…token. Token stored in~/.halton-meter/cloud-credentials.json(chmod 0600) with a Fernet-encrypted mirror in SQLite.- Cloud worker supervised as part of the daemon process. Same-process spawn alongside the proxy / API / heartbeat tasks; gated on
cloud_state.enabledso users who don't pair see zero change. Crash-isolated — a cloud-side failure (transport, 401, schema mismatch) cannot block the proxy hot path. - Upload privacy. Tiered consent presets (
minimal/standard/full) under[cloud.upload]in~/.halton-meter/config.toml. Default isstandardwithsource_workdir = false— the high-leak field (local path) is off by default. Per-field global overrides; per-project rules (upload = falsemakes a project local-only). The daemon redacts before serialisation; the cloud cannot see what the daemon never sends. halton-meter cloud privacy show|set.showprints the resolved policy;set preset standard/set field.source_workdir false/set upload false --project acme-secretmutate the TOML in place (other sections preserved).- Observability + lifecycle. Daemon supervisor logs
cloud.supervisor.spawned/cloud.supervisor.skipwith structured reason.cloud_staterow tracks workspace name, machine id, hostname snapshot, last-connected-at, paused_reason. Worker retry envelope: exponential backoff capped at 300s for transport + 5xx + 429 (withRetry-Afterhonoured). 401 never retried.
v0.1.24 · 2026-05-11
Linux daemon enters public beta. Apps-mode install on Ubuntu 24.04 LTS via `pipx` + `systemd --user`. macOS unchanged.
Added
- Linux support (public beta).
halton-meter init --appsbrings up twosystemd --userunits —halton-meter.service(daemon + mitm + FastAPI) andhalton-meter-edge.service(always-on edge proxy). Verified on Ubuntu 24.04 LTS via Docker soak and AWS Lightsail with real reboot, Claude Code, and Gemini captures. Other systemd distros likely work but are not yet covered by the release gate. - Per-OS install paths: macOS uses
launchd+ Keychain trust + shell-rc edits; Linux usessystemd --user+loginctl enable-linger+ certifi-bundle patching (system trust store untouched). - Linux operator reference at
docs/DAEMON_LINUX.mdin the daemon repo — every command, path, port, unit name, and endpoint cross-checked against the source.
Changed
- Version scheme collapses from four-segment (
v0.1.22.x) to three-segment (v0.1.24) ahead of the v1 cut. Internal nightly cadence continues; the public PyPI tag is the headline. - Daemon systemd unit declares
Restart=always RestartSec=1and anExecStop=halton-meter reset-proxybelt-and-braces so even a SIGKILL leaves the user's network state clean. halton-meter doctoredge-proxy row on Linux usessocket.connect_exfor the port-bound check — works inside containers and non-systemd environments without raising. User-facing contract is "the port is bound", not "systemd thinks the unit is running".
v0.1.22.24 · 2026-05-08
LLM host allowlist gates which traffic the edge chains through the mitm interceptor. Bundled with the v0.1.22.23 freshness-probe URL fix.
Added
- LLM host allowlist controls which hostnames the edge proxy chains through the mitm interceptor. Non-LLM traffic bypasses interception entirely; only allowlisted provider hosts get the captured-cost treatment.
- mitmproxy
ignore_hostsis now derived as the inverse of the LLM allowlist, so the interceptor itself only attaches to traffic the daemon cares about. halton-meter doctorsurfaces the active intercept allowlist. One row, mono, exactly the hostnames the daemon will capture.
Fixed
- Freshness probe URL pointed at the private
haltonlabs/halton-meterrepo viaraw.githubusercontent.com, which returned 404 to unauthenticated clients —halton-meter doctorwas silently fail-open on every install. Probe now targetshttps://haltonmeter.com/rates-manifest.json, hosted out of the public landing repo. (v0.1.22.23 fix, bundled into this release.)
v0.1.22.22 · 2026-05-08
First public release. Local-binary daemon, six adapters across four providers, captured-cost terminal report, bundled dashboard.
Added
- Daemon ships as a local binary on PyPI.
pipx install halton-meterfollowed byhalton-meter initbrings up the three-process architecture: edge on127.0.0.1:8081, mitm listener on127.0.0.1:8090, loopback API on127.0.0.1:8765. - Six adapters across four providers — OpenAI, Anthropic, Gemini, Grok. Direct API surfaces and OAuth surfaces (ChatGPT, Gemini Code Assist) both intercepted without SDK changes.
- Captured-request store: full request and response bodies, redacted, written to
~/.halton-meter/db.sqlite. Schema documented;halton-meter reportslices it from the terminal. - Project attribution chain: env-var override, working-directory match, process-tree walk. Configured per-project via
~/.halton-meter/projects.toml. - Bundled dashboard at
localhost:3000. Open source under Apache 2.0 (separatehalton-meter-dashboardrepo). Read-only; reads the daemon's loopback API. - CLI surface:
init,start,stop,status,report,uninstall. Fail-open behaviour — when the daemon stops, traffic passes through the edge unmodified. - Structured logs under
~/.halton-meter/*.err.log(one pair per process).jq-queryable; documented in/docs/operations/logs.
Changed
- Init self-test retries every 500ms for up to 30 seconds (replaces the single-shot health check). Cold-start no longer reports false negatives.