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]>
26 KiB
agent-joule as the Joule Code reference consumer
agent-joule is the flagship consumer of the Joule Code standard — the SOTA editor surface for it. The protocol itself is the lean joule-code-rs library in the open-standards repository; agent-joule imports it and is one (high-quality) implementation, the way wai-rs is the standard and CommunicationOS is a consumer.
This document maps agent-joule's modules to the Joule Code SPEC.md clauses and states, honestly, what is implemented versus pending. It is the conformance map a reviewer reads to check the claim.
Developer inventory: for the full internal audit — function breakdown, fruition status (live vs aspirational), integration map, and the upstream divergence from Joule — see
dev-docs/.
Dependency on the standard
The panel depends on the standard's reference crates as git dependencies on the public Transaction-Science/open-standards repo, pinned to a revision so a bare clone builds standalone (see crates/agent_joule_panel/Cargo.toml):
joule-code-schema = { git = "https://github.com/Transaction-Science/open-standards.git", rev = "<pinned>" }
joule-code-receipt = { git = "https://github.com/Transaction-Science/open-standards.git", rev = "<pinned>" }
The standard is the single source of truth; agent-joule does not vendor a copy. (The joule-moq/jwp CommunicationOS transport now lives in the same public repo — see dev-docs/.) Bump the standard version by changing the pinned rev.
SPEC clause → module map
| SPEC clause | Requirement | Where in agent-joule | Status |
|---|---|---|---|
| §1.3 No capture | core function never requires a steward's servers; frontier credential lives in the vendor's client; no protocol token | the whole privacy-hardening pass — see PRIVACY_HARDENING.md: sign-in/telemetry/crash/auto-update removed, egress backstop, ACP-registry auto-fetch killed; ACP legs drive the vendor's own logged-in CLI (crates/agent_servers), credential never transported |
Implemented & verified (tshark-confirmed network-silent startup) |
| §6 Receipt | two-part measured + estimated µJ with honest Provenance, per operation |
crates/agent_joule_panel session_receipt() emits a joule_code_receipt::Receipt from real ACP token_usage: tier L4, Energy::from_tokens → Estimator, surfaced as ⚡ N J this session (est.) · L4. Phase 3a: the host's energy is now read from a real jouleclaw_energy::EnergyCounter (host_energy()), surfaced as a separate host N J (provenance) line, with jouleclaw→joule_code provenance mapped 1:1. The honesty split: an L4 op's receipt energy is the remote agent's inference (token-estimated — we cannot read a vendor's shunt); the host line is our machine's measured energy and becomes a local-tier op's receipt energy once L0–L3 land |
Implemented (estimated + host-counter path). Apple Silicon reads JouleClaw's IOReport counter (AppleIoReportCounter, ModelBased SoC-total), falling back to the load-avg Estimator only when IOReport can't be opened. Linux (#[cfg(target_os = "linux")]) reads HwShunt (measured): NVML (NVIDIA GPU shunt) preferred since it's user-readable, then RAPL (Intel/AMD package energy) when a read succeeds — RAPL energy_uj is root-only on modern kernels (PLATYPUS). Both live-verified on AWS: RAPL hw_shunt 98.5 J on a c5.metal (Xeon 8275CL), and NVML hw_shunt 18.9 J on a g4dn.xlarge (Tesla T4), each over a 2 s window |
| §1.1 / §5 Cascade | dispatch L0→L4, stop at first close; L4 = frontier agent over ACP | crates/agent_joule_panel runs the full cascade, stop-at-first-close: L0 (Cache) L0Cache keyed on intent; L1 (Lawful) deterministic literal substitution (rename X to Y / replace X with Y / s/X/Y/) applied with no model; L2 (Retrieval) an on-device semantic cache — the intent is embedded with the L3 GGUF model run as an encoder (jouleclaw_runtime::embed) and a near-paraphrase over the same buffer replays its prior edit (JOULE_CODE_L2_THRESHOLD, default 0.90); L3 (Model) JouleClaw's from-scratch GGUF runtime generates and applies the edit on-device (JOULECLAW_L3_MODEL); L4 (Wire) the federation legs (Claude Code / Gemini / Codex) over ACP. Each applied edit is a real Tier-stamped CodingOp cached for L0, and is sealed through the Sandbox standard (sandbox-mcp) into a signed build receipt — including, for Rust under JOULE_CODE_PROJECT_VERIFY, a genuine cargo check at S5 |
Implemented — L0–L4 all execute; L2 separation live-verified (paraphrase cos 0.88 vs unrelated 0.68 on tinyllama-1.1b-q8_0) |
| §2 / §3 / §4 Cargo, identity, canonical form | coding operations as content-addressed, signed transitions | each local-tier (L1/L2/L3) edit is captured as a real joule_code_cargo::CodingOp — one content-addressed full-file Edit against the prior buffer, stamped with the closing Tier, and cached for L0 stop-at-first-close. L4 turns are still captured at turn granularity (captured_op()) |
Implemented for local tiers; L4 per-operation cargo follows the agent's own diff granularity |
| §8 Conformance | round-trip the C1–C4 wire vectors | crates/agent_joule_panel/tests/conformance.rs runs joule_code_conformance::verify_pack over the published C1–C4 pack vendored at tests/conformance/v1/ (pinned to the dependency rev), asserting zero failures. A second test (emitted_coding_op_is_canonical_and_addressable) checks agent-joule's own emitted CodingOp (via the shared build_coding_op) is canonical and content-addressable (§2/§3) |
Implemented — C1–C4 pack passes in-tree; consumer-output op verified |
| §7 Collaboration | federated, ownerless multi-endpoint editing | not in scope for the v0.1 single-endpoint surface; classified Research in the SPEC | Deferred (Research) |
What "reference consumer" means here
agent-joule does not have to implement every tier to be a faithful consumer of v0.1 — a runtime with only an L4 stage is conformant (SPEC §5), it has simply opted out of the cascade's energy benefit, not its rules. agent-joule's distinctive contributions to the standard are:
- The no-capture posture, made real and verified — the hardest commitment to honor, demonstrated end-to-end (
PRIVACY_HARDENING.md). - A live receipt surface — the joule meter is a real
Receiptemitter wired to real traffic, with honest provenance labeling, not a mock.
The roadmap from here (cascade lower tiers, measured energy, per-operation cargo, CI conformance) is tracked against the standard's own ROADMAP.md.
Live validation (Phase 3c)
The full cascade was validated end-to-end against a real Claude Code turn:
cmd-alt-1connects Claude Code over ACP ("Connected to Claude Code").- Prompt "create hello.txt …" → Claude emits a Write tool call with a diff.
- agent-joule captures it as a real
CodingOpand showsop b3:Ynpl53wU4pwfokr… · 1 edit · L4— a genuine BLAKE3 content address from real agent output (the §2/§3 cargo gap, closed). - Repeating the identical request →
⚡ L0 cache hit — closed at L0 (…), no agent launched; the conversation is unchanged. Stop-at-first-close, proven live: L4 on first request, L0 on the repeat.
Cascade configuration (environment)
The local tiers are off by default (no model is bundled) and configured by environment:
| Variable | Effect | Default |
|---|---|---|
JOULECLAW_L3_MODEL |
Path to a .gguf model. Enables L3 (on-device generation) and L2 (the same model, run as an encoder, supplies intent embeddings). Absent → the cascade skips L2/L3 and falls L1→L4. The in-process reference backend supports F32/Q8_0; the encoder graph supports llama/qwen3/lfm2/eurobert/hunyuan-dense archs. Model choice matters for L3 edits: small chat/reasoning models follow a strict edit contract poorly — they narrate ("Wait, let me think…") or drift instead of emitting clean blocks. (A model emitting gibberish loops is a sampling problem, not the model — see the sampling vars above.) The verified local-edit model is Mellum2 via the mellum engine: JOULE_CODE_L3_ENGINE=mellum + JOULECLAW_L3_MODEL=…/JetBrains_Mellum2-12B-A2.5B-Instruct produces clean, correct <<<FILE…>>> edits on-device (it's a ChatML code model — the harness applies its turn template and stops on `< |
im_end |
JOULE_CODE_L3_ENGINE |
Which in-house engine runs L3 — agent-joule depends on no third-party model runtime (no llama.cpp, no MLX, no vLLM); owning the inference layer is the point, and SOTA speed is the in-house Metal/ANE backend's job, not a vendor's. runtime (default) is JouleClaw's from-scratch in-process GGUF runtime — full per-inference host-energy metering. mellum runs JouleClaw's in-process safetensors MellumModel (loader wave 61) via hybrid_lm::generate — the full-metering destination, but slow on the reference/AMX backend (~0.8 tok/s for the 12B-A2.5B bf16 MoE) until the in-house GPU backend lands, so it's opt-in; here JOULECLAW_L3_MODEL points to the HF snapshot directory (config.json + model-*.safetensors + tokenizer.json), not a .gguf. The mellum decoder has no in-process encoder, so L2 embedding / codebase RAG are unavailable under it (L2 degrades to L3). |
runtime |
JOULE_CODE_FIM_FORMAT |
FIM prompt convention for cursor prediction: starcoder (default — also Qwen-coder/CodeGemma/Mellum), deepseek, or codellama. |
starcoder |
JOULE_CODE_L2_THRESHOLD |
Cosine floor for an L2 retrieval hit. Model-dependent: a decoder-as-encoder scores near-paraphrases ~0.88, a dedicated sentence encoder higher. | 0.90 |
JOULE_CODE_PROJECT_VERIFY (1/true) |
For Rust edits, persist the generated content to the real file and run cargo check --quiet in the crate root as an S5 sandbox step — true type-level attestation in full project context (vs. the non-destructive single-file syntax check for py/js/c). Off because it writes to disk and can be slow. |
off |
JOULE_CODE_GRANT |
Comma-separated tier verbs (l0,l1,l2,l3,l4) the cascade may consult, expressed as a JCP grant (joule-code-jcp). Before any tier runs, the JCP runtime decides the consultation against the grant, debits the tier's energy class, and seals a JCR-1 receipt (allow and deny). A tier the grant doesn't name is skipped and the cascade falls through. Omit l4 and the remote frontier agent is structurally unreachable — denied by the runtime with no model in the loop: the no-phone-home posture made enforceable, not advisory. Unset → the cascade runs ungated (every tier permitted, as before). |
unset (ungated) |
JOULE_CODE_BUDGET_UJ |
Joule ceiling (µJ) over all tiers for the JOULE_CODE_GRANT session. Once the debited total would exceed it, further consultations are denied — the escape hatch closes when the budget is spent. |
unbounded |
JOULE_CODE_AGENT_TOOLS |
Comma-separated tool names the L4 local agent loop may call (read_file, grep, list_files, run, edit_file, mcp), expressed as a JCP capability set (jcp-bridge LocalToolGate). Each call is decided against the grant before it runs and sealed as a JCR-1 receipt; a call to a tool the set doesn't name — including one an injected tool result or a hallucination coaxed the model into — is denied before execution, no model judgement in the loop. Granting only read_file,grep,list_files yields a loop that provably cannot run a command or write a file. Unset → the loop runs ungated (its own is_allowed_command still bounds run). |
unset (ungated) |
JOULE_CODE_AGENT_BUDGET_UJ |
Per-tool joule ceiling (µJ) for the JOULE_CODE_AGENT_TOOLS grant. A tool whose budget is spent is denied. |
unbounded |
JOULE_CODE_AGENT_VERIFY |
A project test command (one of the allowed build/test runners, e.g. cargo test, npm test, pytest) the L4 local agent loop must pass before it finishes an edit. When set, the loop can't just declare a change done: after editing it runs this command, and if it fails it feeds the output back and keeps fixing — autonomous fix-to-green, bounded to 3 attempts — before accepting finish. The command runs under the same Seatbelt sandbox as the agent's run tool. Unset → the agent finishes on its own judgment, as before. |
unset |
JOULE_CODE_AGENT_STEPS |
How many tool-using steps the L4 local agent loop may take before it must stop. Real multi-file edits routinely need more than the original handful; the value is clamped to 1..=50 to keep the loop finite. |
12 |
JOULE_CODE_TEMP / JOULE_CODE_TOP_P / JOULE_CODE_REPEAT_PENALTY / JOULE_CODE_FREQ_PENALTY / JOULE_CODE_SEED |
L3 sampling. Greedy decoding makes small models fall into runaway loops (the. the. the…); the default is nucleus sampling with a frequency penalty (subtracts penalty × count, so a repeated token is penalized N-fold — the real anti-loop lever; history spans the prompt, so it also stops prompt-word echoing) plus a multiplicative repetition penalty, seeded for reproducibility. Temperature is low so a code model reliably follows the strict edit format (loops are handled by the frequency penalty, not by raising temperature); JOULE_CODE_TEMP=0 → greedy but keeps the penalties. |
0.2 / 0.95 / 1.3 / 0.3 / 0 |
JOULE_CODE_CONTROL_SOCK |
Path to a Unix socket on which this instance serves the control protocol (see below). Set → an external process / the JCP-MCP bridge can drive this instance deterministically over the socket; the path is chosen per-instance by the launcher. Unset → no control server. | unset |
JOULE_CODE_CONTROL_TCP + JOULE_CODE_CONTROL_TOKEN |
Also serve the control protocol over TCP for cross-machine coordination. A bare port binds to loopback (127.0.0.1:PORT); reach it from another machine via an SSH tunnel (ssh -L PORT:localhost:PORT host), which provides the encryption and authentication. Every TCP request must carry the matching token; the server refuses to open the port without JOULE_CODE_CONTROL_TOKEN. The local Unix socket stays token-free (local trust). |
unset |
JOULE_CODE_SINGLE_INSTANCE (1/true) |
Restore the classic single-instance lock (a second launch forwards its paths to the running window and exits). Off → multi-instance: every launch is its own process, so many independently-controllable instances can run at once. | off (multi-instance) |
JOULE_CODE_REVIEW_EDITS (1/true) |
Hold every generated L3 edit for review before it touches the buffers. On → the panel shows a per-file diff (added/removed lines, localized) with Accept / Reject; nothing is applied, cached, metered, or sealed until Accept (Reject discards it — the buffers were never written). Off → edits apply immediately, as before. | off |
JOULE_CODE_LIFETIME_UJ |
A lifetime joule ceiling (µJ) on the agent's own metered cognition, enforced by the cascade gate (the single tier-authority, alongside JOULE_CODE_GRANT). The joules it has dissipated over its whole life — the persistent ledger (prior sessions) plus this session — are summed; once they reach the ceiling, the local tiers L1/L2/L3 are spent and the cascade falls through past them to L4 (the cloud, not its own joules). Age (cumulative joules) plus this ceiling is a finite life — the agent ages across sessions and its own thinking eventually exhausts. Setting it alone (no JOULE_CODE_GRANT) still activates the gate (every tier granted, only the lifetime gates). Unset → unbounded. |
unbounded |
sandbox-mcp (the Sandbox standard's attested-execution server) must be on PATH for the sealing step; if it is absent the edit still applies and the panel notes the skip.
Request context (what the on-device tiers see)
Before L3 generates, the harness prompt is assembled from up to three context sources, ordered by directness — each best-effort, each dropping out cleanly when empty:
@mentions. Write@src/foo.rs(a path-like token, containing/or.) and that worktree file's contents are injected — an explicit "look at this." Write@MyStruct(a bare identifier) and that symbol's definition is looked up in the on-device codebase index and injected; skipped when no index is built...escapes are refused; each item is capped and the set deduped. The mention token stays in the intent, so it still distinguishes the request for L0/L2 caching and reads naturally to the model. Mentions flow to every tier that handles the request — the L3 prompt, the L4 cloud message, and the local agent loop's first prompt.- LSP symbol map. The target file's symbols are pulled live from the attached language server (
project.document_symbols) and summarized as a bounded list — each top-level symbol and its immediate children, with kind, name and line span. This hands a weak local model the file's real shape instead of making it infer structure from raw text. Best-effort: no server (or not yet ready, or an unsupported language) → no symbol block, and the edit proceeds on text alone. - Retrieved codebase chunks. The on-device index's nearest chunks to the intent embedding (no egress), as before.
Control protocol (programmatic drive over a socket)
agent-joule exposes a deterministic, CDP-style control wire so an external process — the JCP/MCP bridge, a CLI, a test harness — can drive it without a human at the keyboard. Combined with multi-instance mode (the default), this lets Claude and others spin up and drive one or many editors from the command line.
- Transport. Set
JOULE_CODE_CONTROL_SOCK=/path/to/sockand the instance serves newline-delimited JSON over that Unix socket. The path is the deterministic address of that instance; launch each instance with its own socket. - Wire. Request:
{"id": <any>, "method": "<name>", "params": {...}}. Response (one line):{"id": ..., "ok": true, "result": {...}}or{"id": ..., "ok": false, "error": "..."}. Mutating commands ack as soon as they are dispatched — the work runs asynchronously exactly as a human action would — so the caller observes progress by pollingstate. - Methods.
ping→{pong, pid}— liveness.state→{status, pending_edit, tier_closes, ledger_ops, ledger_energy_uj, local_energy_uj, last_l3}— a full snapshot (cascade telemetry, the agent's life, any edit awaiting review).submit{text}— submit a request through the cascade (L0→L4).run_agent{text}— run the on-device agent loop.accept/reject— apply or discard the edit currently under review (JOULE_CODE_REVIEW_EDITS).open{path}— open a file (worktree-relative or absolute) in the active window.save— write the active buffer to disk (an accepted edit changes the buffer;savepersists it).buffer→{path, text}— the active buffer's current contents (read the result back).revert— restore a reviewed edit's prior contents (the most recent, or{path}).peers→{peers:[{pid, socket, started_at, self}]}— fleet discovery: the other agent-joule instances running on this machine. Each instance advertises itself (pid + control socket) under$TMPDIR/joule-code-instances/; dead entries are pruned on listing.relay{socket|pid, method, params}→ the target's response — the coordination primitive: forward any control command to a sibling instance and get its reply. Discover withpeers, thenrelayasubmit/open/accept/… so one instance (or an external orchestrator) drives others. Refuses to relay to self.
- CLI client. The same binary doubles as the client:
agent-joule --jc-control <method> [--jc-params '<json>'] [--jc-sock <path>]connects, sends one command, prints the JSON response, and exits without launching a UI (socket from--jc-sockorJOULE_CODE_CONTROL_SOCK). Any process speaking this wire works too — the bridge is just a socket client. - MCP bridge.
agent-joule --jc-mcpruns a stdio MCP server (newline JSON-RPC 2.0, protocol2025-06-18) that exposes the control methods as tools namedagent_joule_<method>(agent_joule_submit,agent_joule_state,agent_joule_accept, …) and forwards eachtools/callto the instance's control socket. This is the JCP/MCP bridge: an MCP host (Claude, or the agent loop's ownJOULE_CODE_MCP_SERVER) spawnsagent-joule --jc-mcpwithJOULE_CODE_CONTROL_SOCKpointed at the instance to drive, and Claude gets first-class tools to open files, submit requests, review and accept edits, and read results — all on one machine, no cloud.
// Register the bridge with an MCP host (env points it at the instance to drive):
{ "command": "agent-joule", "args": ["--jc-mcp"],
"env": { "JOULE_CODE_CONTROL_SOCK": "/tmp/jc-1.sock" } }
Example:
export JOULE_CODE_CONTROL_SOCK=/tmp/jc-1.sock
agent-joule /path/to/project & # launch an instance bound to the socket
agent-joule --jc-control ping
agent-joule --jc-control open --jc-params '{"path":"src/main.rs"}'
agent-joule --jc-control submit --jc-params '{"text":"rename greet to welcome"}'
agent-joule --jc-control state # poll: see pending_edit when review mode is on
agent-joule --jc-control accept
Fleet coordination (multi-instance)
agent-joule is multi-instance, and instances are aware of each other. Every instance with a control socket advertises itself for discovery, and any instance can drive any other over the same wire — so several windows (different files, different workspaces) can be coordinated to work a project together.
- Discovery (
peers). On startup each instance writes<pid>.json(pid + control socket + start time) under$TMPDIR/joule-code-instances/.peersreads that directory, liveness-checks each by connecting to its socket, prunes the dead, and returns the live fleet. This is the within-one-machine rung. - Coordination (
relay).relay {pid|socket, method, params}forwards a control command to a sibling and returns its response — so instance A canopena file in instance B,submita task to it, thenacceptthe result, exactly as if driving it by hand. An external orchestrator (or the MCP bridge — Claude) can fan work across the whole fleet:peersto enumerate,relayto assign and collect. - Across the network / SSH (
relay {tcp, token, …}). WithJOULE_CODE_CONTROL_TCP+JOULE_CODE_CONTROL_TOKENset, an instance also serves the protocol over a loopback TCP port, token-gated.relay {tcp: "host:port", token: "…", method, params}(or the client's--jc-tcp host:port --jc-token …) drives a remote instance; tunnel the port over SSH so the link is encrypted and authenticated by SSH and the editor never listens on a public interface. This is the across-SSH rung — explicit point-to-point addressing (you know the host:port), with a shared-secret token. - Global pairing / shared state (next). Auto-discovery across machines and a shared edit log build on the in-house stack — CommunicationOS (
inv-moq, MoQT) for the wire,joule-code-collab(BFTLockstepCluster+ MLS group keying + FROST threshold-auth, already built) for agreement and fleet-auth, and JouleDB for shared state — rather than any third-party realtime DB. The local registry + Unixrelayand the token-gated TCPrelayare the local and point-to-point instances of the same Collab/transport interface those layers generalize into a mesh.
Persistent memory (.joule/ledger.jsonl)
The cascade's L0 cache is the agent's episodic memory — "I have resolved this before, replay it at picojoule cost." On its own that memory is session-scoped: a fresh process starts amnesiac, and every prior resolution is forgotten. The ledger fixes that. Each closed op is appended as one JSON line to .joule/ledger.jsonl (the L0 key it answered, the CodingOp it replays, and the joules it cost); on the next launch the panel rehydrates the L0 cache from that file before the first request, so an identical request closes at L0 across sessions, not just within one.
This is the agent acquiring a past: the ledger is its autobiography (an ordered, content-addressed record of what it did), the rehydrated cache is its memory (recognition of its own prior work), and the cumulative joules are its age — the one clock that is physically real for it, since a state-change costs energy and energy is the receipt that time passed. The panel surfaces it as ⏳ persistent memory: N op(s) · ⚡ X J over this agent's life. Append + rehydrate are best-effort: a missing or partly-corrupt ledger yields a smaller memory, never an error.
Known interaction: registry agents vs. the no-phone-home posture
A type: "registry" ACP agent (e.g. claude-acp) resolves its launch command from the on-disk ACP registry catalog. The Step-4 privacy hardening disables the registry's automatic network fetch, so on a fresh profile the catalog is empty and connect fails with "Custom agent server … is not registered". This is expected, not a bug: the catalog is populated by either an explicit, user-initiated registry refresh (allowed — it is not a background phone-home) or by configuring the agent as type: "custom" with an explicit command (no registry needed). The conflict is inherent to "no unprompted egress" + "registry-resolved agents"; the standard's stance is that catalog population must be an explicit act.
Upstream
agent-joule is a hard fork of the Zed editor and inherits its GPL-3.0-or-later licence. See LICENSE-GPL and the upstream notice in README.md. The Joule Code standard and its joule-code-rs library are separately and permissively licensed (Apache-2.0 / CC-BY-4.0); a GPL reference editor consuming a permissive standard is intentional.