# JouleHook — Metered, Gated, Attested Agent Work in Any Host **Version:** 0.1.0-draft.1 · **Status:** Draft · **Profile:** `joulehook/1` **Specification text:** CC-BY-4.0 · **Reference code (`joulehook-rs/`):** Apache-2.0 > *Provisional name.* The steward may rename the standard; the profile string, > receipt kind, and seam names are the stable surface. JouleHook is the open standard for carrying **energy-metering**, **capability-gating**, and **attested receipts** into a third-party AI coding agent or editor — through the host's *own* extension seams, without forking it. It does not replace the host's agent or its model. It binds the three seams that every host already exposes — a **per-action gate**, a **model-call meter**, and a **receipt surface** — to a decide-outside-the-model grant check, an honest energy meter, and a sealed receipt. One agent's work becomes a metered, gated transaction — in Claude Code, Cursor, Windsurf, Antigravity, Zed, JetBrains, or OpenCode — from one core. JouleHook is one of the [Transaction Science open standards](../README.md). Transaction Science is the steward, not the proprietor: the seam interfaces and the right to fork are public. JouleHook **builds on** three sibling standards rather than re-inventing them: **JCP** ([`../jcp`](../jcp)) supplies the grant + decision model, **Mesh** ([`../mesh`](../mesh)) supplies the metering front door, and **`jcp-efficiency`** supplies the `efficiency/1` receipt. --- ## §1 Why JouleHook exists An energy-accounted, methodology-gated coding loop is worth little if it only runs in one editor. Developers already live in Claude Code, Cursor, Antigravity, Zed, and OpenCode, and **no host exposes its built-in agent loop to a third-party extension**: you cannot, from an extension, observe the host agent's token consumption or intercept its native file/shell tools. A naïve "one native plugin per editor that wraps the agent" is therefore impossible by construction. But every host *does* expose the same three seams, and JouleHook is the normative contract over them: 1. A **gate** — the host lets an external process (or an in-process hook) approve, deny, or rewrite a tool action *before it executes*. This is the `PreToolUse`-hook family (Claude Code, Cursor, Windsurf, Antigravity, Cline, OpenCode) and ACP `session/request_permission` (Zed, JetBrains, Neovim). 2. A **meter** — the one place every agent's tokens are authoritative and every request can be refused is the **model-call boundary**: an OpenAI/Anthropic-shaped proxy addressed by a `base_url` override, or the host's own OpenTelemetry `gen_ai.*` export. 3. A **receipt surface** — a place to carry verifiable artifacts back to the user: MCP resources, a status line, or a panel. JouleHook standardizes the *shape* of what rides those seams, so a single core is written once and bound to each host by a thin adapter. --- ## §2 The three seams (the whole standard) | Seam | Binds to (host mechanisms) | JouleHook contract | Backed by | |---|---|---|---| | **Gate** (§3) | PreToolUse-hook family; ACP `request_permission` | Normalized `ActionDecision` (allow / deny / ask / rewrite) + sealed decision receipt | JCP grant (`../jcp`) | | **Meter** (§4) | `base_url` proxy; OTel `gen_ai.*` ingest | `efficiency/1` per completed unit; joule-ceiling enforced at the model boundary | Mesh (`../mesh`), `jcp-efficiency` | | **Receipt** (§5) | MCP resources; status line; panel | Hash-chained, verifiable `efficiency/1` + decision receipts | `jcp-efficiency`, `jcp-receipt` | A conformant deployment binds **at least the Gate seam or the Meter seam**, MUST seal receipts (§5), and MUST state honest provenance (§7). Binding all three is the `full` profile (§6). --- ## §3 The Gate seam (normative) ### §3.1 The normalized action event A host presents a *pending tool action* to the gate. JouleHook normalizes the host's native schema into a host-agnostic `ActionEvent`: | Field | Type | Meaning | |---|---|---| | `session_id` | string | The host session. | | `tool` | string | The action's tool name (host-native or `mcp____`). | | `input` | object | The tool's arguments (e.g. `{"command":"npm test"}`, `{"path":..,"content":..}`). | | `cwd` | string | Working directory. | | `content_id` | string \| null | Content id of the target, when derivable (a file, a command). | An adapter MUST map its host's hook payload onto `ActionEvent` losslessly for the fields the host provides, and MUST NOT fabricate a `content_id` it cannot derive. ### §3.2 The decision The gate returns an `ActionDecision`: - `allow` — the action proceeds unchanged. - `deny` — the action is blocked; `reason` is surfaced to the agent. - `ask` — a human is asked to approve (bound to the host's approval UI where one exists; otherwise degrades to `deny`). - `rewrite` — the action proceeds with `updated_input` substituted (e.g. a redaction). The decision MUST be reached **outside the model** — it is a deterministic function of the `ActionEvent` and the active JCP grant, never a request to the LLM (JCP § *decide-in-the-runtime*). An adapter that asks the host's model to self-approve is **non-conformant**. ### §3.3 The JCP binding The gate decision is a JCP capability + budget check: 1. Resolve the active `SignedGrant` for the session. Verify its issuer signature. 2. The action `tool`×`content_id` MUST be permitted by the grant's capability set; an action the grant does not hold is `deny`. 3. If the action carries an estimable cost, the grant's joule budget MUST be ≥ the estimate; over budget is `deny`. On `allow`, the spent amount is debited. 4. The decision — `allow` and `deny` alike — is sealed (§5). ### §3.4 Host bindings (informative) | Host | Gate mechanism | `deny` signal | |---|---|---| | Claude Code | `PreToolUse` hook (external process, JSON on stdin) | `permissionDecision:"deny"` / exit 2 — blocks even under `--dangerously-skip-permissions` | | Cursor | `.cursor/hooks.json` (`beforeShellExecution`, `beforeMCPExecution`, …) | `permission:"deny"` / exit 2 | | Windsurf | Cascade hooks (`pre_run_command`, `pre_write_code`, …) | exit 2 | | Antigravity | `hooks.json` `PreToolUse` | `allow_tool:false` + `deny_reason` | | OpenCode | `tool.execute.before` (in-process TS) | throw to deny / mutate args to rewrite | | Cline | SDK `beforeTool` (`fail_closed`); file `PreToolUse` | `{cancel}` | | Zed / JetBrains / Neovim | ACP `session/request_permission` | reject | The four external-process hooks (Claude Code, Cursor, Windsurf, Antigravity) share a JSON-in / decision-out shape; a single gate binary MAY serve all four behind a per-host input adapter. OpenCode/Cline are served by a thin in-process shim that calls the same core; Zed/JetBrains by an ACP agent (§6). --- ## §4 The Meter seam (normative) ### §4.1 The model-call boundary Authoritative token accounting and enforceable spend control exist at exactly one place common to every host: the model call. A JouleHook meter binds there by one of two methods: - **Proxy** *(enforcing)* — an OpenAI/Anthropic-shaped endpoint the host is pointed at via `base_url` / `ANTHROPIC_BASE_URL`. It reads the authoritative `usage` on every response, converts tokens→microjoules at honest provenance, and MAY **refuse** a request whose estimated cost exceeds the active grant's joule ceiling (a hard reject before the request reaches the provider). This is the Mesh front door (`../mesh`); its `usage.energy` carries the receipt. - **Telemetry** *(observing)* — ingest of the host's native OpenTelemetry GenAI export (`gen_ai.usage.input_tokens` / `output_tokens`), where the host emits it (Claude Code, VS Code/Copilot, Gemini CLI, Codex). Observe-only: it cannot refuse. ### §4.2 The unit and the ceiling The meter MUST produce an `efficiency/1` receipt per **completed, verified unit of work** (`jcp-efficiency`), carrying microjoules per accepted token at honest provenance and the outcome anchor. A **joule ceiling** declared on the session grant MUST be enforced by a Proxy meter (reject over budget) and, where only a Telemetry meter is present, MAY be enforced only *after the fact* (the run is metered, not capped) — which the adapter MUST disclose (§7). --- ## §5 The Receipt seam (normative) Every gate decision and every completed unit seals as a receipt: - **Decision receipts** — each `allow`/`deny` from §3, sealed as a JCP decision receipt (non-repudiable, hash-chained). - **`efficiency/1` receipts** — each completed unit from §4. Receipts MUST be verifiable by a third party holding the steward's public key, MUST hash-chain within a session, and SHOULD be surfaced through the host's receipt surface (MCP resources, a status-line figure, or a panel). Where the meter is bound via the Mesh proxy and the energy figure is attested (`sandbox-tee::energy`), the receipt MAY carry the attestation commitment (`efficiency/1.energy_attestation`) and its honest trust level. --- ## §6 Conformance profiles A JouleHook adapter declares one or more profiles. Each names the seams it binds: | Profile | Host(s) | Gate | Meter | Receipt | |---|---|---|---|---| | `claude-code` | Claude Code | PreToolUse hook | proxy or OTel | MCP + statusline | | `hooks-family` | Cursor, Windsurf, Antigravity | hooks.json | proxy or OTel | MCP + panel | | `acp` | Zed, JetBrains, Neovim | `request_permission` | own the loop | `session/update` | | `opencode` | OpenCode | `tool.execute.before` | own the loop or proxy | `tui.toast` | | `cline` | Cline | `beforeTool` | own the loop or proxy | registered tool | | `vscode` | VS Code family (Open-VSX) | own contributed tool only | proxy or OTel | status bar + webview | | `meter-only` | Continue, any OTel host | — | OTel `data`/`gen_ai.*` | dashboard | **Minimal conformance:** bind ≥1 seam and seal receipts (§5) with honest provenance (§7). **`full` conformance:** bind all three seams for a host, with a Proxy meter (enforceable ceiling) and hash-chained receipts. The **reference profile is `claude-code`**: a single installable plugin bundling the gate hook, an MCP receipt server, and a status-line joule meter — the deepest gate available anywhere and the clearest end-to-end demonstration. --- ## §7 The honest limit (normative honesty) A host-side adapter, alone, **cannot** meter the host agent's own tokens or its built-in (non-adapter) tool calls — those are invisible from every host's extension sandbox (confirmed across VS Code, Cursor, Windsurf, Antigravity, Zed, JetBrains). Therefore: - A complete energy figure REQUIRES the **Meter seam** (§4) bound via proxy or OTel. An adapter that presents a whole-session energy figure **without** a bound meter is **non-conformant** — it may only report the energy of work that passed through its own tools, and MUST label it as such. - An adapter whose only meter is Telemetry (§4.1) MUST disclose that its joule ceiling is **observed, not enforced**. Overclaiming coverage is a conformance failure, not a rounding of the truth. --- ## §8 Security and trust JouleHook inherits JCP's stance: every action is decided **in the runtime, never in the LLM**, because a tool description and a tool result are untrusted input. The gate is a deterministic function; a `deny` binds regardless of the host's permission mode where the host allows a hook to override it (e.g. Claude Code `PreToolUse` fires before the permission check). Receipts are non-repudiable and hash-chained. The adapter runs inside the host's trust domain and MUST NOT weaken it: it adds gates and receipts, it does not remove the host's own approvals. --- ## §9 Relationship to the sibling standards JouleHook is the **host-binding layer**, not a re-implementation: - **JCP** decides and grants (capability ⊗ joule budget = one signed grant); JouleHook binds that decision to each host's gate seam. - **Mesh** meters and routes at the model front door; JouleHook points a host at it as the Meter seam and enforces the ceiling. - **`jcp-efficiency`** is the receipt; JouleHook carries it through each host's receipt surface, optionally with an attested energy figure (`sandbox-tee::energy`). The contribution of JouleHook is the **normative seam contract + the conformance profiles** that make one metered, gated, attested loop portable across agents that would otherwise each need a bespoke integration.