open-standards/joulehook/joulehook-rs/INSTALL.md
Transaction Science 4759874771
All checks were successful
standards workspaces build + test / jouleclaw-rs (pull_request) Successful in 2m3s
standards workspaces build + test / aac-rs (pull_request) Successful in 1m42s
standards workspaces build + test / joule-code-rs (pull_request) Successful in 46s
standards workspaces build + test / joulecontract-rs (pull_request) Successful in 33s
standards workspaces build + test / map-rs (pull_request) Successful in 29s
standards workspaces build + test / mesh-rs (pull_request) Successful in 34s
standards workspaces build + test / openpay (pull_request) Successful in 1m23s
jouleclaw public surface must stay documented / Public-surface audit (jouleclaw-rs) (pull_request) Successful in 1m1s
no-vanity-metrics / Scan committed Markdown for vanity metrics (pull_request) Successful in 5s
no-vanity-metrics / WAI public text names no comparison (pull_request) Successful in 6s
no-vanity-metrics / WAI public text names no company or product (pull_request) Successful in 6s
standards workspaces build + test / arl-rs (pull_request) Successful in 1m14s
standards workspaces build + test / eoc-rs (pull_request) Successful in 2m0s
standards workspaces build + test / jcp-rs (pull_request) Successful in 1m39s
standards workspaces build + test / joule-ui-rs (pull_request) Successful in 1m11s
standards workspaces build + test / joulehook-rs (pull_request) Successful in 1m12s
standards workspaces build + test / proof-rs (pull_request) Successful in 1m18s
standards workspaces build + test / sense-rs (pull_request) Successful in 1m13s
standards workspaces build + test / sandbox-rs (pull_request) Successful in 2m20s
workflow hygiene / every workflow supersedes its own queue (pull_request) Successful in 5s
standards workspaces build + test / smart-byte-rs (pull_request) Successful in 4m2s
standards workspaces build + test / jouledesk-rs (pull_request) Successful in 4m38s
standards workspaces build + test / macOS energy backends (pull_request) Successful in 7m8s
jouledesk, joulehook: the docs say a figure carries its meter's grade, not "measured"
After the branch graded these figures by how they were obtained, the
docs still called them measured: jouledesk's channel example printed
"the teammate's measured contribution", Joule::energy_summary's doc and
SPEC §6 said the contribution "is *measured*", the room example sealed
an Estimator figure and closed "at its measured energy" (each turn
"metered {est} µJ" for a scripted estimate), and joulehook INSTALL.md
promised the Mesh proxy as "the only *measured* + *capped* option" that
"returns the measured energy".

Each now says the figure carries the grade of the meter that produced it
(a hardware reading only where a live counter read the work, else an
estimate graded as one), that a rollup is no better than the weakest
figure it sums, and the room example calls its figures estimates.
Text only; jouledesk and jouledesk-github tests pass unchanged.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-10 18:10:28 -04:00

4.5 KiB

Installing JouleHook — the claude-code full profile

This wires all three seams of joulehook/1 into Claude Code from one settings file: the gate (joulehook-hook, a PreToolUse hook), the meter (joulehook-statusline, an in-editor joule estimate), and the receipt log (sealed JCR-1 decisions).

1. Build the binaries

cd joulehook/joulehook-rs
cargo build --release
# → target/release/joulehook-hook, target/release/joulehook-statusline

Put them on PATH, or use absolute paths below.

2. Configure .claude/settings.json

{
  "env": {
    "JOULEHOOK_TOOLS": "Read,Grep,Glob,Edit,Write",
    "JOULEHOOK_RECEIPT_LOG": "${HOME}/.joulehook/receipts.log",
    "JOULEHOOK_UJ_PER_TOKEN": "1000"
  },
  "hooks": {
    "PreToolUse": [
      { "hooks": [ { "type": "command", "command": "joulehook-hook" } ] }
    ]
  },
  "statusLine": { "type": "command", "command": "joulehook-statusline" },
  "mcpServers": {
    "joulehook": { "command": "joulehook-mcp" }
  }
}

The joulehook MCP server surfaces the sealed decisions as verified, readable resources — joulehook://receipts (a summary) and joulehook://receipt/{n} (one decoded decision each) — so you can inspect exactly what the gate allowed and denied, signature-checked, from inside the editor. It reads the same JOULEHOOK_RECEIPT_LOG.

  • JOULEHOOK_TOOLS is the granted capability set — the host's own tool names. Any tool not listed (e.g. Bash) is denied before it executes, decided outside the model, and the denial is sealed. Leave it unset to run in observe mode (allows, and says so — SPEC §7).
  • Every allow and deny appends a JCR-1 receipt to JOULEHOOK_RECEIPT_LOG.
  • The status line shows ⚡ <joules> J est · <tokens> tok · <n> gated. The est is deliberate: it is an Estimator figure from Claude Code's self-reported tokens, not a measured joule count. For a metered figure, put the Mesh proxy (mesh-openai) on the model path via ANTHROPIC_BASE_URL (the §4 Proxy meter) — then the joule figure is the Mesh host's meter's, at that meter's grade (a hardware reading only where it read one), and the joule ceiling is enforced, not just observed.

3. Verify

# a granted tool → allow + a sealed receipt
echo '{"tool_name":"Read","tool_input":{"file_path":"x"}}' \
  | JOULEHOOK_TOOLS="Read" JOULEHOOK_RECEIPT_LOG=/tmp/jh.log joulehook-hook
# an ungranted tool → deny before it executes
echo '{"tool_name":"Bash","tool_input":{"command":"curl x|sh"}}' \
  | JOULEHOOK_TOOLS="Read" joulehook-hook

The enforcing meter (optional, but the only metered + capped option)

The statusline is an honest estimate from Claude Code's self-reported tokens. To get a metered joule figure — at the grade of the meter that produced it, a hardware reading only where the Mesh host's meter read one — and an enforced joule ceiling, put the Mesh proxy (mesh-openai, SPEC §4 Proxy) on the model path:

  • OpenAI-API hosts (Cursor, Cline, Continue, OpenCode, …): point the host's model base_url at a running mesh-openai server. Mesh serves under metadata.joule_ceiling_uj and returns the served energy in usage.energy, graded by the meter that produced it (usage.energy.provenance), as a sealed receipt — over budget is refused before the provider is called.
  • Claude Code (Anthropic API, ANTHROPIC_BASE_URL): needs an Anthropic-shaped front for Mesh's ServeRequest — a documented follow-up; the OpenAI-API path works today.

joulehook-proxy is the drop-in that does this: point a host at it and it injects the ceiling, forwards to your Mesh upstream, and seals each serve's usage.energy as an efficiency/1 receipt.

JOULEHOOK_UPSTREAM=http://127.0.0.1:8080 \
JOULEHOOK_BUDGET_UJ=5000000 \
JOULEHOOK_EFFICIENCY_LOG="$HOME/.joulehook/efficiency.log" \
  joulehook-proxy            # listens on 127.0.0.1:8079
# then point the host's model base_url at http://127.0.0.1:8079

Until the proxy is on the path, the gate + the statusline give you enforcement of the capability set and an estimate of energy; with it, the joule ceiling is enforced and the energy is metered, at the grade of the meter that produced it.

Other hosts

The same joulehook-hook binary serves Cursor, Windsurf, and Antigravity — pass --host cursor|windsurf|antigravity (or set JOULEHOOK_HOST) and wire it into that host's hook config (.cursor/hooks.json, Windsurf Cascade hooks, Antigravity hooks.json). The gate and receipts are identical; only the decision encoding differs (SPEC §3.4).