Skip to content
21 changes: 21 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,27 @@ the run bundle; the trace stores only the matched rule id and primitive names. B
and text observation requires a Chrome/Chromium CDP session in the desktop. For deterministic
browser-observed stops, set `execution.desktop.browser: chrome` or `chromium`.

**Cost tracking (estimated).** Computer-use run bundles carry an advisory `cost` block: a
per-lane token-derived model estimate plus one aggregate E2B desktop-minute estimate. Every
dollar figure is an ESTIMATE, never a provider charge — it is a rate-table multiply, always
surfaced as "~$X estimated (rates as of `<date>`)" in the Observer and the run library, and it
carries the pricing date + source so a token-derived number is never mistaken for an
authoritative bill. Unknown model/rate is declared absent (`null` + a reason), never guessed or
silently zeroed; dry-runs invent no spend. The rates live in
[`src/pricing.ts`](src/pricing.ts) as **operator-editable, dated estimates** — some are
`placeholder` stand-ins (the `gpt-5.5` model rate and the E2B desktop rate); update the numbers
AND the `asOf` date when providers change pricing.

**Fail-closed spend cap.** Set `execution.caps.maxUsd` on a computer-use lab to abort a session
the moment its running estimated spend crosses the cap — the runaway-retry guard (mirrors the
terminal lane's `scenario.caps.maxUsd`). It is a **per-lane** cap: enforced inside each lane's loop,
so an N-lane fan-out can spend up to N × `maxUsd` before any lane aborts (the run bundle warns with
the true ~N × cap ceiling; a shared run-level budget is future work). A lane that did real work then
hits its cap passes (`budget_reached`); a zero-action runaway that crosses it fails (`gave_up`).
Absent = uncapped (the historical CUA behavior); `maxUsd: 0` = no-spend. A cap on a model
`src/pricing.ts` cannot price is refused at preflight (`HUMANISH_CUA_LAB_UNPRICED_CAP`) rather than
run uncapped — an unenforceable cap is more dangerous than none, so add a rate or drop the cap.

**Failed-lane reruns.** Multi-lane CUA fan-out can be rerun surgically without mutating
the source run:

Expand Down
12 changes: 12 additions & 0 deletions docs/architecture/actor-contract.md
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,7 @@ export interface ActorTrace {
counts: Record<string, number>;
items: ActorTraceItem[];
tokenUsage?: { input?: number; output?: number; total?: number; costUsd?: number };
estimatedCost?: ActorEstimatedCost; // humanish.actor-estimated-cost.v1 (additive)
capabilities: ActorCapabilities;
}

Expand Down Expand Up @@ -232,6 +233,17 @@ export interface Actor {
target URLs, or unredacted provider payloads in the trace.
- **Capabilities.** Declare them honestly; the registry uses them to refuse
unsuitable dispatch.
- **Cost (estimate vs. charge).** `tokenUsage.costUsd` stays RESERVED for a
real, provider-returned charge (the codex/agent-SDK path) — a bare `costUsd`
always means "the provider billed this". The optional `estimatedCost`
(`humanish.actor-estimated-cost.v1`) is a SEPARATE, differently-named field: a
token-derived rate-table multiply from the operator-editable `src/pricing.ts`,
labeled honestly as an estimate and projected up into `RunBundle.cost` (see
[`../contracts/schemas.md`](../contracts/schemas.md) → Run Cost Summary And
Estimated Actor Cost). The CUA lab computes and attaches `estimatedCost` at the
lab boundary before persisting the trace, so the pure computer-use loop never
depends on the pricing table. An unknown model yields
`estimatedCostUsd: null` + a `reason`, never a guessed charge.

## The scripted-browser lane (shipped)

Expand Down
23 changes: 23 additions & 0 deletions docs/contracts/run-bundle.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,29 @@ instead (a dirty working tree cannot be commit-pinned), and `app-url` carries
no code pin at all. No path, basename, or other host-machine string ever
enters this field; identity is digests, a sha, a boolean, and counts.

## Cost Estimate (advisory)

`cost` is optional and additive (`humanish.run-cost-summary.v1`): the
computer-use lane's run-level cost ESTIMATE — the sum of each lane's
token-derived model cost plus one aggregate E2B desktop-minute figure. It is an
ESTIMATE, never authoritative: every dollar is a rate-table multiply from the
operator-editable `src/pricing.ts`, carries the pricing `ratesAsOf` date and
`source`, and is surfaced with the "estimated (rates as of `<date>`)" label —
never a bare charge. It follows the same **declared-absent** discipline as the
terminal cost ledger: an unpriceable line stays present with
`estimatedCostUsd: null` + a `reason` and contributes nothing;
`estimatedTotalUsd` is `null` iff every line is null (never coerced to `0`).
Dry-runs and lanes that spend nothing omit `cost` entirely, so pre-existing
bundles stay byte-stable. Each lane's own estimate also rides its
`stream.actor.estimatedCost` (`humanish.actor-estimated-cost.v1`), kept distinct
from the reserved provider-returned `tokenUsage.costUsd`. See
[`schemas.md`](schemas.md) → Run Cost Summary And Estimated Actor Cost.

`humanish verify` treats cost as ADVISORY on magnitude and FAIL-CLOSED on
labeling: absence passes, but a claimed dollar figure without its `ratesAsOf`
date + `source`, or a total that does not match its known lines, fails. Verify
never inspects the magnitude — a correctly-labeled large estimate still passes.

## Adapter Score

`adapterScore` is optional and namespaced. It lets a downstream adapter summarize
Expand Down
91 changes: 89 additions & 2 deletions docs/contracts/schemas.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
Date: 2026-06-02 (current-state note updated 2026-07-14)

Status: reference map for the major contracts shipped through source version
`0.18.0`; it is not an exhaustive inventory of command/result envelopes. Exported types,
`0.19.0`; it is not an exhaustive inventory of command/result envelopes. Exported types,
schema constants, parsers, and validators in `src/` are authoritative. Rows
marked "reserved" name layering intent only — no code emits or validates them
yet. Do not emit a reserved schema.
Expand Down Expand Up @@ -43,6 +43,9 @@ workflow without leaking private upstream truth into core.
| Feedback | `humanish.feedback.v1` | `public-safe-feedback` |
| Terminal cost ledger | `humanish.terminal-cost-ledger.v1` | see Terminal Cost Ledger below |
| Terminal no-spend proof | `humanish.terminal-no-spend-proof.v1` | see Terminal Cost Ledger below |
| Pricing (operator-editable rates) | `humanish.pricing.v1` (`src/pricing.ts`; dated per-model + E2B desktop rates) | see Run Cost Summary And Estimated Actor Cost below |
| Run cost summary | `humanish.run-cost-summary.v1` (additive `RunBundle.cost`; estimate, never a charge) | see Run Cost Summary And Estimated Actor Cost below |
| Estimated actor cost | `humanish.actor-estimated-cost.v1` (additive `ActorTrace.estimatedCost`) | see Run Cost Summary And Estimated Actor Cost below |
| Adapter score | `humanish.adapter-score.v1` (`RunBundle.adapterScore`; namespaced; route-specific acceptance semantics) | see Product-Adapter Extension Seam below |
| Adapter artifact | `humanish.adapter-artifact.v1` (`RunBundle.adapterArtifacts[]`; namespaced; local relative proof references) | see Product-Adapter Extension Seam below |
| Shared-world evidence | `humanish.shared-world.v1` (additive `RunBundle.sharedWorld` + `RunBundle.attributionClass`; `topologyMode: sequential \| concurrent`) | see Shared-World Evidence below |
Expand Down Expand Up @@ -607,6 +610,14 @@ Core-owned fields:
distinct from `timed_out`, which stays reserved for a zero-progress deadline
hit and remains a failure)
- `ids`, `counts`, `items[]`, optional `tokenUsage`, `capabilities`
- optional `estimatedCost` (`humanish.actor-estimated-cost.v1`): a token-derived
cost ESTIMATE for this lane (see Run Cost Summary And Estimated Actor Cost).
It is deliberately a DIFFERENT field from `tokenUsage.costUsd`: a bare
`costUsd` is RESERVED for a real provider-returned charge, while
`estimatedCost.estimatedCostUsd` is a rate-table multiply, named honestly as
an estimate so a reader can never confuse the two (invariant 6). Absent on
codex/scripted lanes and on every pre-existing bundle; a `null`
`estimatedCostUsd` is DECLARED ABSENT (unknown rate / no usage), never 0.

Unexpected actor-loop diagnostics live inside `items[]` as
`kind: notice`, `status: error` rows. They are public-safe evidence, not crash
Expand Down Expand Up @@ -661,7 +672,7 @@ mode (`loopback | exposed | share-safe-open`), the loopback host/port,
`allowEmails`, `allowDomains` — operator-supplied allow rules, public-safe to
echo to the operator's own stdout, never persisted into any bundle), runs
listed, computed warnings, and the `ServeErrorCode` union. Exposure auth is
tunnel-edge only — as of 0.18.0 there are no `capabilityUrl`/`publicCapabilityUrl`
tunnel-edge only — as of 0.19.0 there are no `capabilityUrl`/`publicCapabilityUrl`
/`ttlMinutes` fields, no `--auth`/`--ttl` flags, and no `capability-link` mode
(the in-process `observer-auth.ts` capability-link was removed as a pre-1.0
breaking change).
Expand Down Expand Up @@ -743,6 +754,82 @@ measure) and never grant a green pass (they surface as unmeasured). `verifyRun`
fails closed when a live bundle lacks the cost ledger or no-spend proof, when the
proof claims zero on a `null` line, or when known spend exceeds the declared cap.

## Run Cost Summary And Estimated Actor Cost

The computer-use (CUA) lane surfaces an ADVISORY, additive cost ESTIMATE. It is
never authoritative: every dollar figure is a rate-table multiply, labeled
"estimated (rates as of `<date>`)", and is NEVER presented as a provider charge
(invariant 6). Three new `.v1` schema tags ship, all additive and optional so
`humanish.run-bundle.v1` stays v1 and every pre-existing bundle is byte-stable:

- `humanish.pricing.v1` — the OPERATOR-EDITABLE rate table in `src/pricing.ts`:
dated per-model input/output USD-per-token rates and an E2B desktop
USD-per-minute rate, each with a public pricing-page `source` and an `asOf`
date. A prominent banner says these are estimates to update when providers
change pricing. Some entries are `placeholder: true` stand-ins (the shipped
`gpt-5.5` model rate and the E2B desktop rate) — an operator MUST confirm them
before trusting the magnitude; the flag propagates into every estimate so a
stand-in is never mistaken for a live rate. An UNKNOWN model/desktop rate is
DECLARED ABSENT (`estimatedCostUsd: null` + a `reason`), never guessed.
- `humanish.actor-estimated-cost.v1` — `ActorTrace.estimatedCost`: one lane's
token-derived model cost, with `estimatedCostUsd` (or `null` + `reason`
`no_rate_for_model`/`no_token_usage`), `ratesAsOf`, `source`, `modelId`,
optional `placeholder`, and a `breakdown`.
- `humanish.run-cost-summary.v1` — `RunBundle.cost`: the sum of every lane's
`model-tokens` line PLUS one aggregate `desktop-minutes` line.

The summary follows the SAME null discipline as the terminal cost ledger above.
`estimatedTotalUsd` sums ONLY the non-null `breakdown` lines and is `null` iff
EVERY line is null (never coerced to `0`); a present-but-unpriceable line stays
in `breakdown` with `estimatedCostUsd: null` + a `reason` (it records that we
tried and could not price it) and contributes nothing. `fullyEstimated` is
`false` when any applicable line is null (the total is then a lower bound);
`placeholder` is true when any contributing rate is a stand-in; `ratesAsOf` is
the MIN (oldest) `asOf` across contributing rates — an aggregate is only as fresh
as its stalest input, so MAX would overclaim freshness (each `breakdown` line
keeps its own true `asOf`). `desktopMinutes` is a HOST-SIDE
create→teardown span — an approximation of E2B's server-side billed lifetime, so
the desktop dollar figure is doubly an estimate.

```yaml
schema: humanish.run-cost-summary.v1
currency: usd
estimatedTotalUsd: 11.60167 # sum of KNOWN lines only; null iff every line null
ratesAsOf: "2026-08-01"
fullyEstimated: true # both breakdown lines are priced (no null line)
placeholder: true # a stand-in rate contributed
breakdown:
- { kind: model-tokens, laneId: lane-01, modelId: computer-use-preview,
estimatedCostUsd: 11.60, ratesAsOf: "2026-08-01", source: "openai.com/api/pricing" }
- { kind: desktop-minutes, estimatedCostUsd: 0.00167, ratesAsOf: "2026-08-01",
source: "…e2b.dev/pricing", placeholder: true }
tokenUsage: { input: 3843523, output: 5869, total: 3849392 }
desktopMinutes: 1
note: "Estimated 11.60167 USD total…"
```

`verifyRun` asserts LABELING/provenance, never MAGNITUDE. Absence PASSES
(fail-open on display): a bundle with no cost, a null estimate, or a lane without
`estimatedCost` verifies fine. A CLAIMED number FAILS closed when it lacks its
`ratesAsOf` date or `source`, or when `estimatedTotalUsd` does not equal the
rounded sum of its non-null lines (a null line coerced to 0 is a mechanism
mismatch). A correctly-labeled huge estimate still passes. Cost is neither a
secret nor a share-blocker, so it never affects `shareSafety`.

**Fail-closed spend cap.** `execution.caps.maxUsd` (the terminal lane's
`LabScenarioCaps` shape, consumed on the CUA route) aborts a session the moment
its running ESTIMATED spend crosses the cap — the runaway-retry guard. It is a
**PER-LANE** cap: enforced INSIDE each lane's loop independently, so an N-lane
fan-out can spend up to N × `maxUsd` before any lane aborts (the run bundle
warns with the true ~N × cap ceiling; a shared run-level budget is future work).
A lane that does real work THEN crosses its cap ends `budget_reached` (passed); a
zero-action runaway that crosses it ends `gave_up` (failed) — the cap classifies
its outcome honestly rather than greenlighting the runaway it exists to catch.
Absent = uncapped (the historical behavior); `maxUsd: 0` = no-spend. A cap on a
model `src/pricing.ts` cannot price is REFUSED at preflight
(`HUMANISH_CUA_LAB_UNPRICED_CAP`) before any sandbox rather than run uncapped —
an unenforceable cap is more dangerous than none.

## Product-Adapter Extension Seam

The terminal-product and browser/computer-use lanes let an adopter attach
Expand Down
2 changes: 1 addition & 1 deletion docs/goals/current.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ Humanish should be the open-source CLI that lets a maintainer ask:
The answer should be observable, verifiable, public-safe, and easy to turn into
actionable feedback.

## Current Program Truth (source `0.18.0`)
## Current Program Truth (source `0.19.0`)

The package source and repository implementation in this tree agree on these
points:
Expand Down
9 changes: 9 additions & 0 deletions docs/goals/proof-roadmap/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,15 @@ file records current implementation truth without rewriting that packet.
schema implementation. Its adopter-need and maintainer-review gate is closed.
- Public out-of-tree actor registration and conformance certification remain
unbuilt.
- Cost tracking (ESTIMATED) shipped in `0.19.0`: a dated, operator-editable
pricing table (`src/pricing.ts`), a per-lane + run-level cost ESTIMATE on the
computer-use bundle (`humanish.run-cost-summary.v1` /
`humanish.actor-estimated-cost.v1`, additive so `humanish.run-bundle.v1` stays
v1), an `execution.caps.maxUsd` fail-closed abort for the CUA lane (default
uncapped; an unpriced cap is refused at preflight), and Observer/library
labeling ("estimated (rates as of `<date>`)", never a bare charge). Every
dollar figure is an estimate; `verify` asserts its labeling, never its
magnitude.

## Proof state

Expand Down
2 changes: 1 addition & 1 deletion docs/ramp/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

Status: public-safe contributor and agent ramp.

Package/source version in this tree: `0.18.0` (2026-08-02). The containment boundary introduced in
Package/source version in this tree: `0.19.0` (2026-08-02). The containment boundary introduced in
`0.15.1` remains in force: managed run and output paths bind to validated
physical filesystem identities, and stored provider IDs are evidence, not
cleanup authority. The bundled OSS meta-lab is dry-run only until
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "humanish",
"version": "0.18.0",
"version": "0.19.0",
"description": "Open-source-safe CLI for persona simulation, observer review, and public-safe feedback drafts.",
"author": "Daniel G Wilson <daniel@danielgwilson.com>",
"keywords": [
Expand Down
10 changes: 10 additions & 0 deletions src/actor-contract.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { CodexAppServerRunResult, CodexAppServerStatus, CodexAppServerTrace } from "./codex-app-server.js";
import type { ActorEstimatedCost } from "./pricing.js";
import { redactText } from "./redaction.js";

// The provider-neutral evidence schema. Codex item/* events, Claude
Expand Down Expand Up @@ -129,6 +130,15 @@ export interface ActorTrace {
counts: Record<string, number>;
items: ActorTraceItem[];
tokenUsage?: ActorTokenUsage;
/**
* ADDITIVE + OPTIONAL token-derived cost ESTIMATE for this lane (humanish.actor-estimated-cost.v1).
* Distinct from `tokenUsage.costUsd`, which is RESERVED for a real provider-returned charge: a
* bare `costUsd` always means "the provider billed this", while `estimatedCost.estimatedCostUsd`
* is a rate-table multiply named honestly as an estimate (invariant 6). Absent on codex/scripted
* lanes and on every pre-existing bundle — its absence is tolerated by verify (fail-open on
* display). A `null` estimatedCostUsd is DECLARED ABSENT (unknown rate / no usage), never 0.
*/
estimatedCost?: ActorEstimatedCost;
capabilities: ActorCapabilities;
}

Expand Down
8 changes: 7 additions & 1 deletion src/computer-use-actor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,10 @@ export interface CuaActorSessionOptions {
writeScreenshot?: (name: string, bytes: Buffer) => Promise<string>;
/** Deterministic harness-owned stop guards evaluated between model turns. */
stopWhen?: StopWhen;
/** FAIL-CLOSED spend cap (USD) threaded to the loop; absent = uncapped. See CuaLoopOptions.maxUsd. */
maxUsd?: number;
/** Injected pure per-turn cost estimator paired with `maxUsd`. See CuaLoopOptions.estimateTurnCostUsd. */
estimateTurnCostUsd?: (input: number, output: number) => number | null;
}

export async function runCuaActorSession(options: CuaActorSessionOptions): Promise<CuaLoopResult> {
Expand All @@ -96,7 +100,9 @@ export async function runCuaActorSession(options: CuaActorSessionOptions): Promi
...(options.redactScreenshots === undefined ? {} : { redactScreenshots: options.redactScreenshots }),
...(options.scrubText === undefined ? {} : { scrubText: options.scrubText }),
...(options.writeScreenshot === undefined ? {} : { writeScreenshot: options.writeScreenshot }),
...(options.stopWhen === undefined ? {} : { stopWhen: options.stopWhen })
...(options.stopWhen === undefined ? {} : { stopWhen: options.stopWhen }),
...(options.maxUsd === undefined ? {} : { maxUsd: options.maxUsd }),
...(options.estimateTurnCostUsd === undefined ? {} : { estimateTurnCostUsd: options.estimateTurnCostUsd })
};

return runComputerUseLoop(loopOptions);
Expand Down
Loading