jcp-energy: meter a closure's CPU work with the per-process energy counter, which macOS 27 keeps live #122
3 changed files with 1280 additions and 52 deletions
|
|
@ -45,7 +45,7 @@ jcp/
|
|||
- **`jcp-trace`** — ingests an agent run's **OpenTelemetry GenAI trace** (`gen_ai.*` semantic conventions: `chat` / `tool` / `retriever` spans with token usage) and its **evaluation assessments** (judge / human / code feedback), attaches **per-span joules** (measured where a meter is present, else `Estimator` from token counts — provenance never silently upgrades), and seals one **`eval/1` JCR-1 receipt**: the per-span token + joule breakdown with the headline `uj_per_accepted_token` at the **weakest** provenance present, the assessments, a deterministic **quality-gate** verdict (the sealed, declarative analog of a CI assert), and a **readiness projection** an ARL validation-axis claim consumes. The observability stacks *record* a trace and *log* a judge score in a table; this is the interop projection that **seals** the same trace, puts a joule on it, and hash-chains it via `EvalLog` — no new cryptography. The **gate is content-addressed**, so the sealed receipt commits to exactly which bar it was judged against (a tamper-evident artifact, not a mutable in-code `assert`), and **`align_threshold` / `Judge`** calibrate a judge's decision threshold to human pass/fail labels — judge alignment as a reproducible number, not a UI click. Behind the `server` feature, a std-only **OTLP `/v1/traces` receiver** lets any OpenTelemetry exporter POST a `gen_ai.*` trace (with its assessments) and get the sealed `eval/1` receipt back the instant the run ends.
|
||||
- **`jcp-intercept`** — **attested interceptor chains**: a composable, ordered pipeline of transforms *around* each model and tool call — the `before_model` / `after_model` / `before_tool` seams an agent's middleware stack runs, composed onion-order (forward in, reverse out) and stopping at the first refusal or human-approval hold. An interceptor may inspect or **rewrite** the request (redact a secret before it reaches the model, trim stale context), **refuse** it, or **hold** it for a human. The difference from the incumbent's arbitrary in-process hooks: every step seals an **`intercept/1`** JCR-1 receipt carrying the request digest **before and after** the step (so a redaction or a trim is provable — the content itself never sealed, only its hash), the seam, the outcome, and the **metered joules** at their honest provenance, hash-chained — the exact transform pipeline that produced a model request becomes reconstructable and verifiable by anyone, and a step cannot run un-sealed. Three reference interceptors — `Redact` (a content transform), `CallLimit` (a guardrail), `Approve` (a human-in-the-loop hold) — each reuse a primitive the family already holds rather than reinventing it.
|
||||
- **`jcp-efficiency`** — the **energy-efficiency spec**, surfaced as a sealed receipt. The dark-factory economics turn on four figures — **joules per accepted token, tokens per second, value per token, and the tokens/joules/dollars spent per COMPLETED, VERIFIED unit of work** — and the incumbents publish, at best, a *rate* (cost per token) that can't tell you whether the spend bought a result. An **`efficiency/1`** JCR-1 receipt records all four at honest provenance, hash-chained, with the one thing the rates omit: the **outcome anchor** (`completed` + a verdict). A measurement that didn't complete is spend that bought nothing, and the receipt says so. `aggregate` then splits value-delivered from total spend across a chain — charging *every* attempt (including the failed ones) against the results actually delivered — so `tokens_per_completed_unit` / `uj_per_completed_unit` / `value_per_joule` become the honest efficiency a "cost per token" number is designed to hide. Integers and strings only (JCR-1 forbids bools/floats); the rate figures recompute from the sealed inputs on read.
|
||||
- **`jcp-energy`** — the metering ladder (SPEC §5.1): RAPL package counters (Linux powercap, wraparound-corrected, `HwShunt`, only while every package counter is live — `rapl_status()`), NVML GPU energy behind the `nvml` feature (`HwShunt` and `Partial`, since it does not read the CPU's share; only while live — `nvml_status()`), Apple Silicon IOReport (`HwShunt`, only while every CPU energy rail, each die's on a multi-die part, is live — on macOS 27.0.1 that rail is republished in lumps, so there the rung is declined and `ioreport_status()` says why), a time×calibrated-power model (`ModelBased`, always available), and a constant per-op estimator (`Estimator`). A vendor counter's reading is real but vendor-opaque and relayed by the platform, and it counts as a reading only while the counter is live (SPEC §5). Measured when the platform exposes it, estimated when it doesn't — and the provenance always says which; a hardware backend that fails mid-reading degrades honestly to the model.
|
||||
- **`jcp-energy`** — the metering ladder (SPEC §5.1): RAPL package counters (Linux powercap, wraparound-corrected, `HwShunt`, only while every package counter is live — `rapl_status()`), NVML GPU energy behind the `nvml` feature (`HwShunt` and `Partial`, since it does not read the CPU's share; only while live — `nvml_status()`), the macOS kernel's per-process CPU energy count (`proc_pid_rusage`'s `ri_energy_nj`; `HwShunt`, whole for in-process CPU work, only while live — `process_energy_status()`; it covers this process's threads, not children, the GPU or DRAM, so a span that reaps a child process or ends with one is `Partial`; it is live on macOS 27.0.1 where IOReport's CPU rails are not), Apple Silicon IOReport (`HwShunt`, only while every CPU energy rail, each die's on a multi-die part, is live — on macOS 27.0.1 that rail is republished in lumps, so there the rung is declined and `ioreport_status()` says why; `Meter::detect_explained()` names the rung selected and why each one above it was declined), a time×calibrated-power model (`ModelBased`, always available), and a constant per-op estimator (`Estimator`). A vendor counter's reading is real but vendor-opaque and relayed by the platform, and it counts as a reading only while the counter is live (SPEC §5). Measured when the platform exposes it, estimated when it doesn't — and the provenance always says which; a hardware backend that fails mid-reading degrades honestly to the model.
|
||||
- **`jcp-gateway`** — the **energy-native MCP gateway**: a JCP enforcement point that sits in front of an MCP server so an existing client/server pair keeps its transport while every `tools/call` becomes capability-scoped, joule-budgeted, and sealed. The novel mechanic is two-phase metered enforcement: the call is *authorized* before it runs (no debit, because the cost isn't yet known), the upstream call is **metered** by the sibling Sandbox standard's energy probe (Apple Silicon IOReport, RAPL, NVML — vendor energy counters, whose readings are real but vendor-opaque and relayed by the platform, and which count as readings only while the counter is live for the work, SPEC §5; otherwise the figure is an estimate and is graded as one), and the metered joules are debited from the grant — so the budget that stops a runaway agent is denominated in joules, not a token estimate. Every decision, allow and deny, seals into a **hash-chained receipt log**: excise, reorder, or splice a receipt and the chain breaks at that point. A denied call never reaches the server (the live 2025–26 MCP exploits land on tool *output*, which the gateway frames as untrusted `data` that can never exercise a capability), and the `jcp-gateway` binary runs it over stdio in front of any MCP-server subprocess. This is the chokepoint the AI-gateway field occupies, on the two axes nobody there holds: the unit of account is the joule, and the audit is cryptographic and tamper-evident rather than a dollar figure and a span.
|
||||
- **`jcp-wire`** — the `jcp/1` wire binding (SPEC §11): 4-byte length prefix + canonical det-CBOR over any byte stream, with TCP and Unix-socket endpoints and a client. The decoder rejects non-canonical bodies, so a message has exactly one wire form. Requests carry the full delegation chain and are **caller-signed** — the endpoint authenticates from the request itself and denies an impersonation `bad-signature`, sealed like any other decision. Responses carry the deterministic decision and the sealed receipt.
|
||||
- **`jcp-threshold`** — threshold actors (SPEC §2.2): a `did:key` whose private key is split k-of-n with FROST (RFC 9591); the group key is an ordinary Ed25519 key, so a 2-of-3 team issues, holds, and signs wire requests with nothing else in JCP changing. Shares come from a trusted dealer or **distributed key generation** — with DKG the group's private key never exists anywhere, not even momentarily. The two-agent rule for irreversible actions, refused by arithmetic rather than policy.
|
||||
|
|
|
|||
24
jcp/SPEC.md
24
jcp/SPEC.md
|
|
@ -405,7 +405,8 @@ MUST NOT claim coverage of a domain its counter does not read; where the format
|
|||
has a coverage field, such a figure is Partial.
|
||||
|
||||
The record of why is the implementation's to choose; the reference meter
|
||||
exposes the IOReport liveness status it was selected or declined on. Coverage
|
||||
exposes each counter's liveness status, and which rung it selected with the
|
||||
status of every rung above it that it declined. Coverage
|
||||
is defined in §5.2.
|
||||
|
||||
### 5.1 The metering ladder (REQUIRED behavior, RECOMMENDED backends)
|
||||
|
|
@ -432,18 +433,31 @@ Recommended ladder, best first:
|
|||
counter is live by the same rule; the probe cannot load the GPU, so for
|
||||
this counter live means published continuously, advancing on the device's
|
||||
own draw. A span over which it did not advance is metered by a lower rung.
|
||||
3. **Apple IOReport** (the Energy Model group) — the SoC energy counters
|
||||
3. **Per-process CPU energy** (macOS `proc_pid_rusage`, `ri_energy_nj`) —
|
||||
the kernel's count of the CPU energy of the metering process's own
|
||||
threads → `HwShunt`, `Whole` for work that runs on those threads.
|
||||
Selected only while the count is live by the rule above. Its domain is
|
||||
that process's threads alone: not other processes, not its child
|
||||
processes, not the GPU, the Neural Engine or DRAM. A span over which the
|
||||
process reaped a child (its reaped children's CPU time moved), or at whose
|
||||
end it has a child, running or not yet reaped, is `Partial` (§5.2): the
|
||||
child's energy is not in the count. It ranks above the
|
||||
SoC-wide counter because it attributes the work's own CPU energy rather
|
||||
than the machine's. A span over which it did not advance is metered by a
|
||||
lower rung.
|
||||
4. **Apple IOReport** (the Energy Model group) — the SoC energy counters
|
||||
→ `HwShunt`, selected only while every CPU energy rail (one per die on a
|
||||
multi-die part) is live by the rule above. A span over which that rail did not advance is metered by the next
|
||||
multi-die part) is live by the rule above. A span over which no CPU rail
|
||||
advanced is metered by the next
|
||||
rung, never graded `HwShunt`. Where the CPU rail is not live but the GPU rail
|
||||
is, the rung is declined rather than reporting the GPU rail as a `Partial`
|
||||
reading of the work: the GPU rail is machine-wide (display compositing
|
||||
moves it), so booking it to a task misattributes energy the task did not
|
||||
spend, and the partial figure would under-debit the budget, the CPU's share
|
||||
going unmetered. The whole-task estimate below is the honest figure.
|
||||
4. **Time × calibrated power** — wall-clock duration times a host-calibrated
|
||||
5. **Time × calibrated power** — wall-clock duration times a host-calibrated
|
||||
active power → `ModelBased`. Always available.
|
||||
5. **Constant per-operation estimate** → `Estimator`.
|
||||
6. **Constant per-operation estimate** → `Estimator`.
|
||||
|
||||
### 5.2 Coverage (REQUIRED where a figure is partial)
|
||||
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue