| Filename | Latest commit message | Latest commit date |
|---|---|---|
|
Some checks failed
standards workspaces build + test / which workspaces this change touches (pull_request) Successful in 13s
repository checks / lockfiles, tests in CI, workflow hygiene, spec and section references, public text (pull_request) Failing after 2m24s
standards workspaces build + test / joule-code-rs (joule-code/joule-code-rs, joule-code-rs, linux-arm64, true) (pull_request) Successful in 1m46s
standards workspaces build + test / macOS energy backends (pull_request) Successful in 6m7s
standards workspaces build + test / mesh-rs (mesh/mesh-rs, -p mesh-engine-gguf -p mesh-openai --features mesh-engine-gguf/gguf,mesh-openai/server, mesh-rs, linux-arm64) (pull_request) Successful in 59s
standards workspaces build + test / jouleclaw-rs (-nodebug, jouleclaw/jouleclaw-rs, -p jouleclaw-energy --features rapl,nvml, jouleclaw-rs, linux-arm64, -C target-feature=+fp16) (pull_request) Successful in 2m38s
standards workspaces build + test / jouledesk-rs (jouledesk/jouledesk-rs, jouledesk-rs, macos) (pull_request) Successful in 1m21s
standards workspaces build + test / jcp-rs (jcp-conformance, jcp/jcp-rs, -p jcp-energy --features nvml, jcp-rs, linux) (pull_request) Successful in 1m39s
standards workspaces build + test / joule-ui-rs (joule-ui/joule-ui-rs, joule-ui-rs, linux) (pull_request) Successful in 1m12s
standards workspaces build + test / joulehook-rs (joulehook/joulehook-rs, joulehook-rs, linux) (pull_request) Successful in 1m14s
standards workspaces build + test / sandbox-rs (sandbox-conformance, sandbox/sandbox-rs, sandbox-rs, linux) (pull_request) Successful in 2m26s
SPEC §3.3 is normative: "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."
The debit lived in the `LocalToolGate` that `gate()` constructs, and
`gate()` runs once per process — this binary is a PreToolUse command the
host spawns per action. So the spend total died with the process on every
single call and the next action started again at the full
`JOULEHOOK_BUDGET_UJ`. **There was no sequence of actions that could
exhaust the budget**, so the deny branch could never be taken, and
`conformance/v1/receipt.json` records the symptom: `budget_remaining_uj:
1000000000`, the untouched ceiling.
Worse, the deny branch that *was* reachable reported the wrong thing: a
single message, "is not in the granted capability set", covered every
denial — so a budget denial would have been recorded as a capability
denial if one could ever have happened.
The hook already wrote the evidence and never read it: sealed receipts
are appended to `JOULEHOOK_RECEIPT_LOG`, hex per line.
- `LocalToolGate::resume` replays a sealed stream into the gate's spend
ledger via `jcp_receipt::replay_spend`, so only verified allow-receipts
of this gate's own steward contribute debits. `Bridge` gains
`steward()`, `steward_pubkey()` and `restore()` to make that possible.
The grants are deterministic (fixed issuer key, `nonce: [0; 16]`,
`expiry: 0`), which is exactly why a later process rebuilds
byte-identical grant ids for the debits to land on — and exactly why
the budget refilled without them.
- `gate()` replays the stream before deciding, and the allow reason now
records the spend and how many receipts it replayed.
- A budget denial says it is a budget denial.
- **No stream configured** ⇒ the ceiling covers one call only. That is
now disclosed in the allow reason rather than asserted: §3.3 states
the budget MUST hold, and per-call it cannot. The crate's env doc
previously carried the disclosure ("Cross-call budget accounting needs
a persistent gate") while the SPEC's MUST did not; the doc is updated
to point at the stream that now provides it.
- **A stream that does not decode denies.** The prior spend is then
unknown, the budget cannot be asserted, and allowing anyway is the
refill the replay exists to prevent — fail closed, as SPEC §8 has this
binary do for an unparseable payload and an unrecognised shape.
A missing file is simply the first call.
The headline test runs three separate `run()` calls — each a fresh gate,
which is what a per-action process is — persisting between them as
`main` does: 2 mJ, 2 mJ, 2 mJ against a 5 mJ ceiling gives allow, allow,
**deny**, with the denial naming the budget. Previously that third call
allowed, and so would the three-hundredth.
Co-Authored-By: Claude Opus 5 <[email protected]>
|
||
| .. | ||
| conformance/v1 | ||
| joulehook-rs | ||
| LICENSE | ||
| LICENSE-SPEC | ||
| README.md | ||
| SPEC.md | ||
JouleHook — Metered, Gated, Attested Agent Work in Any Host
Provisional name — the steward may rename; the profile string joulehook/1 and the
seam names are the stable surface.
The energy-accounting and methodology-gating you build for one coding agent is worth little if it's trapped in that one agent. JouleHook carries it into the editors developers already use — Claude Code, Cursor, Windsurf, Antigravity, Zed, JetBrains, OpenCode — without forking any of them.
It rests on one finding: no host lets an extension observe its agent's tokens or intercept its native tools — but every host exposes the same three seams, and JouleHook is the normative contract over them:
A per-action gate, a model-call meter, and a receipt surface. Bind those three to a decide-outside-the-model grant check, an honest energy meter, and a sealed receipt, and any agent's work becomes a metered, gated transaction — from one core.
JouleHook does not replace the host's agent. It is the host-binding layer for the
sibling standards: JCP decides and grants, Mesh meters at the model front
door, jcp-efficiency is the receipt. JouleHook adds the seam contract and the
per-host conformance profiles that make them portable.
- Licence: Apache-2.0 for reference code (
LICENSE); CC-BY-4.0 for specification text (LICENSE-SPEC). - Status: v0.1.0 draft. Specification published (
SPEC.md); reference workspacejoulehook-rs/ships the gate binding (joulehook-hook) for theclaude-codeandhooks-familyprofiles — one PreToolUse hook that gates by JCP capability (decided outside the model, every decision sealed) and speaks the native decision format of Claude Code, Cursor, Windsurf, and Antigravity behind a--hostselector;joulehook-statusline, the in-editor joule meter (honestestfrom Claude Code's own tokens); andjoulehook-mcp, an MCP server that surfaces the sealed decisions as verified, human-readable resources in any MCP-speaking editor. Theclaude-codefullprofile installs today (joulehook-rs/INSTALL.md); the enforcing Mesh-proxy meter + a site land next.
The three seams
| Seam | Host mechanisms | What rides it |
|---|---|---|
| Gate | PreToolUse-hook family; ACP request_permission |
allow / deny / rewrite, decided outside the model, sealed |
| Meter | base_url proxy; OTel gen_ai.* |
tokens→joules, honest provenance, enforceable joule ceiling |
| Receipt | MCP resources; status line; panel | hash-chained, verifiable efficiency/1 + decision receipts |
Conformance profiles
claude-code (reference) · hooks-family (Cursor / Windsurf / Antigravity) · acp
(Zed / JetBrains / Neovim) · opencode · cline · vscode (Open-VSX) · meter-only
(Continue, any OTel host). See SPEC.md §6.
The honest limit
A host-side adapter alone cannot meter the host agent's own tokens; a complete energy
figure requires the Meter seam (proxy or OTel). An adapter that claims whole-session
energy without a bound meter is non-conformant (SPEC.md §7).
What's here
joulehook/
├── SPEC.md — the normative standard (§1–§9)
├── README.md — this file
├── joulehook-rs/ — reference implementation
│ └── crates/
│ ├── joulehook-hook — ✅ the PreToolUse gate CLI for the hook family
│ │ (Claude Code / Cursor / Windsurf / Antigravity via --host;
│ │ wraps jcp-bridge::LocalToolGate for the JCP check + seal)
│ │ + joulehook-statusline — ✅ the in-editor joule meter
│ ├── joulehook-mcp — ✅ the receipt-surface MCP server (sealed decisions →
│ │ verified MCP resources, any MCP-speaking editor)
│ ├── joulehook-meter — ✅ the meter-seam binding: inject a grant's budget as
│ │ the Mesh joule ceiling (enforce) + bridge Mesh's
│ │ usage.energy into an efficiency/1 receipt
│ └── joulehook-proxy — ✅ the deployable meter: an OpenAI-shaped base_url
│ proxy that caps + seals every model call
└── conformance/v1/ — ✅ reproducible vectors: per-host decision encodings
(decisions.json) + a byte-deterministic sealed deny receipt
(deny-receipt.hex + receipt.json, verified-decode)
JouleHook is one of the Transaction Science open standards. Transaction Science is the steward, not the proprietor.