The fork shipped renamed binary/bundle but the product name "Zed" was still
shown everywhere in the UI. Rename the display name to "Joule" (tagline
"Energy-metered agentic coding") while preserving every load-bearing reference.
Display renames (105 .rs files): release_channel display_name, welcome +
onboarding + ai_onboarding, macOS app menu (About/Hide/Quit), window titles,
About panel, settings UI, notifications, update/collab messages, system specs.
Bundle display name (crates/zed/Cargo.toml) -> "Joule" so CFBundleName / the
menu-bar name and the .app are "Joule" (identifier science.transaction.AgentJoule
and the agent-joule binary are unchanged).
Docs (223 .md): product prose Zed -> Joule, fence-aware (code blocks untouched).
Embedded assets (12 JSONC): // comments only.
Legal (4 files): product -> Joule, entity "Zed Industries, Inc." -> "Transaction
Science, Inc.", domain zed.dev -> openie.dev. NOTE: these are structurally Zed's
inherited terms and need a real legal review, not just a rename.
Deliberately PRESERVED (renaming would break the app): font aliases ("Zed Plex
Sans"/".ZedMono" in text_system + assets), HTTP header names (X-Zed-Predict-*),
zed:: action ids, app/bundle identifiers (dev.zed.Zed*, Zed-Editor-* product ids),
zed.dev URLs, ZED_* env vars, crate/package names, "Zed Industries" in non-legal
attribution, and the {zed,Zed} config-dir glob.
Signed + notarized + stapled as Joule.app, installed to /Applications, verified
(Gatekeeper: Notarized Developer ID, FH93XC2VRR). Caveat: test-code string
literals were renamed too, so some `cargo test` assertions may need updating.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
5.7 KiB
agent-joule — Privacy Hardening
agent-joule is a private, no-phone-home fork of Joule. It drives vendors' own logged-in agent CLIs (Claude Code, Gemini) over ACP plus a local vLLM backend. It must never establish a Joule identity, send telemetry, upload crashes, check for updates, or make any unprompted network call to Joule-operated (or Joule-adjacent) infrastructure.
This document records what was removed/neutralized and how it was verified.
Threat model / goal
- No automatic sign-in; no Joule account is ever established.
- No telemetry, analytics, or usage events leave the machine.
- No crash/minidump upload.
- No auto-update or contact with release servers.
- No unprompted startup fetches to Joule-operated endpoints.
- Expected, allowed traffic: the federated agent processes the user explicitly
drives (e.g.
npxACP agents) and the local vLLM backend on localhost. These are the vendor's processes, not agent-joule phoning home.
Changes
A — Sign-in removed
crates/zed/src/main.rs:authenticate()is a no-op — no automatic sign-in/connect at startup, so no Joule identity is established (this was the real phone-home).crates/title_bar: removed the Sign In button + now-unused bindings.crates/onboarding: removed the Joule-cloud "Joule Agent" promo +SignInaction. The danglingonboarding::SignInkeybindings were also removed from the three default keymaps (a removed action left bound = startup keymap-validation panic).- The
clientcrate stays compiled but signed-out; collab/CRDT is retained for later self-hosting against a non-Joule server.
B — Auto-update removed entirely
Updates are a wholesale uninstall + reinstall; there is no in-app updater.
- Deleted the
auto_update,auto_update_ui, andauto_update_helpercrates and all usages (menus, title-bar affordance, activity indicator, init). - This closed a residual egress that the earlier poll-neuter missed:
auto_updatestill contained live calls to Joule release servers (download_remote_server_release/get_release_asset), reachable via "Check for Updates" and the SSH remote-server download path. remote_connection: remote-server auto-download is stubbed to a clear "disabled in agent-joule" error instead of silently fetching from Joule.- Windows packaging was updated to match:
script/bundle-windows.ps1no longer builds/copies/signsauto_update_helper, andzed.issdrops the{app}\toolsand{app}\updatesdirectories (auto-update installer artifacts).
C — Telemetry neutralized
crates/client/src/telemetry.rs:send_event()is a no-op, so everytelemetry::event!call site across the tree is inert with a single edit. Nothing is queued, recorded, or sent.
D — Crash upload disabled
MINIDUMP_ENDPOINTis forced toNone(ignores build/runtime env), so the upload paths inreliability.rsare unreachable. Local minidump capture is unaffected. (Runtime log confirms: "Minidump endpoint not set".)RefreshLlmTokenListeneris inert (no sign-in event to trigger a Joule cloud token refresh).
E — ACP agent-registry auto-fetch disabled
The federation feature auto-fetched the ACP agent registry from
cdn.agentclientprotocol.com at startup (registry index + icons). This is not
Joule telemetry, but it is an unprompted launch-time request to Joule-adjacent
infrastructure — exactly the egress this build avoids.
crates/project/src/agent_registry_store.rs: removed the startup auto-refresh ininit_global, and maderefresh_if_stale()a no-op (this was the path that actually fired, via agent-settings load and registry-agent connect).- The registry still loads from the on-disk cache, and the explicit, user-initiated
refresh()(e.g. opening the Agent Registry page) still works. Only the automatic, staleness-driven fetch is gone.
F — Egress backstop
Belt-and-suspenders: even if some code path survived the removals above, it cannot reach a Joule host.
crates/http_client/src/http_client.rs: the four Joule-host URL builders (build_zed_api_url,build_zed_cloud_url,build_zed_cloud_url_with_query,build_zed_llm_url) nowbail!instead of returning a URL. Any surviving caller fails loudly rather than contactingapi.zed.dev/cloud.zed.dev/llm.zed.dev.
Extensions
assets/settings/default.json:auto_install_extensionsis empty by default (was{"html": true}), so the extension registry/CDN is not contacted at startup. Users opt in to extensions explicitly.
Verification
Method: launch the built agent-joule against a fresh --user-data-dir, then
observe outbound connections with lsof -nP -i -p <pid> and capture TLS SNI
hostnames with tshark -i en0 -Y 'tls.handshake.type == 1' (Wireshark's ChmodBPF
grants capture access without sudo). IP-based inference is unreliable here because
Joule and the ACP CDN both sit behind Cloudflare anycast; SNI is authoritative.
Findings across the hardening passes:
- Zero connections to
*.zed.dev(Zed's own infra) at any point. - The one persistent startup egress observed (~36 connections) was conclusively
identified by SNI as
cdn.agentclientprotocol.com— the ACP registry fetch, fixed in E above. - After E: agent-joule makes zero outbound connections at idle startup (0
established across repeated samples; no
cdn.agentclientprotocol.com, no*.zed.devin the SNI capture). Startup is network-silent.
Re-run the check after any change touching networking:
cargo build --bin agent-joule
tshark -i en0 -f 'tcp port 443' -Y 'tls.handshake.type == 1' \
-T fields -e tls.handshake.extensions_server_name -a duration:26 | sort | uniq -c &
./target/debug/agent-joule --user-data-dir /tmp/azed-check .
# expect: no agent-joule connections to *.zed.dev or *.agentclientprotocol.com