All checks were successful
energy meters on real hardware / RAPL and NVML on jetson-hub (pull_request) Successful in 38s
standards workspaces build + test / jouleclaw-rs (pull_request) Successful in 10m39s
standards workspaces build + test / aac-rs (pull_request) Successful in 5m32s
standards workspaces build + test / joule-code-rs (pull_request) Successful in 2m4s
standards workspaces build + test / joulecontract-rs (pull_request) Successful in 1m51s
standards workspaces build + test / map-rs (pull_request) Successful in 1m55s
standards workspaces build + test / mesh-rs (pull_request) Successful in 1m55s
standards workspaces build + test / openpay (pull_request) Successful in 6m45s
wai cross-architecture byte-equality / linux-aarch64 (pull_request) Successful in 1m42s
WAI conformance / cargo-class corpora · linux-arm64 (pull_request) Successful in 16m10s
WAI conformance / cross-arch byte-equality · linux-arm64 (pull_request) Successful in 44s
WAI energy receipts / energy receipts · linux-arm64 (pull_request) Successful in 3m0s
wai-pack / wai-pack · linux-arm64 (pull_request) Successful in 2m5s
joule-code conformance gate / Build + round-trip C1-C4 vectors (pull_request) Successful in 1m49s
jouleclaw public surface must stay documented / Public-surface audit (jouleclaw-rs) (pull_request) Successful in 58s
no-vanity-metrics / Scan committed Markdown for vanity metrics (pull_request) Successful in 3s
no-vanity-metrics / WAI public text names no comparison (pull_request) Successful in 4s
no-vanity-metrics / WAI public text names no company or product (pull_request) Successful in 3s
standards workspaces build + test / arl-rs (pull_request) Successful in 1m7s
standards workspaces build + test / eoc-rs (pull_request) Successful in 1m54s
standards workspaces build + test / jcp-rs (pull_request) Successful in 1m27s
standards workspaces build + test / joule-ui-rs (pull_request) Successful in 1m8s
standards workspaces build + test / joulehook-rs (pull_request) Successful in 1m13s
standards workspaces build + test / proof-rs (pull_request) Successful in 1m17s
standards workspaces build + test / sandbox-rs (pull_request) Successful in 2m17s
standards workspaces build + test / sense-rs (pull_request) Successful in 1m10s
standards workspaces build + test / smart-byte-rs (pull_request) Successful in 3m38s
wai cross-architecture byte-equality / linux-x86_64 (pull_request) Successful in 24s
WAI conformance / cargo-class corpora · linux (pull_request) Successful in 6m33s
WAI conformance / v2 vocabulary · manifests validate as stated (pull_request) Successful in 13s
WAI conformance / independent verifiers · claims, JWP, key discovery and integer payloads (Python) (pull_request) Successful in 13m3s
WAI conformance / delivery telemetry · CMCD and CMSD against an independent library (pull_request) Successful in 8s
WAI conformance / cross-arch byte-equality · linux (pull_request) Successful in 24s
WAI conformance / C2PA manifest emission · signed, read back, validated (pull_request) Successful in 46s
WAI energy receipts / energy receipts · linux (pull_request) Successful in 1m47s
WAI HTTP delivery / HTTP corpus · falsification and differential (pull_request) Successful in 1h19m25s
WAI HTTP delivery / cache rules · controls and labelled caches (pull_request) Successful in 1m6s
WAI JPEG-AI whole decode (pinned inputs, required) / fetch pinned JPEG-AI inputs → whole decode + pixel hash, required (pull_request) Successful in 1m2s
WAI neural sink gates / model-free refusals of the neural sink (pull_request) Successful in 1m8s
wai-pack / wai-pack · linux (pull_request) Successful in 1m4s
wai-pack / wai-pack · independent verifier (Python) (pull_request) Successful in 1m43s
wai-pack / wai-pack · the floor through a media prober (pull_request) Successful in 21s
wai-pack / wai-pack · dependency licences (pull_request) Successful in 1m8s
WAI section references / cited sections exist (pull_request) Successful in 10s
WAI mbt2018_mean integer decode (regenerated vectors) / regenerate mbt2018_mean integer vectors → Rust decode, required (pull_request) Successful in 4m16s
wai-web wasm32 / wai-web wasm32 build · corpus, menu and writers (pull_request) Successful in 2m41s
workflow hygiene / every workflow supersedes its own queue (pull_request) Successful in 4s
standards workspaces build + test / jouledesk-rs (pull_request) Successful in 37m35s
standards workspaces build + test / macOS energy backends (pull_request) Successful in 47m17s
wai cross-architecture byte-equality / macos-arm64 (reference) (pull_request) Successful in 1m4s
WAI conformance / cargo-class corpora · macos (pull_request) Successful in 16m12s
WAI conformance / cross-arch byte-equality · macos (pull_request) Successful in 40s
WAI energy receipts / energy receipts · macos (pull_request) Successful in 3m50s
wai-pack / wai-pack · macos (pull_request) Successful in 3m28s
A `PolicyReceipt` recorded `policy_version` and, on an allow,
`decision.rule` — a counter and a name. Neither says what the rules were.
The hole is narrower than it first looks, and worth stating precisely because
the obvious version of it is already closed: `InMemoryPolicyEngine::install_policy`
refuses a non-increasing install, so *within one engine instance* a version does
identify a ruleset. What the counter cannot do is mean anything outside that
instance. A restart, a replica, or a second host starts its own count at 1 with
whatever rules it was handed, and the monotonic guard lives in the bundled
engine rather than in the `PolicyEngine` contract, so another backend need not
have one. Either way a receipt citing `(engine_id, policy_version)` is a
pointer into state only its producer holds: a verifier has to be *told* what
engine-A's version 1 contained, by the party whose decision is under audit.
`Policy::digest_hex` content-addresses the rules. Three choices make it a digest
of the policy rather than of a serialization, each with a test:
- **Rule order is in the digest.** `decide` scans `Deny` rules first and
short-circuits, so reordering can change the outcome: two orderings are two
policies.
- **The agent list is sorted before hashing.** `agents:one_of` is matched by
membership, so two policies differing only in that order are the same policy
and must share a digest.
- **A port range is hashed as written, not normalised.** A range whose `from`
exceeds its `to` matches nothing, which is a different policy from the same
pair reversed.
`version` is excluded: the digest answers "which rules", the version travels
beside it answering "which installation", and folding the counter in would make
one ruleset hash differently per engine. Mode and method tags are written out
rather than taken from serde, so the digest does not move if a rename or a
variant reordering lands.
Every variable-length part is length-prefixed. The first test I wrote for that
did not actually exercise it — a rule named "ab" with glob "c" is already
distinguishable from one named "a" with glob "bc", because the constant tags
("allow", "action:filesystem") sit between them. Falsification caught it:
stripping the prefixes left the suite green. The prefixes are load-bearing in
the one place two *arbitrary* strings are adjacent — the sorted agent id list,
where nothing separates one id from the next and the count is equal either way,
so `["ab","c"]` and `["a","bc"]` render to the identical run "abc". The test now
pins that case, and fails without the prefixes.
The digest is recorded on the `AuditEvent` at decision time, not read off the
engine when the event is later shipped: the live policy can be replaced in
between, and attributing a decision to rules installed after it was made is the
error the field exists to prevent. Both construction sites set it — `evaluate`
and `evaluate_traced` — because a digest on one but not the other is a gap that
looks like data. `PolicyEngine::policy_digest` defaults to `None` so a backend
that holds its rules somewhere it cannot hash stays conformant rather than being
forced to invent one; a consumer requiring the binding can only refuse such an
engine if the absence is reported.
Additive on the wire. `policy_digest` is skipped when absent *and* omitted from
`canonical_wire`, so a receipt without it has a signed core byte-identical to
one produced before the field existed. Regenerating the pack proves it: vectors
1-3 keep their published `signed_core_cbor_hex` and `cose_sign1_hex` exactly,
and new vector 4 is vector 1's decision with the binding, differing by exactly
that one key. The Rust, Python and Go reference verifiers all verify 4/4 with no
verifier change, because each encodes the map the vector publishes.
Three other crates build a `PolicyReceipt` by struct literal in test fixtures
(`jouleclaw-cli`, `jouleclaw-ledger`, `jouleclaw-otel`); each passes `None`, the
legacy shape. A targeted `cargo check -p jouleclaw-policy-receipt --all-targets`
did not reach them — only the whole-workspace run did.
`jouleclaw-otel` exports the digest beside the version it already exported. A
span naming the version alone has the same gap the receipt field closes. Absent
is left absent rather than exported as an empty string, so a query can tell "no
binding" from "a binding that is blank".
SPEC §10 gains §10.1: the preimage written out byte-for-byte, the three
rendering rules as MUSTs, the requirement that the digest be the one in force at
the decision, and the requirement that an absent digest be omitted rather than
nulled.
Co-Authored-By: Claude Opus 5 <[email protected]>
1244 lines
54 KiB
Markdown
1244 lines
54 KiB
Markdown
# JouleClaw v1 — Energy-Optimised AI Runtime
|
||
|
||
Status: **Draft Standard**. Reference implementation:
|
||
[`jouleclaw-rs/`](jouleclaw-rs/) (Rust workspace, Apache-2.0).
|
||
Spec text: CC-BY-4.0.
|
||
|
||
Keywords **MUST**, **MUST NOT**, **SHOULD**, **MAY** are RFC 2119.
|
||
|
||
---
|
||
|
||
## 1. Scope and doctrine
|
||
|
||
JouleClaw is a runtime + harness + agentic-layer standard for
|
||
energy-optimised AI. It applies to:
|
||
|
||
- text and code generation (LLMs, SSMs, state-space hybrids)
|
||
- multimodal understanding and generation (vision, audio, video, 3D)
|
||
- diffusion-based image / video synthesis (DDPM / LCM / Euler / Heun /
|
||
DPM++ / CFG++ / Rectified Flow and friends)
|
||
- speech recognition (Whisper-class)
|
||
- the developer-facing harness that drives the above
|
||
|
||
The **doctrine** is one sentence: capability per joule, not capability
|
||
per parameter.
|
||
|
||
Three normative consequences follow from the doctrine.
|
||
|
||
### 1.1 The cascade
|
||
|
||
A conforming runtime **MUST** dispatch every resolution through an
|
||
explicit energy-tiered cascade and **MUST** stop at the first tier
|
||
that closes the query. The tiers are:
|
||
|
||
| Tier | Wire tag | Named tag | Joule class | What it is |
|
||
|------|----------|-----------|-------------------|--------------------------------------------------|
|
||
| L0 | `L0` | Cache | picojoules | content-addressed cache hit |
|
||
| L1 | `L1` | Lawful | nanojoules | deterministic primitive (text / code only) |
|
||
| L2 | `L2` | Embed | sub-millijoules | embedding + hybrid retrieval |
|
||
| L3 | `L3` | Model | joules | local SSM / ternary / multimodal / diffusion |
|
||
| L4 | `L4` | Wire | tens of joules | remote frontier RPC (escape hatch) |
|
||
|
||
L1 (Lawful) is meaningful only for text and code modalities. For
|
||
image / audio / video / 3D generation the cascade collapses to
|
||
{L0, L2, L3, L4} — there is no deterministic compute path that
|
||
produces a high-quality image from "draw me a cat".
|
||
|
||
A conforming runtime **MUST NOT** invoke a higher-cost tier before
|
||
the lower-cost tiers have returned `Unresolvable`. Inference is the
|
||
**last resort**, not the entry point.
|
||
|
||
### 1.2 Recent knowledge over frozen weights
|
||
|
||
A conforming runtime **MUST** prefer fresh, provenance-stamped
|
||
retrieval over synthesis from frozen model weights whenever:
|
||
|
||
- the cost of the fetch + extract is below the cost of the
|
||
inference, AND
|
||
- the retrieved source's trust tier is above the operator's
|
||
configured threshold.
|
||
|
||
This is the L3.5 stage in the cascade implementation — between L2
|
||
(local index) and L3 (local model). The retriever **MUST** emit a
|
||
`ClaimProvenance` envelope (see §4) for every fact that contributes
|
||
to the resolution.
|
||
|
||
### 1.3 Honest energy provenance
|
||
|
||
Every energy reading **MUST** carry a `Provenance` tag declaring
|
||
how the value was obtained:
|
||
|
||
| Provenance | Meaning |
|
||
|----------------|--------------------------------------------------------|
|
||
| `HwShunt` | Hardware reading: a shunt / coulomb counter (Jetson INA3221), or a vendor energy counter (RAPL MSR, NVML cumulative-energy counter, Apple IOReport) while it is live for the domain it claims (below) |
|
||
| `ModelBased` | Vendor-provided estimate from freq / voltage / utilisation (ROCm SMI, NVML `power.draw`) |
|
||
| `Estimator` | JouleClaw static cost model from arch × precision × batch tables |
|
||
|
||
A vendor energy counter's reading is a real reading. What separates
|
||
one such reading from another is provenance — which counter, over
|
||
which domain, and whether it was live when read — not accuracy. So a
|
||
live vendor counter is graded `HwShunt`, and liveness is the
|
||
condition:
|
||
|
||
**Liveness (MUST).** A reading from a vendor energy counter (RAPL,
|
||
NVML, Apple IOReport, …) **MAY** be graded as a hardware reading only
|
||
while the counter is demonstrably live for the domain the figure
|
||
claims: the implementation **MUST** have observed the counter
|
||
covering that domain advance under load (for Apple IOReport: the CPU
|
||
energy rail itself advancing in at least 5 sample intervals, in at
|
||
least 4 of every 5, over at least 300 ms while the probing thread
|
||
burns CPU). A counter that is present but not live — frozen, or
|
||
republished in lumps — **MUST NOT** produce a hardware-graded figure;
|
||
the implementation falls back to its estimator or reports the work
|
||
unmetered, and **SHOULD** record why. A figure **MUST NOT** claim
|
||
coverage of a domain its counter does not read; where the format has
|
||
a coverage field, such a figure is Partial.
|
||
|
||
Apple IOReport's GPU rail is not a partial reading of CPU work. It is
|
||
machine-wide — it meters every client of the GPU on the host, display
|
||
compositing among them, and moves while nothing of the metered work
|
||
runs — so where the CPU rail is frozen and only the GPU rail advances,
|
||
the reference implementation declines the counter and estimates the
|
||
work rather than reporting the GPU rail as `Partial`: the rail does
|
||
not cover part of the work, it covers other work.
|
||
|
||
The thermodynamic circuit breaker **MUST** enforce at the granularity
|
||
of the **worst** counter in the request's span. Implementations
|
||
**MUST** surface `resolution_uj()` and `min_window_ns()` so callers
|
||
can refuse to claim microjoule accuracy on platforms that cannot
|
||
deliver it.
|
||
|
||
Realistic floors:
|
||
|
||
- Intel / AMD x86 RAPL: ~1 μJ, ~10 ms window. `HwShunt`.
|
||
- Jetson INA3221: ~10 mW (integrate for energy). `HwShunt`.
|
||
- NVIDIA discrete (NVML cumulative-energy): ~1 mJ, ~50 ms. `HwShunt`.
|
||
- Apple Silicon IOReport: ~1 mJ, ~10 ms. `HwShunt` while its CPU
|
||
energy rail is live. On macOS 27.0.1 an unprivileged reader's CPU
|
||
rails are frozen (observed on an M5 Max and an M3 Ultra), so the
|
||
counter is declined there and the host is estimated (`Estimator`).
|
||
Marketing claims of microjoule precision on Apple Silicon are
|
||
sales talk; the spec is honest about this.
|
||
- Apple Silicon, per process (macOS 27.0.1, unprivileged): the
|
||
process's own CPU energy (`proc_pid_rusage`, nJ, live) is
|
||
`HwShunt`. Its GPU energy is `HwShunt` in two cases only: an exact
|
||
zero when its own GPU time (I/O Registry) did not move, and the
|
||
IOReport `GPU Energy` rail (nJ, live) when it had at least 99% of
|
||
every GPU client's GPU time over the span. A shared GPU gives no
|
||
figure: the rail **MUST NOT** be split by GPU time, which is not
|
||
energy-proportional (measured: ~1.9 W vs ~17 W per GPU-second for
|
||
two jobs). Without a GPU figure, the process's CPU figure alone is
|
||
`Partial`. No live source covers the ANE: a figure for work that
|
||
used it is `Partial`.
|
||
- Consumer AMD GPU, ARM PMU: no usable counter. Use a calibrated
|
||
`Estimator` and surface the wider tolerance band.
|
||
|
||
---
|
||
|
||
## 2. The receipt
|
||
|
||
Every cascade walk **MUST** produce one [`Receipt`][prov]:
|
||
|
||
```json
|
||
{
|
||
"jc_receipt": "1",
|
||
"id": "<uuid v4>",
|
||
"closed_at": "<rfc 3339>",
|
||
"input_hash": "<blake3 hex of normalised input>",
|
||
"tier": "L3",
|
||
"joules_uj": 3500000,
|
||
"energy_provenance": "ModelBased",
|
||
"tools_touched": [
|
||
{
|
||
"tool_id": "model:gemma4-9b-q5_k_m",
|
||
"joules_uj": 3500000,
|
||
"energy_provenance": "ModelBased"
|
||
}
|
||
],
|
||
"claims": [
|
||
{
|
||
"source": "https://en.wikipedia.org/wiki/Gemma_(model)",
|
||
"content_hash": "<blake3 hex>",
|
||
"fetched_at": "<rfc 3339>",
|
||
"trust_tier": 9
|
||
}
|
||
],
|
||
"eoc_stage": null
|
||
}
|
||
```
|
||
|
||
Receipts are shaped to seal cleanly inside a Smart Byte signed
|
||
envelope (see the sibling [Smart Byte standard](../smart-byte/)).
|
||
The Smart Byte signature attests to receipt integrity; the receipt
|
||
itself is the auditable thermodynamic record.
|
||
|
||
A receipt's `energy_provenance` is the **worst** counter seen across
|
||
all `tools_touched` — not the best. This is normative.
|
||
|
||
[prov]: jouleclaw-rs/crates/jouleclaw-prov/src/lib.rs
|
||
|
||
---
|
||
|
||
## 3. The `.jc.toml` sidecar
|
||
|
||
A model file on disk (GGUF / safetensors / MLX) tells you tensor
|
||
shapes and quant schemes. It does NOT tell you what one forward
|
||
pass costs in joules on real hardware. The cascade auction cannot
|
||
pick the cheapest backend if every backend lies about its cost.
|
||
|
||
A `.jc.toml` sidecar travels next to the model file and declares
|
||
the energy contract. See [`jouleclaw-pack`][pack] for the schema.
|
||
Backends loading a model with a `.jc.toml` are bound to honour the
|
||
declared cost within the per-measurement `drift_factor` tolerance;
|
||
the runtime trips `CostDrift` and down-weights the backend in
|
||
future auctions when measured > declared × drift_factor.
|
||
|
||
Without a declared-cost contract, "energy-optimised inference" is
|
||
marketing. With one, it is an engineering claim a third party can
|
||
verify against the published reference-hardware corpus (see §6).
|
||
|
||
[pack]: jouleclaw-rs/crates/jouleclaw-pack/src/lib.rs
|
||
|
||
---
|
||
|
||
## 4. Provenance
|
||
|
||
Every retrieved claim that contributes to a resolution **MUST**
|
||
carry a `ClaimProvenance` envelope:
|
||
|
||
```json
|
||
{
|
||
"source": "<url, did:plc, doi, ...>",
|
||
"content_hash": "<blake3 hex of bytes as fetched>",
|
||
"fetched_at": "<rfc 3339>",
|
||
"trust_tier": 9
|
||
}
|
||
```
|
||
|
||
`trust_tier` is 0..10. Bootstrap data comes from the Wikipedia
|
||
perennial-sources list (machine-readable via the Wikimedia
|
||
Enterprise parsed-references endpoint); operators **MAY** extend the
|
||
table.
|
||
|
||
A retriever **MUST NOT** present a synthesised fact as a retrieved
|
||
claim. If the runtime falls back to model inference because no
|
||
retrieval closed the query, the receipt's `claims` array **MUST**
|
||
remain empty for that tier and the resolution carries the
|
||
`L3:Model` tag, not `L2:Embed`.
|
||
|
||
---
|
||
|
||
## 5. The MCP tool surface
|
||
|
||
The standard tool dispatch protocol is MCP (Model Context Protocol).
|
||
A conforming runtime **SHOULD** speak MCP for interop with the
|
||
Claude Code / Codex / Goose ecosystem.
|
||
|
||
Every MCP tool call **MUST** be wrapped in
|
||
`jouleclaw-mcp::dispatch_metered`, which brackets the call with
|
||
energy counter reads and pushes a `ToolTouch` entry onto the
|
||
running receipt's `tools_touched` ledger.
|
||
|
||
### 5.1 The `joule-mcp` profile
|
||
|
||
Two JouleClaw-aware endpoints **MAY** opt into a CBOR transport
|
||
profile by advertising the capability tag `x-jouleclaw/joule-mcp@1`
|
||
in their handshake. When both sides advertise it, the wire encoding
|
||
switches from JSON-RPC to length-prefixed CBOR (~30–50% lower
|
||
serialisation tax per call). When either side does not advertise,
|
||
the encoding **MUST** fall back to standard MCP JSON-RPC.
|
||
|
||
Non-JouleClaw MCP clients (Claude Code, Codex, etc.) see plain
|
||
JSON-RPC. The CBOR profile is a negotiated extension, never a
|
||
replacement.
|
||
|
||
---
|
||
|
||
## 6. Conformance
|
||
|
||
A conforming runtime **MUST** produce a `Receipt` for every published
|
||
conformance vector in [`conformance/v1/`](conformance/v1/) that matches
|
||
the canonical receipt's `tier`, `tools_touched[].tool_id`, and
|
||
`claims[].content_hash`.
|
||
|
||
The `joules_uj` field in a conforming receipt **MUST** fall within
|
||
the platform-specific drift band declared in the `.jc.toml` sidecar
|
||
for each tool / model touched. Drift beyond the band is a
|
||
non-conformance signal, not a hard failure — the runtime **SHOULD**
|
||
emit `DriftAlert` and let the operator decide whether to demote the
|
||
backend or pause the deployment.
|
||
|
||
Conformance vectors are signed at release time. Implementations
|
||
self-certify by round-tripping the public vectors and publishing
|
||
their receipts.
|
||
|
||
---
|
||
|
||
## 7. Wire format versioning
|
||
|
||
- The cascade tier wire tags (`L0`–`L4`) are stable across versions.
|
||
A future major version **MAY** add tiers (e.g. `L5:Quantum`) but
|
||
**MUST NOT** renumber existing ones.
|
||
- The `jc_receipt` field on a receipt is the schema version. This
|
||
document defines `"1"` (the cascade receipt of §2) and `"policy/1"`
|
||
(the policy receipt of §10). Unknown major → reject. Unknown minor
|
||
→ accept by ignoring unknown fields. New `jc_receipt` schemas are
|
||
added under their own prefixed namespace (`"<name>/<version>"`); the
|
||
bare numeric form `"N"` is reserved for the cascade-walk receipt.
|
||
- The `jc_pack` field on a `.jc.toml` sidecar follows the same rule.
|
||
- The `jc_workspace` field on a workspace manifest (§11) follows the
|
||
prefixed-namespace rule (`"workspace/1"`).
|
||
|
||
---
|
||
|
||
## 8. Agent identity
|
||
|
||
A long-running JouleClaw agent is identified by a stable handle
|
||
bound to an Ed25519 public key. The canonical derivation is W3C
|
||
`did:key` over multicodec Ed25519:
|
||
|
||
```
|
||
handle = "did:key:z" || base58btc(0xed 0x01 || raw_public_key_32)
|
||
```
|
||
|
||
A conforming `AgentIdentity` carries `(handle, public_key, optional
|
||
EAT attestation, optional created_at)`. A verifier **MUST** confirm
|
||
that `handle == did:key_handle(public_key)` before accepting any
|
||
signed material from that identity. Attestation envelopes
|
||
(RATS / EAT per RFC 9711) **MAY** be bound to the identity by
|
||
placing the same `handle` in the EAT `cti` claim.
|
||
|
||
Where this identity flows:
|
||
|
||
- `Receipt.steward` (§2): the cascade-receipt steward identity is the
|
||
`handle` form when self-describing.
|
||
- `PolicyReceipt.steward` (§10): same handle, same key.
|
||
- `WorkspaceManifest.owner` (§11): same handle, same key, with the
|
||
pubkey embedded so a third-party verifier needs no external mapping.
|
||
- COSE_Sign1 `kid` header on any signed envelope: same handle.
|
||
|
||
Identity rotation is out of scope for v1. KERI-based AID rotation is
|
||
the recommended substrate; this spec does not normatively constrain
|
||
the rotation mechanism.
|
||
|
||
---
|
||
|
||
## 9. The policy engine
|
||
|
||
JouleClaw runtimes **MAY** consult an out-of-process policy engine
|
||
before executing an `Action`. When they do, the engine MUST be
|
||
deny-by-default and operate at filesystem / network / process
|
||
granularity.
|
||
|
||
A conforming engine exposes the following surface:
|
||
|
||
- **`Action`** — `Filesystem { path, mode }` |
|
||
`Network { host, port, method }` |
|
||
`Process { binary, argv }`.
|
||
- **`Decision`** — `Allow { rule }` | `Deny { reason }`, where
|
||
`DenyReason` is `NoMatchingAllow` (deny-by-default) or
|
||
`ExplicitDeny { rule }`.
|
||
- **`Policy`** — ordered list of rules, plus a monotonic `version`.
|
||
A `Rule` is `(effect, agent_scope, action_predicate, name)`.
|
||
- **Evaluation rule** — explicit `Deny` rules short-circuit before
|
||
any `Allow`. First matching `Allow` wins. No matching `Allow` ⇒
|
||
deny-by-default. **MUST** record an `AuditEvent` per evaluation.
|
||
- **`AuditEvent`** — `(sequence, policy_version, agent, action,
|
||
decision)`. Sequence is monotonic within the engine instance.
|
||
- **Live policy update** — `install_policy(new)` accepts only when
|
||
`new.version > current.version`.
|
||
|
||
A conforming **out-of-process transport** is line-delimited JSON
|
||
over a duplex byte stream. One request per line, one response per
|
||
line. Operations: `evaluate`, `audit`, `audit_since`,
|
||
`policy_version`, `install_policy`. The server treats a malformed
|
||
line as an error response and keeps the stream open.
|
||
|
||
The reference engine is `jouleclaw-policy::InMemoryPolicyEngine`
|
||
(in-process) plus `jouleclaw-policy-ipc::PolicyClient` /
|
||
`jclaw-policyd` (out-of-process).
|
||
|
||
---
|
||
|
||
## 10. The policy receipt
|
||
|
||
A `PolicyReceipt` is the signed audit record for one policy
|
||
decision. It is a **sibling** of the cascade receipt in §2: same
|
||
COSE_Sign1 envelope (alg = -8 EdDSA over canonical det-CBOR), same
|
||
steward identity, but its own schema id (`"policy/1"`) and its own
|
||
wire shape.
|
||
|
||
```json
|
||
{
|
||
"jc_receipt": "policy/1",
|
||
"id": "<uuid v4>",
|
||
"closed_at": "<rfc 3339>",
|
||
"steward": "<did:key handle>",
|
||
"engine_id": "<id>",
|
||
"audit_sequence": 7,
|
||
"policy_version": 2,
|
||
"policy_digest": "<blake3 hex, OPTIONAL>",
|
||
"agent": "<did:key handle>",
|
||
"action": { "kind": "filesystem", "path": "/workspace/x", "mode": "read" },
|
||
"decision": { "kind": "allow", "rule": "read-workspace" }
|
||
}
|
||
```
|
||
|
||
### 10.1 Binding the receipt to the rules
|
||
|
||
`policy_version` is an **installation counter**, not a content address. A
|
||
conforming engine SHOULD refuse a non-increasing install, which makes the
|
||
number identify a ruleset *within one engine instance*; across a restart, a
|
||
replica, or a different backend it does not, because each starts its own count.
|
||
`decision.rule` likewise names the rule that decided by **name**. A receipt
|
||
carrying only those is a pointer into state that only its producer holds: a
|
||
verifier must be told what engine-A's version 2 contained, by the party whose
|
||
decision is under audit.
|
||
|
||
A receipt therefore **MAY** carry `policy_digest`, a BLAKE3 hex digest of the
|
||
rules themselves, and an implementation that emits one **MUST** compute it as:
|
||
|
||
```
|
||
BLAKE3( "jouleclaw.policy/1" || rule_count(8, BE) || per rule:
|
||
len(name) || name
|
||
"allow" | "deny"
|
||
"agents:any" | "agents:one_of" || count(8, BE) || (len||id)* [ids SORTED]
|
||
"action:filesystem" || len(path_glob) || path_glob || count || (len||mode_tag)*
|
||
| "action:network" || len(host_glob) || host_glob || from(2,BE) || to(2,BE)
|
||
|| count || (len||method_tag)*
|
||
| "action:process" || len(binary_glob) || binary_glob )
|
||
```
|
||
|
||
Three rules make it a digest of the *policy* rather than of a serialization:
|
||
|
||
- **Rule order is included.** Evaluation scans `Deny` rules first and
|
||
short-circuits, so reordering can change the outcome: two orderings are two
|
||
policies.
|
||
- **The agent list is sorted before hashing.** `agents:one_of` is matched by
|
||
membership, so two policies differing only in that order are the same policy
|
||
and **MUST** share a digest.
|
||
- **A port range is hashed as written, not normalised.** A range whose `from`
|
||
exceeds its `to` matches nothing, which is a different policy from the same
|
||
pair the other way round.
|
||
|
||
`version` is **excluded** from the digest: the digest answers "which rules", the
|
||
version travels beside it answering "which installation", and folding the
|
||
counter in would make one ruleset hash differently per engine.
|
||
|
||
Every variable-length part is length-prefixed, so no two distinct policies can
|
||
share a preimage by moving a field boundary. The mode and method tags are
|
||
written out above rather than derived from a serializer, so the digest does not
|
||
move if a `serde` rename or a variant reordering ever lands.
|
||
|
||
The digest **MUST** be the one in force when the decision was made, not the
|
||
engine's current policy — the two differ across an install, and attributing a
|
||
decision to rules installed after it was made is the error the field exists to
|
||
prevent. Implementations SHOULD record it on the audit event at decision time.
|
||
|
||
When absent, the field **MUST** be omitted from the signed core entirely, not
|
||
emitted as a null or an empty string: a receipt without the binding then has a
|
||
signed core byte-identical to one produced before the field existed, which is
|
||
why the three `policy/1` vectors published before it are unchanged and
|
||
`policy/1` verifiers keep working. A consumer that requires the binding
|
||
**MUST** check for the field's presence rather than defaulting it.
|
||
|
||
- The canonical signed core is the det-CBOR (RFC 8949 §4.2.1
|
||
length-first map ordering) of the receipt as JSON.
|
||
- The COSE_Sign1 envelope is identical in shape to the JCR-1 v1
|
||
envelope on the cascade receipt: `protected = {1: -8, 4: steward}`,
|
||
`unprotected = {}`, `payload = canonical signed core`,
|
||
`signature = Ed25519(Sig_structure)`.
|
||
- Verifiers reject envelopes whose `kid` (in the protected header)
|
||
disagrees with the embedded `steward`.
|
||
- Conforming implementations **MUST** byte-match the four reference
|
||
vectors in
|
||
[`jouleclaw-rs/crates/jouleclaw-policy-receipt/conformance/vectors-policy.json`](jouleclaw-rs/crates/jouleclaw-policy-receipt/conformance/vectors-policy.json).
|
||
Reference verifiers in Rust, Python, and Go ship alongside the
|
||
vectors; all three produce byte-identical canonical bytes for the
|
||
same receipt. Vectors 1-3 omit `policy_digest` and keep the bytes they
|
||
shipped with; vector 4 is vector 1's decision with the binding, so the two
|
||
differ by exactly that key.
|
||
|
||
Policy receipts and cascade receipts share the same JSONL "unified
|
||
log" file format: readers dispatch on the `jc_receipt` schema tag
|
||
(`"1"` → cascade, `"policy/1"` → policy, unknown → forward-compat
|
||
skip).
|
||
|
||
---
|
||
|
||
## 11. The agent workspace
|
||
|
||
A persistent, content-addressed workspace **MAY** be attached to a
|
||
long-running agent. The workspace's wire form is a
|
||
`WorkspaceManifest`:
|
||
|
||
```json
|
||
{
|
||
"jc_workspace": "workspace/1",
|
||
"owner": { "handle": "<did:key>", "public_key": "<hex32>" },
|
||
"entries": [
|
||
{ "path": "notes/x.md", "content_hash": "<blake3 hex>", "size": 17, "kind": "text" },
|
||
...
|
||
]
|
||
}
|
||
```
|
||
|
||
- **Path normalisation** — paths are relative `/`-joined segments.
|
||
Absolute paths, empty paths, empty segments, `.` and `..`
|
||
segments, and NUL bytes are **MUST** rejected on write.
|
||
- **Content addressing** — `content_hash` is BLAKE3 hex of the
|
||
bytes; the underlying backend stores bytes keyed by
|
||
`content_hash` so identical content is deduplicated.
|
||
- **Entry order** — entries in the manifest are sorted lexically
|
||
by `path`.
|
||
- **Manifest content-address** — the manifest's own content hash is
|
||
BLAKE3 over its canonical JSON serialisation. Identical workspace
|
||
state → identical manifest hash → identical signature.
|
||
- **Signing** — a `SignedWorkspaceManifest` carries an Ed25519
|
||
signature over the canonical manifest bytes. Verifiers MUST
|
||
check both that the embedded `owner.handle` matches the embedded
|
||
`owner.public_key` under §8 derivation, AND that the signature
|
||
verifies.
|
||
|
||
The workspace deliberately does NOT specify encryption, conflict
|
||
resolution, or multi-writer merge. Backends are single-writer; if
|
||
multi-writer is needed it composes behind a future
|
||
`WorkspaceBackend` impl with a lock service. Encryption is the
|
||
caller's job — the manifest signature covers what is observable,
|
||
not what is decrypted.
|
||
|
||
---
|
||
|
||
## 12. Skill interoperability — `SKILL.md`
|
||
|
||
JouleClaw skills are portable to the broader open-skills ecosystem
|
||
(Claude Code, OpenAI Codex, Gemini CLI, GitHub Copilot, Cursor, et
|
||
al.) via the agentskills.io `SKILL.md` format. Conformance:
|
||
|
||
- A JouleClaw-emitted `SKILL.md` MUST carry the agentskills.io
|
||
required frontmatter (`name` ≤ 64 chars matching
|
||
`^[a-z0-9]([a-z0-9-]*[a-z0-9])?$`; `description` ≤ 1024 chars).
|
||
- The deterministic JouleClaw procedure (the `Template` and
|
||
`Procedure` that let a JouleClaw cascade resolve the skill at
|
||
lookup energy) travels under `metadata.jouleclaw` as a vendor
|
||
extension. Non-JouleClaw consumers treat the Markdown body as
|
||
natural-language instructions; JouleClaw-aware consumers
|
||
reconstruct the typed Skill.
|
||
- The `body` is required by emitters — empty bodies are rejected
|
||
unless the caller opts in explicitly via a `_with_default_body`
|
||
helper (intended only for drafts).
|
||
- Unknown vendor metadata keys are preserved on round-trip.
|
||
|
||
---
|
||
|
||
## 13. Vocabulary (JSON-LD + Turtle)
|
||
|
||
JouleClaw publishes a stable IRI vocabulary so a JSON-LD parser, a
|
||
SPARQL store, or a generic third-generation agent client can read
|
||
any JouleClaw receipt as RDF without bespoke integration.
|
||
|
||
- **Namespace:** `https://jouleclaw.transaction.science/vocab/v1#`
|
||
(prefix `jc:` by convention).
|
||
- **JSON-LD context:** `https://jouleclaw.transaction.science/vocab/v1.jsonld`
|
||
(every field name in §§2, 10, 11 maps to a `jc:`-prefixed IRI with
|
||
the typed range below).
|
||
- **Turtle ontology:** `https://jouleclaw.transaction.science/vocab/v1.ttl`
|
||
(classes, enumerations, properties, ranges; CC-BY-4.0).
|
||
- **Human index:** [`https://jouleclaw.transaction.science/vocab/v1`](https://jouleclaw.transaction.science/vocab/v1).
|
||
- **Canonical source:** [`jouleclaw/vocab/`](vocab/) in this
|
||
repository.
|
||
|
||
The vocabulary is **purely additive**:
|
||
|
||
- A v1 cascade or policy receipt remains a valid JouleClaw receipt
|
||
on the wire with or without an `@context` field.
|
||
- Adding `"@context": "https://jouleclaw.transaction.science/vocab/v1.jsonld"`
|
||
to a receipt expands it into typed RDF triples (e.g.
|
||
`tier: "L3"` becomes `<receipt> jc:tier jc:Model`,
|
||
`energy_provenance: "ModelBased"` becomes
|
||
`<receipt> jc:energyProvenance jc:ModelBased`) without changing
|
||
any other field.
|
||
- The canonical signed core of a JCR-1 envelope **MUST NOT** include
|
||
`@context` — the JSON-LD context is metadata, not payload.
|
||
Producers that want to publish a JSON-LD form alongside the signed
|
||
receipt SHOULD do so in a separate field, or fetch the context
|
||
out-of-band from the published URL.
|
||
|
||
**Production default.** Conformant producers `SHOULD` emit
|
||
`@context` set to the v1 vocabulary URL on every receipt by
|
||
default, so any downstream JSON-LD parser treats the document as
|
||
typed RDF without configuration. The reference implementations
|
||
do this in [`ReceiptBuilder::seal`][rb-seal] and
|
||
[`PolicyReceiptBuilder::build`][pb-build]; the `without_context()`
|
||
opt-out is reserved for conformance-vector regeneration and
|
||
backward-compatible wire-form replay.
|
||
|
||
[rb-seal]: jouleclaw-rs/crates/jouleclaw-prov/src/lib.rs
|
||
[pb-build]: jouleclaw-rs/crates/jouleclaw-policy-receipt/src/lib.rs
|
||
|
||
The vocabulary subclasses W3C PROV-O where the alignment is exact:
|
||
|
||
- `jc:ClaimProvenance &rsubeq; prov:Entity`
|
||
- `jc:AuditEvent &rsubeq; prov:Activity`
|
||
- `jc:AgentIdentity &rsubeq; prov:Agent`
|
||
|
||
PROV-O consumers can therefore reason about a JouleClaw deployment
|
||
as a provenance graph using this one IRI mapping — no
|
||
JouleClaw-specific code beyond the standard PROV-O semantics.
|
||
|
||
**Versioning.** A future major bump publishes under
|
||
`/vocab/v2#`. v1 IRIs remain resolvable forever per the
|
||
open-standards "names are final" rule.
|
||
|
||
---
|
||
|
||
## 14. Hydra ApiDocumentation
|
||
|
||
A JouleClaw service publishing a tool surface (MCP server, the
|
||
out-of-process policy daemon, an HTTP endpoint) **SHOULD** serve a
|
||
[Hydra Core Vocabulary][hydra] `hydra:ApiDocumentation` document
|
||
at a stable discovery endpoint. Hydra is the W3C CG draft that
|
||
solved the "tool schemas live in the client's context window"
|
||
pathology for the third-generation web: the API describes itself
|
||
in a machine-readable form, a generic client reads the description
|
||
once, and the client calls every operation without ever loading a
|
||
schema into its context.
|
||
|
||
A conforming ApiDocumentation:
|
||
|
||
- Is JSON-LD using both the Hydra Core Vocabulary namespace
|
||
(`http://www.w3.org/ns/hydra/core#`) and the JouleClaw vocabulary
|
||
namespace from §13 (`jc:` prefix).
|
||
- Carries `@type: "ApiDocumentation"` at the root, a stable
|
||
`@id`, a human-readable `title` + `description`, and a
|
||
`supportedClass` list.
|
||
- Each `Class` in `supportedClass` carries
|
||
`supportedOperation` — one `Operation` per published verb. An
|
||
`Operation` declares `method`, `expects` (input class IRI;
|
||
`owl:Nothing` for parameterless), `returns` (output class IRI),
|
||
and `statusCodes`.
|
||
- The JouleClaw extension fields `jc_tool_id` (stable tool id) and
|
||
`jc_mutating` (mirror of MCP's mutation flag) MAY appear on each
|
||
operation. They reference the JouleClaw vocabulary from §13 so a
|
||
receipt emitted by that tool cross-links back to its Hydra
|
||
description.
|
||
|
||
Default mappings the reference emitter uses:
|
||
|
||
| Source field | Hydra field | Default value |
|
||
|------------------------|-----------------------|------------------------------|
|
||
| `mutating: false` | `method` | `"GET"` |
|
||
| `mutating: true` | `method` | `"POST"` |
|
||
| (none) | `expects` | `owl:Nothing` |
|
||
| (none) | `returns` | `jc:ToolResponse` |
|
||
| (none) | `statusCodes` | 200, 400, 500 |
|
||
| `name` | `jc_tool_id` | `"jouleclaw:<name>"` |
|
||
|
||
The reference emitter is `jouleclaw-hydra::ApiDocumentationBuilder`
|
||
in the workspace; the `mcp-adapter` feature provides
|
||
`From<&McpToolSchema> for ToolDescription` so an existing MCP
|
||
server emits a Hydra doc in one chained call.
|
||
|
||
A generic third-generation agent client fetches the
|
||
ApiDocumentation, validates the `@context`, and is then free to
|
||
call every tool by following the typed contract — no
|
||
out-of-band tool prompts, no schema-per-tool context bloat.
|
||
**This is the structural fix for the generation-2 "72% of context
|
||
window on tool schemas" pathology.**
|
||
|
||
[hydra]: https://www.hydra-cg.com/spec/latest/core/
|
||
|
||
---
|
||
|
||
## 15. SHACL shapes
|
||
|
||
A JouleClaw service **SHOULD** publish [SHACL][shacl] Core shapes
|
||
for every input/output class its Hydra ApiDocumentation (§14)
|
||
references. The shapes let an agent validate a candidate payload
|
||
**before** calling a tool — locally, without any model
|
||
invocation, with deterministic results.
|
||
|
||
The canonical JouleClaw v1 shape graph lives at
|
||
[`https://jouleclaw.transaction.science/vocab/v1.shapes.ttl`](https://jouleclaw.transaction.science/vocab/v1.shapes.ttl)
|
||
and covers every class in §§2, 8, 10, 11 (Receipt, PolicyReceipt,
|
||
WorkspaceManifest, WorkspaceEntry, AgentIdentity, Action with the
|
||
three subclasses, Decision with Allow and Deny subclasses,
|
||
ToolTouch, ClaimProvenance). The shapes use only SHACL Core
|
||
constraints — no SPARQL, no qualified value shapes —
|
||
so:
|
||
|
||
- A full SHACL validator (pyshacl, Apache Jena) accepts the file
|
||
directly and produces a `sh:ValidationReport` against any
|
||
candidate document.
|
||
- A lightweight in-tree validator (the reference impl is
|
||
`jouleclaw-shacl::ShapeGraph::jouleclaw_v1()`) covers the same
|
||
constraints in ~600 lines of Rust with no SPARQL engine. Compiles
|
||
to WASM so an in-browser agent can validate locally.
|
||
|
||
Constraints in use: `sh:minCount` / `sh:maxCount`, `sh:datatype`
|
||
(xsd:string, xsd:integer, xsd:nonNegativeInteger, xsd:dateTime,
|
||
xsd:double, xsd:boolean), `sh:nodeKind` (sh:IRI, sh:Literal),
|
||
`sh:in` (enumerations), `sh:pattern` (regex), `sh:minInclusive` /
|
||
`sh:maxInclusive`, and `sh:node` (nested shape delegation).
|
||
|
||
The shape file is the contract a JouleClaw operator publishes
|
||
alongside their Hydra ApiDocumentation; the lightweight validator
|
||
is the agent-side counterpart. A tool input that fails validation
|
||
**MUST NOT** be sent to the tool.
|
||
|
||
[shacl]: https://www.w3.org/TR/shacl/
|
||
|
||
---
|
||
|
||
## 16. Validation contract
|
||
|
||
A **validation contract** is the planning artifact a multi-step
|
||
JouleClaw run is judged against. It is authored *before* any
|
||
implementation work begins, lives independently of the code, and
|
||
is the only thing that decides whether a mission succeeded.
|
||
|
||
### 16.1 Shape
|
||
|
||
A contract is a JSON document with the fields:
|
||
|
||
- `jc_contract`: `"contract/1"` (this version).
|
||
- `@context`: optional JSON-LD context, defaults to
|
||
`https://jouleclaw.transaction.science/vocab/v1.jsonld`.
|
||
- `title`: free-form mission name.
|
||
- `authored_by`: `did:key:…` of the agent or person who sealed
|
||
the contract. May be empty for unsigned drafts.
|
||
- `assertions`: an ordered list of [`Assertion`](#162-assertion).
|
||
Ids **MUST** be unique within the contract.
|
||
- `features`: an ordered list of [`Feature`](#163-feature). Ids
|
||
**MUST** be unique. Each feature's `claims` **MUST** reference
|
||
only assertion ids that exist in the contract.
|
||
- `acceptance`: optional metadata controlling ship-gate rules.
|
||
|
||
The contract's content hash is BLAKE3 over the canonical JSON
|
||
serialisation; identical contracts → identical hashes. The
|
||
content hash is what a [JCR-1 receipt](#2-the-receipt) binds to
|
||
when it claims to have run against this contract.
|
||
|
||
### 16.2 Assertion
|
||
|
||
Each assertion is one falsifiable claim:
|
||
|
||
- `id`: stable string (e.g., `"A-001"`).
|
||
- `title`: one-line summary.
|
||
- `rationale`: free-form *why*. Optional.
|
||
- `priority`: one of `must_have` | `should_have` | `nice_to_have`.
|
||
- `check`: one of:
|
||
- `{ "kind": "tool", "tool_id": "…", "args_hash": "…"? }`
|
||
- `{ "kind": "artifact", "path": "…", "expected_hash": "…"? }`
|
||
- `{ "kind": "command", "command_hash": "…", "expected_exit": N }`
|
||
- `{ "kind": "checker", "checker_id": "…" }`
|
||
|
||
A `must_have` assertion that does not roll up to `verified`
|
||
**MUST** block ship. A `should_have` assertion that does not
|
||
roll up to `verified` **MUST NOT** block ship unless
|
||
`acceptance.block_on_should_have` is true.
|
||
|
||
### 16.3 Feature
|
||
|
||
Each feature is one unit of work that *claims* one or more
|
||
assertion ids it intends to satisfy:
|
||
|
||
- `id`: stable string (e.g., `"F-001"`).
|
||
- `title` + optional `description`.
|
||
- `claims`: list of assertion ids this feature covers.
|
||
|
||
A worker dispatched against a feature **SHOULD** emit
|
||
[evidence](#164-evidence) for every assertion that feature
|
||
claims; the verifier surfaces missing evidence as a gap.
|
||
|
||
### 16.4 Evidence
|
||
|
||
The verifier consumes a bag of evidence items the run produced:
|
||
|
||
- `tool_result { tool_id, args_hash?, passed }`
|
||
- `artifact_present { path, hash }`
|
||
- `command_run { command_hash, exit_code }`
|
||
- `checker_result { checker_id, passed }`
|
||
|
||
An evidence item matches an assertion when its discriminants
|
||
match the assertion's check. A matching item with `passed: true`
|
||
(or, for `command`, `exit_code == expected_exit`; for `artifact`,
|
||
`hash == expected_hash` when supplied) raises the assertion to
|
||
`verified`. Any matching failure raises the assertion to
|
||
`failed`; the verifier surfaces the disagreement rather than
|
||
silently allowing a later success to mask it.
|
||
|
||
### 16.5 Verification
|
||
|
||
The verifier is a pure function over `(contract, evidence)`. It
|
||
emits a `ContractVerdict`:
|
||
|
||
- `jc_verdict`: `"verdict/1"`.
|
||
- `contract_hash`: hex BLAKE3 of the contract.
|
||
- `passed`: boolean.
|
||
- `summary`: human-readable one-liner.
|
||
- `coverage`: a typed matrix with one row per assertion, one
|
||
column per feature, plus a per-assertion rollup
|
||
(`verified` | `claimed_unverified` | `failed` | `gap`).
|
||
- `blocking_gaps`: must-have assertion ids not rolled up to
|
||
`verified`.
|
||
- `should_have_gaps`: should-have assertion ids not rolled up
|
||
to `verified`.
|
||
|
||
Same inputs → same verdict. The verdict is the artifact a
|
||
receipt links to when reporting a multi-day run's outcome.
|
||
|
||
### 16.6 Conformance
|
||
|
||
A conforming **producer** of a contract MUST:
|
||
|
||
- Set `jc_contract` to `"contract/1"`.
|
||
- Reject duplicate assertion ids and duplicate feature ids.
|
||
- Reject features whose `claims` reference unknown assertion ids.
|
||
|
||
A conforming **verifier** of a contract MUST:
|
||
|
||
- Implement the rollup rules in §16.5 exactly: any matching
|
||
failure → `failed`; otherwise any matching pass → `verified`;
|
||
otherwise any claim → `claimed_unverified`; otherwise → `gap`.
|
||
- Block ship on any `must_have` non-`verified` rollup.
|
||
- Block ship on `should_have` non-`verified` rollups iff
|
||
`acceptance.block_on_should_have` is true.
|
||
|
||
Reference: [`crates/jouleclaw-contract`](./jouleclaw-rs/crates/jouleclaw-contract/).
|
||
SHACL shape: `jc:ValidationContractShape` at
|
||
[`vocab/v1.shapes.ttl`](./vocab/v1.shapes.ttl).
|
||
|
||
### 16.7 Verdict receipt
|
||
|
||
The §16.5 verifier emits a [`ContractVerdict`](#165-verification)
|
||
as plain JSON. To land that verdict on the unified auditable log
|
||
alongside cascade receipts (`"1"`) and policy receipts
|
||
(`"policy/1"`), wrap it in a **verdict receipt** — sibling
|
||
schema `"verdict-receipt/1"`, same canonical det-CBOR +
|
||
COSE_Sign1 envelope every other JouleClaw sibling receipt uses,
|
||
under the same steward identity.
|
||
|
||
```json
|
||
{
|
||
"jc_receipt": "verdict-receipt/1",
|
||
"id": "<uuid v4>",
|
||
"closed_at": "<rfc 3339>",
|
||
"steward": "<did:key handle>",
|
||
"engine_id": "<id>",
|
||
"audit_sequence": 7,
|
||
"contract_hash": "<hex BLAKE3 of the contract>",
|
||
"verdict": { /* ContractVerdict, verbatim */ }
|
||
}
|
||
```
|
||
|
||
Wire-format rules:
|
||
|
||
- The canonical signed core carries the verdict as a
|
||
**JSON-string** field named `verdict_json` (not a nested
|
||
object), with a sibling `verdict_hash` field set to hex BLAKE3
|
||
of the `verdict_json` bytes. This keeps the signed core inside
|
||
the JCR-1 canonical-CBOR subset (which forbids bools and
|
||
nulls) while still binding the verdict to the signature. The
|
||
wire-form JSON object surfaced to consumers MAY carry
|
||
`verdict` as a nested object for ergonomics; the signed core
|
||
is normative.
|
||
- The COSE_Sign1 envelope is identical in shape to JCR-1 v1:
|
||
`protected = {1: -8, 4: steward}`, `unprotected = {}`,
|
||
`payload = canonical signed core`, `signature =
|
||
Ed25519(Sig_structure)`.
|
||
- `contract_hash` MUST equal the BLAKE3 content hash of the
|
||
[`ValidationContract`](#161-shape) the verdict was produced
|
||
against. Verifiers consuming the unified log MAY index by
|
||
`contract_hash` without parsing the inner verdict.
|
||
|
||
Conforming producers + verifiers SHOULD reuse the existing
|
||
JCR-1 envelope tooling (verifiers reject envelopes whose `kid`
|
||
disagrees with the embedded `steward`, etc.).
|
||
|
||
Reference: [`crates/jouleclaw-contract-receipt`](./jouleclaw-rs/crates/jouleclaw-contract-receipt/).
|
||
|
||
---
|
||
|
||
## 17. Worker handoff
|
||
|
||
A **worker handoff** is the structured wire form an agent emits
|
||
at end-of-feature-work. The handoff is what makes a multi-day
|
||
run readable to the next worker (or the next session of the same
|
||
worker) — it forces the agent to write down what actually
|
||
happened rather than relying on conversational context.
|
||
|
||
### 17.1 Shape
|
||
|
||
A handoff is recorded as a `RunEvent::HandoffRecorded` variant
|
||
on the [run journal](#2-the-receipt):
|
||
|
||
- `worker`: stable worker id (typically `did:key:…`).
|
||
- `node_id`: optional graph node this handoff closes.
|
||
- `feature_id`: optional contract feature id this handoff
|
||
claims to satisfy.
|
||
- `closed_at`: epoch-seconds wall-clock.
|
||
- `completed`: list of items reported done.
|
||
- `undone`: list of items reported left undone.
|
||
- `commands`: list of `{ command_hash, argv, exit_code }`
|
||
entries observed during the work window.
|
||
- `issues_found`: list of issues the worker discovered.
|
||
- `procedures_followed`: list of skills / runbooks the worker
|
||
applied.
|
||
|
||
`command_hash` MUST be hex BLAKE3 over `argv.join("\0")`. The
|
||
hash, not the argv array, is the command's identity; the argv
|
||
is carried for human inspection only.
|
||
|
||
The handoff hash is part of the journal entry's hash chain, so
|
||
editing handoff fields after the fact breaks chain verification.
|
||
|
||
### 17.2 Conformance
|
||
|
||
A conforming **producer** of a handoff MUST:
|
||
|
||
- Set every field that is non-empty in the run; empty lists are
|
||
acceptable but the discriminator MUST be present.
|
||
- Compute `command_hash` as specified.
|
||
|
||
A conforming **consumer** of a handoff MUST:
|
||
|
||
- Accept additive fields (the variant set is open under
|
||
semver-compatible extension).
|
||
- Verify the journal's hash chain before trusting the handoff's
|
||
contents.
|
||
|
||
Reference: [`crates/jouleclaw-graph`](./jouleclaw-rs/crates/jouleclaw-graph/)
|
||
(`WorkerHandoffEvent`, `HandoffCommand`,
|
||
`RunEvent::HandoffRecorded`). SHACL shape:
|
||
`jc:WorkerHandoffShape` at
|
||
[`vocab/v1.shapes.ttl`](./vocab/v1.shapes.ttl).
|
||
|
||
---
|
||
|
||
## 18. Mission orchestrator
|
||
|
||
The **mission** primitive composes [§16 contracts](#16-validation-contract)
|
||
+ [§17 handoffs](#17-worker-handoff) + the run journal into a
|
||
three-role pipeline: **Orchestrator** authors, **Worker** does,
|
||
**Validator** judges.
|
||
|
||
### 18.1 Roles
|
||
|
||
- The **Orchestrator** authors a `ValidationContract` and a
|
||
feature plan. It does NOT execute work; it composes the plan
|
||
and the validator's expectation in one artifact.
|
||
- A **Worker** is dispatched per feature. It performs whatever
|
||
work it does (opaque to this spec) and returns a
|
||
`WorkerOutcome { handoff, evidence }`. Workers MAY be backed
|
||
by any cascade tier — deterministic tools, models, or
|
||
humans-in-the-loop.
|
||
- The **Validator** runs the §16.5 verifier over the
|
||
accumulated evidence and emits a `ContractVerdict`. The
|
||
validator's only input is `(contract, evidence)` — pure data —
|
||
so its verdict is reproducible and reviewable.
|
||
|
||
### 18.2 Dispatch
|
||
|
||
Workers MUST be dispatched in feature order, serially, when the
|
||
features mutate shared state. Read-only fan-out (parallel
|
||
search, parallel verifiers) MAY be parallel; the implementation
|
||
is the caller's choice and the spec does not require it.
|
||
|
||
Each worker's handoff MUST be recorded on the run journal
|
||
before the next worker is dispatched, so the chain stays
|
||
chronological.
|
||
|
||
### 18.3 Strictness
|
||
|
||
A `strict_workers` mode MAY refuse worker outcomes that produce
|
||
no evidence for at least one assertion the worker's feature
|
||
claims, failing fast rather than letting the validator surface
|
||
the gap later. The default behaviour is non-strict (let the
|
||
validator rollup decide).
|
||
|
||
Reference: [`crates/jouleclaw-mission`](./jouleclaw-rs/crates/jouleclaw-mission/).
|
||
|
||
---
|
||
|
||
## 19. Value report
|
||
|
||
Where a JCR-1 receipt records *what happened* (the activity),
|
||
a **value report** records *what landed* against the agent's
|
||
declared intent. The two together close the activity-vs-value
|
||
gap: activity is easy to count; value is what the user paid for.
|
||
|
||
### 19.1 Shape
|
||
|
||
A value report is a JSON document:
|
||
|
||
- `intent`: free-form natural-language intent the agent was
|
||
acting on.
|
||
- `tool_id`: tool id the call addressed.
|
||
- `checks`: per-check verdicts, each `{ check, passed,
|
||
reason? }`.
|
||
- `closure_rate`: `passed_count / checks.len()` ∈ [0, 1].
|
||
`0.0` when `checks` is empty.
|
||
- `fully_closed`: true iff `checks` is non-empty AND every
|
||
check passed.
|
||
|
||
### 19.2 Check kinds
|
||
|
||
- `status { codes }` — status code MUST appear in `codes`.
|
||
- `status_matches_contract` — status MUST be in the operation's
|
||
declared `statusCodes` menu (the Hydra contract).
|
||
- `output_hash_equals { hash }` — canonical-JSON BLAKE3 of the
|
||
body MUST equal `hash`.
|
||
- `json_pointer_equals { pointer, expected }` — RFC 6901 pointer
|
||
MUST resolve to `expected`.
|
||
|
||
Implementations MAY define additional check kinds; consumers
|
||
MUST tolerate unknown kinds and treat them as `passed: false`
|
||
with a `reason` that names the unknown kind.
|
||
|
||
### 19.3 Aggregation
|
||
|
||
The mean closure rate across a set of value reports is the
|
||
system's honest "delivered / attempted" metric — denominated in
|
||
user intent rather than joules. Reports with no checks declared
|
||
MUST be skipped in the aggregate (they convey no value
|
||
information).
|
||
|
||
Reference: [`crates/jouleclaw-hydra-client`](./jouleclaw-rs/crates/jouleclaw-hydra-client/)
|
||
(`ValueReport`, `ValueCheck`, `ValueCheckResult`,
|
||
`mean_closure_rate`).
|
||
|
||
---
|
||
|
||
## 20. What this spec deliberately does not say
|
||
|
||
- **How to build the model.** That's the model-author's concern.
|
||
JouleClaw measures and dispatches; it doesn't train.
|
||
- **Which retrieval API to use.** Brave / Tavily / Exa / Serper all
|
||
plug in through the `SearchProvider` trait. Pick whichever your
|
||
deployment permits.
|
||
- **How to ship receipts.** The receipt is produced. Transport
|
||
(HTTP / lockstep / message queue) is the deployer's choice. Smart
|
||
Byte's signed-envelope replication is the recommended substrate.
|
||
- **A specific signing key topology.** Bring your own. Smart Byte's
|
||
KERI-based AID rotation is the recommended path.
|
||
|
||
---
|
||
|
||
## 21. Adjacent techniques
|
||
|
||
This section names the architectural pattern JouleClaw implements
|
||
and lists the research communities that have, separately, solved
|
||
parts of it. The intent is not novelty-claiming. It is to make
|
||
the assembly visible so an engineer arriving from any one of those
|
||
communities can locate their work inside the cascade.
|
||
|
||
### 9.1 The architectural pattern
|
||
|
||
An LLM inference call is a render pass. A token is a frame. A
|
||
reasoning chain is a sequence of frames. A context window is a
|
||
scene graph. An agent loop is an NPC control loop. RAG is texture
|
||
streaming. A model checkpoint is a level asset. The pipeline from
|
||
prompt to response is — structurally — exactly the work a game
|
||
engine does between input poll and presented frame.
|
||
|
||
The game-engine industry was forced to solve this problem under a
|
||
fixed 150–200 W power budget twenty years ago. The five JouleClaw
|
||
cascade tiers are direct translations of game-engine architectural
|
||
primitives:
|
||
|
||
| JouleClaw tier | Game-engine primitive | What the engine actually does |
|
||
|-----------------|--------------------------------|-------------------------------|
|
||
| `L0:Cache` | texture / asset cache | Content-addressed; cheap to check; valid until invalidated |
|
||
| `L1:Lawful` | pre-baked geometry, LUTs | The work the engine *refuses to run* because the answer is already in the format |
|
||
| `L2:Embed` | BVH / spatial query | Find the closest match; do not compute from first principles |
|
||
| `L3:Model` | procedural / runtime synthesis | Invoked only when nothing baked or streamed will do |
|
||
| `L4:Wire` | server-streamed asset / CDN | The escape hatch when the local box cannot satisfy |
|
||
|
||
Each game-engine technique listed below has a near-exact
|
||
counterpart in modern AI inference that the AI industry has, so
|
||
far, declined to deploy at the same architectural rigor:
|
||
|
||
Every lever below now has a **shipped** JouleClaw realization — the
|
||
frame is no longer rhetorical; each row points at in-tree code:
|
||
|
||
| Game-engine technique | AI counterpart | JouleClaw component (shipped) |
|
||
|--------------------------------|-------------------------------------------------------------|---------------------|
|
||
| Frustum culling | Skip tiers whose `estimate_cost` returns `None` | `jouleclaw-cascade::Tier::estimate_cost` |
|
||
| Level-of-detail (LOD) | Route to the cheapest sufficient model — SLM before frontier | `jouleclaw-dispatch::ModelBankTier` (cheapest *capable* slot by measured J) |
|
||
| Deferred rendering | Do not forward-pass through 175B parameters for a token a 7B would produce identically | `jouleclaw-dispatch` cost-table over backends declaring their `J/op` |
|
||
| Physically-based rendering | One foundation model, derive specializations — not 12 full fine-tunes | (consumer concern; the spec doesn't dictate) |
|
||
| Virtual texturing | Retrieve only the context this token will attend to, not the whole 128k window | `jouleclaw-stack` RAG pipeline (`retrieve → enrich → rerank → read`, top-k only) + `jouleclaw-fresh` |
|
||
| Temporal upsampling | Reuse prior verified work; reconstruct from a small delta, not by re-running | `jouleclaw-promote` (exact yang→yin) + `jouleclaw-skill` (whole query class) |
|
||
| Nanite-style mesh streaming | Activate only the reasoner the input actually needs | `jouleclaw-dispatch` routes to the one slot, not the whole bank |
|
||
| Hardware ray tracing + denoiser | A few high-quality forward passes plus a verifier beats one large unconstrained pass | `jouleclaw-verify` (`OutputVerifier`) + `jouleclaw-approve` gate before the pass |
|
||
| Agent / NPC control loop | Bounded multi-step recursion — fan-out capped, depth bounded by the joule budget | `jouleclaw-agent` (`max_steps` + per-branch budget clamp) |
|
||
| Frame budget | `JouleBudget` walks the cascade like a render budget walks the scene graph | `jouleclaw-energy::EnergyQuota` + breaker, `jouleclaw-governor` (global), `jouleclaw-agent` (nested) |
|
||
|
||
The pattern is not new. The assembly — and the fact that every lever is
|
||
a shipped, tested crate rather than a slogan — is.
|
||
|
||
### 9.2 Five communities, one architecture
|
||
|
||
Five research communities currently hold one face each of this
|
||
architecture. They publish at different venues and do not, by and
|
||
large, talk to one another. A conforming JouleClaw runtime composes
|
||
all five of their primitives — that composition is the standard's
|
||
contribution.
|
||
|
||
| Community | Representative work | JouleClaw component |
|
||
|---|---|---|
|
||
| **Constrained decoding** | Outlines (Willard & Louf), XGrammar (Dong et al. 2024), Guidance, LMQL, Pre³, OpenAI/Anthropic structured-output APIs | `jouleclaw-decode` *(shipped)* — pure-Rust regex / CFG / JSON-schema → token-vocabulary mask |
|
||
| **Compound AI systems / DSPy** | DSPy (Khattab et al., Stanford → MIT; CAIS 2026 inaugural ACM conference, May 2026) | `jouleclaw-program` *(shipped)* — typed signatures + module compiler — and `jouleclaw-skill`, its deterministic skill-compilation realization |
|
||
| **Small / specialist models** | NVIDIA Research (Belcak & Heinrich, 2025), Microsoft Phi team, TinyML, llama.cpp | `jouleclaw-cascade` router + `jouleclaw-pack` declared-cost contracts |
|
||
| **Neurosymbolic / verifier-in-the-loop** | Lean + LLM provers, BMC + LLM (LaM4Inv), SymCode, ProofNet++, VIRF, Eidoku | `jouleclaw-verify` (`OutputVerifier` trait + `VerifierChain` + receipt integration) |
|
||
| **Energy measurement / Green Software** | Patterson et al., ML.ENERGY Leaderboard, MDPI Sensors edge-computing surveys, GSF SCI specification, AI Energy Score | `jouleclaw-energy` + the `Provenance` honesty contract + `.jc.toml` measured-cost rows |
|
||
|
||
A conforming runtime **SHOULD** be able to point at concrete
|
||
in-tree code or a registered adapter for each of the five primitives
|
||
above. A runtime that solves only one face is a research project,
|
||
not a JouleClaw implementation.
|
||
|
||
### 9.3 The three-lever ceiling
|
||
|
||
Energy reduction from a fully-naive monolithic baseline (~10¹⁸
|
||
above the Landauer floor for current generative inference per
|
||
output bit) composes multiplicatively across three independent
|
||
levers:
|
||
|
||
| Lever | Reduces by | What does it |
|
||
|-------|-----------|---------------|
|
||
| **Route to the right cell** | 10⁸–10¹⁰× | The cascade itself — `L0 / L1 / L2` close most queries without invoking a model |
|
||
| **Cache the prior work** | 10²–10⁴× | Content-addressed cache (`L0`), prior-resolution history, MRL embeddings (`L2`) |
|
||
| **Align silicon to primitive** | 10²–10⁴× | The `.jc.toml` declared-cost contract is the input data hardware co-design needs |
|
||
|
||
Stacked total: **10¹²–10¹⁸×** below the naive baseline. The "90 %
|
||
reduction" figure quoted on the marketing surface is the
|
||
conservative floor of what the three levers permit when composed
|
||
honestly.
|
||
|
||
The ceiling is not the Landauer bound — reaching the bound requires
|
||
reversible logic and zero-impedance interconnect that nobody can
|
||
engineer today. The ceiling is how disciplined the routing and the
|
||
hardware choices are.
|
||
|
||
### 9.4 The receipt is the proof, not the marketing
|
||
|
||
A runtime that produces lower receipts than the conformance vectors
|
||
demand without the per-tool-touch `Provenance` to back them is
|
||
non-conformant. JouleClaw refuses the receipts-as-marketing failure
|
||
mode that has accumulated around carbon offsets and software
|
||
sustainability claims more broadly. Every `joules_uj` figure in a
|
||
conforming receipt must trace back to either a real hardware
|
||
counter (`Provenance::HwShunt`), a documented vendor model
|
||
(`Provenance::ModelBased`), or a calibrated static estimator
|
||
(`Provenance::Estimator`) with a wider tolerance band.
|
||
|
||
A hardware counter here means one that was live for the domain the
|
||
figure claims (§1.3, Liveness). A counter that is present but not
|
||
live — frozen, or republished in lumps — is not a hardware reading
|
||
however real the silicon behind it: its span is the estimator's, or
|
||
the work is reported unmetered, and the receipt says so. Grading a
|
||
frozen counter's figure `HwShunt` is the receipts-as-marketing
|
||
failure mode in its most literal form.
|
||
|
||
The breaker enforces at the granularity of the worst counter in
|
||
the span. This is normative.
|
||
|
||
---
|
||
|
||
## 22. Transparency log (OPTIONAL)
|
||
|
||
A receipt proves what a runtime decided. It does not prove that the set of
|
||
receipts you were shown is the set that exists: a stream held privately by the
|
||
party that produced it can be pruned, re-ordered, or served differently to two
|
||
auditors, and nothing in §2 or §10 detects that. A conforming runtime **MAY**
|
||
publish its receipts into an append-only Merkle log, which closes the part of
|
||
that gap a log can close.
|
||
|
||
### 22.1 Tree
|
||
|
||
The log is an RFC 6962 Merkle tree over receipt bytes, with BLAKE3 in place of
|
||
SHA-256 — JouleClaw is BLAKE3-native — and the RFC's domain separators
|
||
preserved so a BLAKE3-shimmed CT verifier can use the same proof shapes:
|
||
|
||
- leaf hash: `BLAKE3(0x00 || receipt_bytes)`
|
||
- internal node: `BLAKE3(0x01 || left || right)`
|
||
- a tree of size `N` is built by the RFC 6962 method: the root is the node hash
|
||
of the largest power-of-two subtree on the left and the recursive tree of the
|
||
remainder on the right.
|
||
|
||
The leaf is the receipt's own bytes, so an auditor checks a proof against the
|
||
receipt it holds rather than against a digest the operator computed for it.
|
||
|
||
### 22.2 Signed tree head
|
||
|
||
A **tree head** is `(size, root, timestamp)`. A head that crosses a trust
|
||
boundary **MUST** be signed, and the signature **MUST** cover all three fields
|
||
over exactly these bytes:
|
||
|
||
```
|
||
0x02 || size (8 bytes, big-endian) || root (32 raw bytes) || timestamp (8 bytes, big-endian)
|
||
```
|
||
|
||
`0x02` is a third domain separator, distinct from the leaf and node prefixes, so
|
||
a tree head cannot be presented as an entry in its own log. Every field is
|
||
fixed-width, so no two distinct heads share a preimage.
|
||
|
||
The key **MUST** be the same principal that seals the receipts (§8): a head
|
||
signed by anyone else commits a party that did not make the decisions.
|
||
|
||
An unsigned root **MUST NOT** be used to verify a proof. This is the whole
|
||
premise — an inclusion proof against an unsigned root proves only that the
|
||
operator can produce a consistent story on demand, which it always can. A
|
||
signed root makes two different roots at one size **attributable
|
||
equivocation**: both carry the operator's signature, and the pair is evidence
|
||
against one key.
|
||
|
||
A verifier **MUST** reject a head whose root is not 32 bytes of hex rather than
|
||
treating it as absent or empty.
|
||
|
||
### 22.3 What the log proves, and what it does not
|
||
|
||
A runtime that publishes this log **SHOULD** state the boundary plainly, because
|
||
the two halves are easy to conflate:
|
||
|
||
- **Proven.** A given receipt is in the tree a given signed head commits to; the
|
||
log has only grown between two signed heads (consistency proof); a receipt
|
||
committed to under a published head cannot afterwards be removed or rewritten
|
||
without equivocating under the operator's key.
|
||
- **Not proven.** *Completeness.* An operator that never records a decision logs
|
||
nothing, and no proof about the entries that exist can speak for one that was
|
||
never written.
|
||
|
||
What narrows the gap is **where** the append happens. The entry **SHOULD** be
|
||
written on the same code path that produces the verdict, before the verdict is
|
||
returned to the caller. A caller that observed a refusal can then demand the
|
||
proof of the entry that refused it, and an implementation that cannot record a
|
||
decision **MUST** refuse the action rather than perform it unrecorded — an
|
||
unrecordable allow is the one outcome the log exists to make impossible.
|
||
|
||
Conformance class **C-TLOG**: a runtime claiming it signs its head over all
|
||
three fields, refuses an unparseable root, rejects a head signed by another key,
|
||
and serves an inclusion proof that closes against the signed root for every
|
||
entry — allow and deny alike.
|
||
|
||
---
|
||
|
||
## 23. References
|
||
|
||
- [JouleClaw Charter](../CHARTER.md) — stewardship pattern (shared
|
||
across all five open standards)
|
||
- [Smart Byte open standard](../smart-byte/) — signed envelopes
|
||
- [EOC open standard](../eoc/) — the four-stage memoising cascade
|
||
pattern JouleClaw embodies
|
||
- [WAI open standard](../wai/) — media transport + capability
|
||
dispatch
|
||
- [OpenPay open standard](../openpay/) — the typestate-enforced
|
||
lifecycle pattern adopted by JouleClaw's cascade
|
||
- W3C PROV-O — provenance vocabulary
|
||
- RFC 9449 — DPoP (sender-constrained tokens, recommended for
|
||
ClaimProvenance envelopes that carry a fetch credential)
|
||
- Landauer, R. (1961). "Irreversibility and Heat Generation in the
|
||
Computing Process." The thermodynamic floor under everything in
|
||
this spec.
|