humanish 0.127.1 → 0.128.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +13 -0
- package/CONTEXT.md +2 -2
- package/README.md +1 -1
- package/dist/actors/computer-use/loop/output-limit.js +3 -0
- package/dist/actors/computer-use/loop/types.d.ts +16 -10
- package/dist/actors/computer-use/loop.js +6 -1
- package/dist/feedback/export.js +3 -2
- package/dist/observer/data.d.ts +13 -6
- package/dist/observer/data.js +40 -10
- package/dist/observer-app.html +8 -8
- package/dist/routes/computer-use/participant-model.js +1 -1
- package/dist/run/bundle.d.ts +7 -4
- package/dist/run/bundle.js +10 -4
- package/dist/run/detail.js +8 -2
- package/dist/run/locate.d.ts +2 -2
- package/dist/run/locate.js +2 -2
- package/dist/run/run.js +11 -3
- package/dist/run/stream-trace.d.ts +17 -7
- package/dist/run/stream-trace.js +135 -29
- package/dist/run/streams.d.ts +4 -1
- package/dist/study/bundle-estimate.js +17 -17
- package/dist/verify/costs.d.ts +7 -3
- package/dist/verify/costs.js +27 -12
- package/dist/verify/verify.js +14 -8
- package/docs/architecture/github-feedback-loop.md +1 -1
- package/docs/architecture/observer.md +19 -10
- package/docs/contracts/README.md +1 -1
- package/docs/contracts/run-bundle.md +93 -15
- package/docs/contracts/schemas.md +55 -45
- package/docs/contracts/study-analysis.md +1 -1
- package/docs/decisions/0002-observer-is-one-self-contained-file.md +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -8,6 +8,19 @@ The Unreleased section holds the full notes for the next version until it is tag
|
|
|
8
8
|
|
|
9
9
|
## Unreleased
|
|
10
10
|
|
|
11
|
+
## 0.128.0: Run bundle v4 keeps per-request lists in trace files; the study budget is checked before each request (2026-10-13)
|
|
12
|
+
|
|
13
|
+
humanish 0.128.0 keeps run.json small in long studies. Run bundles are now
|
|
14
|
+
`humanish.run-bundle.v4`: each participant's per-request lists (`tokenUsage.turns`,
|
|
15
|
+
`conversation.requests` and `providerRequests`) are only in its trace file, and run.json keeps the
|
|
16
|
+
totals. A synthetic run of 30 participants making 2,880 requests each wrote run.json at 93 to 106
|
|
17
|
+
KB, where 0.127.1 wrote 16.1, 27.2 and 41.8 MB; analysis reads run.json only up to 16 MiB. This
|
|
18
|
+
release reads v1 to v4 bundles, and `loadRunBundle` joins the lists back. 0.127.1 and earlier fail
|
|
19
|
+
verify on a v4 run. A computer-use participant now checks the study budget before each model
|
|
20
|
+
request, so one whose sibling crossed `caps.maxTotalUsd` no longer sends one more request.
|
|
21
|
+
|
|
22
|
+
[Release notes](https://github.com/danielgwilson/humanish/releases/tag/v0.128.0)
|
|
23
|
+
|
|
11
24
|
## 0.127.1: Clock-end closing reports declare their outcome; skipped participants named as skipped (2026-10-13)
|
|
12
25
|
|
|
13
26
|
humanish 0.127.1 fixes two things the 0.127.0 release smoke found in scheduled studies. An OpenAI
|
package/CONTEXT.md
CHANGED
|
@@ -12,7 +12,7 @@ bundle and output fields keep older spellings; the second table maps them.
|
|
|
12
12
|
| Term | Meaning | Where it lives in the code |
|
|
13
13
|
| ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
14
14
|
| study | What a user designs and runs: one YAML file that declares the subject, the actor, the participants and the caps. The file lives at `humanish/studies/<id>.yaml` with schema `humanish.study.v3`, read by `humanish study` and `--study`. humanish refuses a 0.107 `humanish.lab.v2` file, and `humanish migrate` converts it (second table). | `src/study/`; `StudyConfig` in `src/study/types.ts`, parsed by `parseStudy` in `src/study/config.ts` |
|
|
15
|
-
| run | One execution of a study, on disk: `.humanish/runs/<runId>/`, with `run.json` (`humanish.run-bundle.
|
|
15
|
+
| run | One execution of a study, on disk: `.humanish/runs/<runId>/`, with `run.json` (`humanish.run-bundle.v4`) as the source of truth and each participant's trace in its own file. | `src/run/`; `RunBundle` in `src/run/bundle.ts` |
|
|
16
16
|
| dry run | A run with no live participant, from `--dry-run` or `mode: dry-run`. humanish writes a synthetic run bundle, review and Observer with no browser, model, keys or provider spend, and tests no product behavior. The `preview` route only dry-runs; "preview" names that route and nothing else. | `runDryRun` in `src/run/dry-run.ts`; `resolveStudyDryRun` in `src/study/plan.ts` |
|
|
17
17
|
| participant | One simulated person, or agent, taking part in a study. The planner builds each one's record (id, persona id, assignment, labels and the route's own fields). Shared-world participants resolve the plan's records; computer-use participants rebuild the same records from the study config with `computerUseParticipants` (`src/study/plan-participants.ts`) and resolve those. `ResolvedParticipant` adds the bundle ids, persona and prompt. `run.json` records each one as a `simulations[]` entry with one or more `streams[]`. | `ResolvedParticipant` in `src/run/participant.ts`; the planned records in `src/study/plan-participants.ts`. Computer-use and shared-world participants run a `DesktopParticipantRun` (`src/routes/computer-use/types.ts`). The terminal session (`src/routes/terminal/session.ts`), scripted surfaces (`src/routes/scripted/plan.ts`) and fixture simulations (`src/run/dry-run.ts`) still build their own. |
|
|
18
18
|
| persona | Who a participant is: traits, background, device. `humanish/personas/<id>.yaml`. | `src/study/persona.ts`, `src/study/persona-resolve.ts` |
|
|
@@ -36,7 +36,7 @@ bundle and output fields keep older spellings; the second table maps them.
|
|
|
36
36
|
| topology | Whether participants share one instance of the subject. `route: shared-world` gives one for all; every other route gives each participant its own (`per-lane-worlds`). `run.json` records it as `sharedWorld.topology`. | `route` in `StudyConfig` (`src/study/types.ts`); `isSharedWorldComposition` in `src/study/routing.ts` |
|
|
37
37
|
| plane | The one instance of the subject that a shared-world study's participants use together. A `provisioned` plane is a `clone` or `local-tree` subject that humanish serves in a sandbox, with a declared subject state. An `external-public` plane is a deployment already running at `subject.appUrl`, whose owner the study names in `subject.publicTarget.owner`. Each plane has at least two participants. | `SharedWorldPlane` in `src/study/plan-types.ts`; `src/routes/shared-world/` |
|
|
38
38
|
| stream | One live or recorded view of a participant, such as a browser or a terminal, in `run.json` and the Observer. | `RunStream` in `src/run/streams.ts` |
|
|
39
|
-
| Observer | The single-file review page built from `observer/observer-data.json` (`humanish.observer-data.
|
|
39
|
+
| Observer | The single-file review page built from `observer/observer-data.json` (`humanish.observer-data.v3`). | `observer/` (the page), `src/observer/` (data, render, serve) |
|
|
40
40
|
| evidence | Files a run keeps: screenshots, traces, transcripts, recordings. `verify` checks them and grades share-safety. | `src/evidence/`; `verifyRun` in `src/verify/verify.ts` |
|
|
41
41
|
| analysis | An independent model reading of a finished run's retained evidence, kept apart from participant feedback. Supported live runs request it automatically, and `humanish analyze` runs it on demand. It records findings and observations as `humanish.study-analysis.v1`. | `src/analysis/`; `runAutomaticAnalysis` in `src/analysis/automatic.ts`; `analyzeRun` in `src/analysis/service.ts`; `ANALYSIS_SCHEMA` in `src/analysis/types.ts` |
|
|
42
42
|
| finding | A result of analysis, with citations to evidence in the run. Each finding groups the observations that support it. | `src/analysis/` |
|
package/README.md
CHANGED
|
@@ -335,7 +335,7 @@ and the library exports.
|
|
|
335
335
|
- Breaking changes ship together in one minor release. Its release notes carry a migration
|
|
336
336
|
table: each old form, its new form, and the change to make.
|
|
337
337
|
- The study file and the run bundle carry versioned schemas, `humanish.study.v3` and
|
|
338
|
-
`humanish.run-bundle.
|
|
338
|
+
`humanish.run-bundle.v4`. A breaking change to either one bumps its version.
|
|
339
339
|
|
|
340
340
|
Pin the exact version in `package.json` and read [CHANGELOG.md](CHANGELOG.md) before upgrading.
|
|
341
341
|
|
|
@@ -17,5 +17,8 @@ export function retryAfterOutputLimit(session, turn, turnNumber, alreadyRetried)
|
|
|
17
17
|
session.usage.record(turn, "interaction");
|
|
18
18
|
const output = turn.usage?.output;
|
|
19
19
|
session.trace.record("notice", () => notice("warn", "provider reply cut off by the output limit; asking again", `provider turn ${turnNumber} reached the output-token limit${output === undefined ? "" : ` after ${output} output tokens`} before any action; its actions were not run, and the same request is sent once more`));
|
|
20
|
+
// haltBeforeTurn checks spend again before the resend. Checking here as well keeps a reply that
|
|
21
|
+
// crossed a cap out of the trace's narration, as after a complete reply, and keeps a crossed
|
|
22
|
+
// cap ahead of unknown usage.
|
|
20
23
|
return spendStop(session) ?? unknownSpendStop(session) ?? "retry";
|
|
21
24
|
}
|
|
@@ -386,19 +386,25 @@ export interface CuaLoopOptions {
|
|
|
386
386
|
*/
|
|
387
387
|
maxUsd?: number;
|
|
388
388
|
/**
|
|
389
|
-
* Injected pure
|
|
390
|
-
*
|
|
391
|
-
* estimated USD, or null when unpriceable. Only consulted when `maxUsd` is set.
|
|
392
|
-
*
|
|
393
|
-
*
|
|
389
|
+
* Injected pure cost estimator (keeps the loop free of the operator rate table and makes the
|
|
390
|
+
* cap deterministic in tests). Given running (input, output) token totals, returns the
|
|
391
|
+
* estimated USD, or null when unpriceable. Only consulted when `maxUsd` is set. The loop calls it
|
|
392
|
+
* after each reply and again before each provider request, the first included, when every total
|
|
393
|
+
* is 0 and the usage has no `turns`. An estimate of zero usage above `maxUsd` therefore stops the
|
|
394
|
+
* session before its first request. A null estimate mid-run cannot trip the cap; preflight
|
|
395
|
+
* already guaranteed a rate exists, so a null here is a vanished-rate harness condition, not a
|
|
396
|
+
* silent uncapped pass.
|
|
394
397
|
*/
|
|
395
398
|
estimateTurnCostUsd?: (usage: ActorTokenUsage) => number | null;
|
|
396
399
|
/**
|
|
397
|
-
* Run-level spend guard: called with this participant's running usage each
|
|
398
|
-
*
|
|
399
|
-
*
|
|
400
|
-
*
|
|
401
|
-
*
|
|
400
|
+
* Run-level spend guard: called with this participant's running usage at each point the
|
|
401
|
+
* per-participant cap is checked, after each reply and before each provider request. The first
|
|
402
|
+
* call comes before the first request, with every total at 0 and no `turns`. How many calls a
|
|
403
|
+
* turn makes depends on the loop's checks, so a guard should decide from the usage it is given
|
|
404
|
+
* and the ledger it reads. Returns a human-readable reason when the study's shared budget is
|
|
405
|
+
* exhausted, else null. On a non-null return the loop stops with `budget_reached` regardless of
|
|
406
|
+
* material progress: a study-level stop is a recruiting decision hitting its limit, not this
|
|
407
|
+
* participant's runaway, so it never reads as `gave_up`.
|
|
402
408
|
*/
|
|
403
409
|
overRunBudget?: (usage: ActorTokenUsage) => string | null;
|
|
404
410
|
/**
|
|
@@ -201,11 +201,16 @@ function refuseAccountBilledCaps(options) {
|
|
|
201
201
|
});
|
|
202
202
|
}
|
|
203
203
|
}
|
|
204
|
+
/**
|
|
205
|
+
* The stops checked before every provider request. The spend caps are checked here as well as
|
|
206
|
+
* after each reply, because a sibling participant can exhaust the study budget while this one acts.
|
|
207
|
+
*/
|
|
204
208
|
function haltBeforeTurn(session, observation) {
|
|
205
209
|
if (session.signal?.aborted)
|
|
206
210
|
return stops.harnessAborted;
|
|
207
211
|
return (clockEndStop(session, observation) ??
|
|
208
|
-
(session.remaining() < 0 ? stops.timeLimit(session) : undefined)
|
|
212
|
+
(session.remaining() < 0 ? stops.timeLimit(session) : undefined) ??
|
|
213
|
+
spendStop(session));
|
|
209
214
|
}
|
|
210
215
|
/**
|
|
211
216
|
* Build the next request; the context hint and safety acknowledgements ride it once. The
|
package/dist/feedback/export.js
CHANGED
|
@@ -180,7 +180,8 @@ async function readObserverSlot(cwd, runInput, runRoot, runId, verified, deps, w
|
|
|
180
180
|
* even if an older saved HTML was never refreshed; old recording-only exports keep the saved slot.
|
|
181
181
|
* A page saved since observer-data v2 holds no participant's items in its slot, and verify does not
|
|
182
182
|
* read the page's compressed copy, so its export is the current projection of the run and its
|
|
183
|
-
* traces. Undefined when run.json no longer matches the analysis, or
|
|
183
|
+
* traces, for a page saved as v2 too. Undefined when run.json no longer matches the analysis, or
|
|
184
|
+
* such a page's run cannot be read.
|
|
184
185
|
*/
|
|
185
186
|
async function currentObserverData(runPaths, analysis, slot) {
|
|
186
187
|
const saved = JSON.parse(slot);
|
|
@@ -189,7 +190,7 @@ async function currentObserverData(runPaths, analysis, slot) {
|
|
|
189
190
|
const schema = saved?.schema;
|
|
190
191
|
if (schema === "humanish.observer-data.v1")
|
|
191
192
|
return { value: observerDataFromSaved(saved) };
|
|
192
|
-
if (schema !== OBSERVER_DATA_SCHEMA)
|
|
193
|
+
if (schema !== OBSERVER_DATA_SCHEMA && schema !== "humanish.observer-data.v2")
|
|
193
194
|
return { value: saved };
|
|
194
195
|
const bundle = await sourceTraces(runPaths, await source());
|
|
195
196
|
return bundle === undefined ? undefined : { value: buildObserverData(bundle) };
|
package/dist/observer/data.d.ts
CHANGED
|
@@ -2,7 +2,12 @@ import { type ActorEnding } from "../actors/stop-cause.js";
|
|
|
2
2
|
import type { RunBundle, RunCostSummary, RunEvent, RunSimulation } from "../run/bundle.js";
|
|
3
3
|
import { type RunDisplay } from "../run/display.js";
|
|
4
4
|
import type { RunStream, RunStreamKind } from "../run/streams.js";
|
|
5
|
-
|
|
5
|
+
/**
|
|
6
|
+
* The Observer's data. From v2 observer-data.json leaves out each participant's items, and from v3
|
|
7
|
+
* each participant's per-request lists (REQUEST_LISTS in src/run/stream-trace.ts), which no part
|
|
8
|
+
* of the page shows.
|
|
9
|
+
*/
|
|
10
|
+
export declare const OBSERVER_DATA_SCHEMA = "humanish.observer-data.v3";
|
|
6
11
|
interface ObserverArtifactLink {
|
|
7
12
|
label: string;
|
|
8
13
|
href: string;
|
|
@@ -10,7 +15,7 @@ interface ObserverArtifactLink {
|
|
|
10
15
|
}
|
|
11
16
|
export interface ObserverData {
|
|
12
17
|
schema: typeof OBSERVER_DATA_SCHEMA;
|
|
13
|
-
schemaVersion:
|
|
18
|
+
schemaVersion: 3;
|
|
14
19
|
generatedAt: string;
|
|
15
20
|
/** Served-only liveness observation. Never a replacement for the recorded evidence verdict. */
|
|
16
21
|
runtime?: {
|
|
@@ -124,10 +129,12 @@ interface ObserverLaneGroup {
|
|
|
124
129
|
*/
|
|
125
130
|
export declare function buildObserverData(bundle: RunBundle, generatedAt?: string, statusRecord?: unknown): ObserverData;
|
|
126
131
|
/**
|
|
127
|
-
* A saved Observer projection as this version reads it, unchecked as before
|
|
128
|
-
* written before v2 holds every participant's items
|
|
129
|
-
*
|
|
130
|
-
*
|
|
132
|
+
* A saved Observer projection as this version reads it, as v3 and unchecked as before. An
|
|
133
|
+
* observer-data.json written before v2 holds every participant's items, and its per-request lists,
|
|
134
|
+
* which are left out. A v2 or v3 one holds no items, so it is read only when no participant has
|
|
135
|
+
* any: showing participants with no steps would say they did nothing, and the items are in the
|
|
136
|
+
* run's traces. Null for a projection that leaves items out, and for a schema other than v1, v2
|
|
137
|
+
* and v3.
|
|
131
138
|
*/
|
|
132
139
|
export declare function observerDataFromSaved(value: unknown): ObserverData | null;
|
|
133
140
|
/** Refresh presentation from recorded actor evidence, including older exported snapshots. */
|
package/dist/observer/data.js
CHANGED
|
@@ -3,8 +3,15 @@ import { isCommsReceivingEvidence, receivingAnalysisContext } from "../comms/rec
|
|
|
3
3
|
import { formatParticipantOutcomes, formatRunTaskFunnel, participantOutcomeDetails, withCuaReviewProvenance, } from "../run/outcomes.js";
|
|
4
4
|
import { cuaGoalSource, CUA_COMPLETION_NOTE } from "../actors/goal-source.js";
|
|
5
5
|
import { bundleDisplayFacts, runDisplay } from "../run/display.js";
|
|
6
|
+
import { withoutRequestLists } from "../run/stream-trace.js";
|
|
7
|
+
import { isRecord } from "../run/type-guards.js";
|
|
6
8
|
import { joinObserverData, shellNeedsItems } from "./participant-items.js";
|
|
7
|
-
|
|
9
|
+
/**
|
|
10
|
+
* The Observer's data. From v2 observer-data.json leaves out each participant's items, and from v3
|
|
11
|
+
* each participant's per-request lists (REQUEST_LISTS in src/run/stream-trace.ts), which no part
|
|
12
|
+
* of the page shows.
|
|
13
|
+
*/
|
|
14
|
+
export const OBSERVER_DATA_SCHEMA = "humanish.observer-data.v3";
|
|
8
15
|
/** Discard a forged or stale runtime grant before projecting persisted evidence. */
|
|
9
16
|
export function recordedStreamEmbed(embed) {
|
|
10
17
|
if (!embed)
|
|
@@ -52,7 +59,7 @@ export function buildObserverData(bundle, generatedAt = new Date().toISOString()
|
|
|
52
59
|
: stream.status);
|
|
53
60
|
byKind[stream.kind] += 1;
|
|
54
61
|
return {
|
|
55
|
-
...stream,
|
|
62
|
+
...withoutStreamLists(stream),
|
|
56
63
|
...(stream.embed === undefined ? {} : { embed: recordedStreamEmbed(stream.embed) }),
|
|
57
64
|
status,
|
|
58
65
|
sim,
|
|
@@ -81,7 +88,7 @@ export function buildObserverData(bundle, generatedAt = new Date().toISOString()
|
|
|
81
88
|
const active = streams.filter((stream) => stream.status === "running" || stream.status === "preparing").length;
|
|
82
89
|
return withObserverEndings({
|
|
83
90
|
schema: OBSERVER_DATA_SCHEMA,
|
|
84
|
-
schemaVersion:
|
|
91
|
+
schemaVersion: 3,
|
|
85
92
|
generatedAt,
|
|
86
93
|
run: {
|
|
87
94
|
runId: bundle.runId,
|
|
@@ -142,19 +149,42 @@ export function buildObserverData(bundle, generatedAt = new Date().toISOString()
|
|
|
142
149
|
},
|
|
143
150
|
});
|
|
144
151
|
}
|
|
152
|
+
/** The stream without its participant's per-request lists; a saved stream that is not an object as it is. */
|
|
153
|
+
function withoutStreamLists(stream) {
|
|
154
|
+
if (!isRecord(stream))
|
|
155
|
+
return stream;
|
|
156
|
+
return {
|
|
157
|
+
...stream,
|
|
158
|
+
...(isRecord(stream.actor) ? { actor: withoutRequestLists(stream.actor) } : {}),
|
|
159
|
+
...(isRecord(stream.liveActor) ? { liveActor: withoutRequestLists(stream.liveActor) } : {}),
|
|
160
|
+
};
|
|
161
|
+
}
|
|
145
162
|
/**
|
|
146
|
-
* A saved Observer projection as this version reads it, unchecked as before
|
|
147
|
-
* written before v2 holds every participant's items
|
|
148
|
-
*
|
|
149
|
-
*
|
|
163
|
+
* A saved Observer projection as this version reads it, as v3 and unchecked as before. An
|
|
164
|
+
* observer-data.json written before v2 holds every participant's items, and its per-request lists,
|
|
165
|
+
* which are left out. A v2 or v3 one holds no items, so it is read only when no participant has
|
|
166
|
+
* any: showing participants with no steps would say they did nothing, and the items are in the
|
|
167
|
+
* run's traces. Null for a projection that leaves items out, and for a schema other than v1, v2
|
|
168
|
+
* and v3.
|
|
150
169
|
*/
|
|
151
170
|
export function observerDataFromSaved(value) {
|
|
152
171
|
const saved = value;
|
|
153
|
-
|
|
154
|
-
|
|
172
|
+
const schema = saved.schema;
|
|
173
|
+
if (schema === "humanish.observer-data.v1") {
|
|
174
|
+
const streams = Array.isArray(saved.streams)
|
|
175
|
+
? saved.streams.map(withoutStreamLists)
|
|
176
|
+
: saved.streams;
|
|
177
|
+
return { ...saved, streams, schema: OBSERVER_DATA_SCHEMA, schemaVersion: 3 };
|
|
178
|
+
}
|
|
179
|
+
if (schema !== "humanish.observer-data.v2" && schema !== OBSERVER_DATA_SCHEMA)
|
|
180
|
+
return null;
|
|
155
181
|
return shellNeedsItems(saved)
|
|
156
182
|
? null
|
|
157
|
-
:
|
|
183
|
+
: {
|
|
184
|
+
...joinObserverData(saved, null),
|
|
185
|
+
schema: OBSERVER_DATA_SCHEMA,
|
|
186
|
+
schemaVersion: 3,
|
|
187
|
+
};
|
|
158
188
|
}
|
|
159
189
|
const GOAL_SOURCE_LABELS = {
|
|
160
190
|
participant_report: "Reported complete",
|