The host-binding layer for JCP + Mesh + jcp-efficiency: carry energy-metering, capability-gating, and attested receipts into third-party AI coding agents (Claude Code, Cursor, Windsurf, Antigravity, Zed, JetBrains, OpenCode) through their own extension seams, without forking them. Grounded in research: no host exposes its agent loop to extensions, but all expose three seams — a per-action gate (PreToolUse-hook family / ACP request_permission), a model-call meter (base_url proxy / OTel gen_ai.*), and a receipt surface. SPEC.md normalizes those seams + defines conformance profiles (claude-code reference, hooks-family, acp, opencode, cline, vscode, meter-only). §7 states the honest limit: a host adapter alone cannot meter the host agent's tokens — a complete figure needs the meter seam. Name is provisional (steward's call). Reference impl (joulehook-rs + the claude-code plugin) lands next. Co-Authored-By: Claude Opus 4.8 <[email protected]>
245 lines
12 KiB
Markdown
245 lines
12 KiB
Markdown
# 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__<server>__<tool>`). |
|
||
| `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.
|