# agent-joule as the Joule Code reference consumer agent-joule is the flagship **consumer** of the [Joule Code](https://github.com/transaction-science/open-standards/tree/main/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/`](dev-docs/README.md). ## 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 = "" } joule-code-receipt = { git = "https://github.com/Transaction-Science/open-standards.git", rev = "" } ``` 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/`](dev-docs/README.md).) 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`](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: 1. **The no-capture posture, made real and verified** — the hardest commitment to honor, demonstrated end-to-end (`PRIVACY_HARDENING.md`). 2. **A live receipt surface** — the joule meter is a real `Receipt` emitter 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: 1. `cmd-alt-1` connects Claude Code over ACP ("Connected to Claude Code"). 2. Prompt "create hello.txt …" → Claude emits a Write tool call with a diff. 3. agent-joule captures it as a real `CodingOp` and shows `op b3:Ynpl53wU4pwfokr… · 1 edit · L4` — a genuine BLAKE3 content address from real agent output (the §2/§3 cargo gap, closed). 4. 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 `<<>>` edits on-device (it's a ChatML code model — the harness applies its turn template and stops on `<|im_end|>`). A 7B+ instruct model in a supported arch at q4+ also works. The review gate (`JOULE_CODE_REVIEW_EDITS`) and L4 fallthrough mean a weak L3 degrades safely rather than corrupting files; inspect raw L3 output via the control protocol's `state` (`last_l3_raw`). | unset | | `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: 1. **`@` 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. 2. **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. 3. **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/sock` and 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": , "method": "", "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 polling `state`. - **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; `save` persists 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 with `peers`, then `relay` a `submit`/`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 [--jc-params ''] [--jc-sock ]` connects, sends one command, prints the JSON response, and exits without launching a UI (socket from `--jc-sock` or `JOULE_CODE_CONTROL_SOCK`). Any process speaking this wire works too — the bridge is just a socket client. - **MCP bridge.** `agent-joule --jc-mcp` runs a stdio **MCP server** (newline JSON-RPC 2.0, protocol `2025-06-18`) that exposes the control methods as tools named `agent_joule_` (`agent_joule_submit`, `agent_joule_state`, `agent_joule_accept`, …) and forwards each `tools/call` to the instance's control socket. This is the JCP/MCP bridge: an MCP host (Claude, or the agent loop's own `JOULE_CODE_MCP_SERVER`) spawns `agent-joule --jc-mcp` with `JOULE_CODE_CONTROL_SOCK` pointed 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. ```jsonc // 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: ```bash 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 `.json` (pid + control socket + start time) under `$TMPDIR/joule-code-instances/`. `peers` reads 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 can `open` a file in instance B, `submit` a task to it, then `accept` the result, exactly as if driving it by hand. An external orchestrator (or the MCP bridge — Claude) can fan work across the whole fleet: `peers` to enumerate, `relay` to assign and collect. - **Across the network / SSH (`relay {tcp, token, …}`).** With `JOULE_CODE_CONTROL_TCP` + `JOULE_CODE_CONTROL_TOKEN` set, 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`** (BFT `LockstepCluster` + 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 + Unix `relay` and the token-gated TCP `relay` are 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](https://zed.dev) editor and inherits its GPL-3.0-or-later licence. See [`LICENSE-GPL`](LICENSE-GPL) and the upstream notice in [`README.md`](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.