humanish 0.117.0 → 0.119.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 +42 -0
- package/dist/analysis/admission.d.ts +23 -4
- package/dist/analysis/admission.js +47 -9
- package/dist/analysis/analysis-limits.d.ts +32 -0
- package/dist/analysis/analysis-limits.js +41 -3
- package/dist/analysis/automatic-config.d.ts +11 -1
- package/dist/analysis/automatic-config.js +26 -11
- package/dist/analysis/cohorts.d.ts +103 -0
- package/dist/analysis/cohorts.js +80 -0
- package/dist/analysis/evidence.d.ts +5 -10
- package/dist/analysis/evidence.js +37 -34
- package/dist/analysis/execute.d.ts +24 -7
- package/dist/analysis/execute.js +290 -307
- package/dist/analysis/responses.d.ts +19 -0
- package/dist/analysis/responses.js +201 -0
- package/dist/analysis/store.js +2 -1
- package/dist/analysis/validation.js +13 -7
- package/dist/cli/commands/analyze.js +10 -4
- package/dist/cli/commands/study-format.js +8 -11
- package/dist/cli/commands/study-route-shared-world.js +3 -1
- package/dist/cli/commands/study-run.js +1 -1
- package/dist/cli/doctor.js +11 -1
- package/dist/comms/lease-store.d.ts +1 -1
- package/dist/comms/lease-store.js +3 -2
- package/dist/comms/receiving-types.d.ts +2 -0
- package/dist/comms/receiving-types.js +2 -0
- package/dist/observer-app.html +2 -2
- package/dist/routes/computer-use/fanout-bundle.js +4 -2
- package/dist/routes/computer-use/fanout-records.js +6 -1
- package/dist/routes/computer-use/participant-execution.d.ts +6 -4
- package/dist/routes/computer-use/participant-execution.js +42 -14
- package/dist/routes/computer-use/participant-model.js +18 -8
- package/dist/routes/computer-use/participant-runs.js +15 -9
- package/dist/routes/computer-use/plan.d.ts +3 -1
- package/dist/routes/computer-use/plan.js +38 -12
- package/dist/routes/computer-use/rerun-selection.js +14 -3
- package/dist/routes/computer-use/single-bundle.js +7 -4
- package/dist/routes/computer-use/subject-projection.js +2 -0
- package/dist/routes/computer-use/types.d.ts +13 -1
- package/dist/routes/scripted/plan.d.ts +4 -1
- package/dist/routes/scripted/plan.js +25 -2
- package/dist/routes/scripted/setup.js +1 -4
- package/dist/routes/scripted/types.d.ts +1 -1
- package/dist/routes/shared-world/bundle-records.js +14 -0
- package/dist/routes/shared-world/bundle.js +2 -1
- package/dist/routes/shared-world/external-public.js +15 -3
- package/dist/routes/shared-world/handoff.d.ts +2 -2
- package/dist/routes/shared-world/participant-specs.js +2 -2
- package/dist/routes/shared-world/plan.d.ts +10 -1
- package/dist/routes/shared-world/plan.js +86 -10
- package/dist/routes/shared-world/provisioned.js +15 -8
- package/dist/routes/shared-world/route.js +7 -4
- package/dist/routes/shared-world/types.d.ts +2 -0
- package/dist/run/bundle-shape.js +12 -2
- package/dist/run/bundle.d.ts +20 -3
- package/dist/run/participant-records.js +1 -0
- package/dist/run/projection.js +7 -2
- package/dist/run/run.js +4 -2
- package/dist/run/subject-name.d.ts +11 -0
- package/dist/run/subject-name.js +11 -0
- package/dist/study/arrivals.d.ts +114 -0
- package/dist/study/arrivals.js +204 -0
- package/dist/study/composition-rules.js +1 -1
- package/dist/study/config.js +6 -20
- package/dist/study/doctor.js +2 -1
- package/dist/study/keys.d.ts +1 -0
- package/dist/study/keys.js +2 -0
- package/dist/study/migrate/v2.js +3 -2
- package/dist/study/parse/actors.d.ts +2 -2
- package/dist/study/parse/actors.js +10 -4
- package/dist/study/parse/front.d.ts +1 -1
- package/dist/study/parse/front.js +36 -2
- package/dist/study/plan-base.d.ts +1 -1
- package/dist/study/plan-participants.d.ts +2 -0
- package/dist/study/plan-participants.js +3 -5
- package/dist/study/plan-types.d.ts +13 -3
- package/dist/study/plan.d.ts +4 -4
- package/dist/study/plan.js +21 -12
- package/dist/study/preflight.d.ts +2 -1
- package/dist/study/preflight.js +24 -3
- package/dist/study/routing.d.ts +5 -4
- package/dist/study/routing.js +16 -7
- package/dist/study/types.d.ts +14 -0
- package/dist/study/validation.d.ts +2 -2
- package/dist/study/validation.js +27 -14
- package/dist/study/warnings.js +11 -3
- package/dist/substrates/e2b/lifetime.d.ts +75 -4
- package/dist/substrates/e2b/lifetime.js +134 -10
- package/dist/substrates/e2b/subject-sandbox.js +2 -6
- package/dist/tui-app.js +112 -112
- package/dist/verify/artifacts.d.ts +19 -4
- package/dist/verify/artifacts.js +39 -8
- package/dist/verify/verify.js +6 -15
- package/docs/contracts/run-bundle.md +8 -7
- package/docs/contracts/schemas.md +33 -9
- package/docs/contracts/study-analysis.md +61 -8
- package/docs/principles/invariants-and-defaults.md +14 -14
- package/docs/product/automatic-analysis.md +10 -3
- package/package.json +1 -1
|
@@ -7,6 +7,20 @@ export declare const MAX_REPORTED_FINDINGS = 50;
|
|
|
7
7
|
* refuses a run with more, so the scan never passes a run whose files it did not all read.
|
|
8
8
|
*/
|
|
9
9
|
export declare const MAX_RUN_ENTRIES = 10000;
|
|
10
|
+
/** What the public-safety scan did not read. A run with any of it cannot grade share_ready. */
|
|
11
|
+
export interface UnscannedArtifacts {
|
|
12
|
+
/** Run files it could not read as text, by run-relative path. */
|
|
13
|
+
readonly files: string[];
|
|
14
|
+
/** Folders it could not list, by run-relative path ("" is the run folder). */
|
|
15
|
+
readonly unlistedFolders: string[];
|
|
16
|
+
/** The folder it was listing when it reached MAX_RUN_ENTRIES. It read nothing listed after. */
|
|
17
|
+
stoppedIn?: string;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* The UNSCANNED_ARTIFACT reason: the files the scan could not read, the folders it could not list
|
|
21
|
+
* and where it stopped, each in its own sentence. Undefined when the scan read everything.
|
|
22
|
+
*/
|
|
23
|
+
export declare function unscannedMessage(unscanned: UnscannedArtifacts): string | undefined;
|
|
10
24
|
export declare function missingLocalEvidenceArtifacts(runPaths: PreparedRunArtifactPaths, bundle: RunBundle): Promise<string[]>;
|
|
11
25
|
/**
|
|
12
26
|
* The PNG frames that actor traces register as stream screenshots, relative to the run root.
|
|
@@ -50,9 +64,10 @@ interface RegisteredStreamMedia {
|
|
|
50
64
|
/**
|
|
51
65
|
* Scans every run file for secret and path patterns and returns the findings. A file that is not
|
|
52
66
|
* registered stream media and that the scan cannot read as text (readPlainText), cannot read at
|
|
53
|
-
* all, or that holds more than RUN_ARTIFACT_MAX_BYTES goes to `unscanned`, so the caller can
|
|
54
|
-
* the run from grading share_ready.
|
|
55
|
-
*
|
|
67
|
+
* all, or that holds more than RUN_ARTIFACT_MAX_BYTES goes to `unscanned.files`, so the caller can
|
|
68
|
+
* keep the run from grading share_ready. A directory it cannot list goes to
|
|
69
|
+
* `unscanned.unlistedFolders`, and the directory it was listing when it passed MAX_RUN_ENTRIES
|
|
70
|
+
* entries is `unscanned.stoppedIn`.
|
|
56
71
|
*/
|
|
57
|
-
export declare function scanRunPublicSafetyArtifacts(runPaths: PreparedRunArtifactPaths, derivedFindings: string[], media: RegisteredStreamMedia, unscanned:
|
|
72
|
+
export declare function scanRunPublicSafetyArtifacts(runPaths: PreparedRunArtifactPaths, derivedFindings: string[], media: RegisteredStreamMedia, unscanned: UnscannedArtifacts): Promise<string[]>;
|
|
58
73
|
export {};
|
package/dist/verify/artifacts.js
CHANGED
|
@@ -12,6 +12,7 @@ import { SANDBOX_RECEIPTS_ARTIFACT } from "../run/sandbox-receipts.js";
|
|
|
12
12
|
import { readSafeRunArtifact, readSafeRunArtifactBytes, readSafeRunArtifactJson, } from "../run/locate.js";
|
|
13
13
|
import { isRecord } from "../run/type-guards.js";
|
|
14
14
|
import { TERMINAL_EVENTS_ARTIFACT } from "../run/terminal-contract.js";
|
|
15
|
+
import { plural } from "../run/text.js";
|
|
15
16
|
import { isZeroEventTerminalTrace } from "./actor.js";
|
|
16
17
|
/** Public-safety and evidence-reference findings stop at this many per list. */
|
|
17
18
|
export const MAX_REPORTED_FINDINGS = 50;
|
|
@@ -20,6 +21,35 @@ export const MAX_REPORTED_FINDINGS = 50;
|
|
|
20
21
|
* refuses a run with more, so the scan never passes a run whose files it did not all read.
|
|
21
22
|
*/
|
|
22
23
|
export const MAX_RUN_ENTRIES = 10_000;
|
|
24
|
+
// The UNSCANNED_ARTIFACT reason names this many paths of each kind and counts the rest.
|
|
25
|
+
const MAX_LISTED_UNSCANNED = 10;
|
|
26
|
+
/**
|
|
27
|
+
* The UNSCANNED_ARTIFACT reason: the files the scan could not read, the folders it could not list
|
|
28
|
+
* and where it stopped, each in its own sentence. Undefined when the scan read everything.
|
|
29
|
+
*/
|
|
30
|
+
export function unscannedMessage(unscanned) {
|
|
31
|
+
const files = [...new Set(unscanned.files)].sort();
|
|
32
|
+
const folders = [...new Set(unscanned.unlistedFolders)].sort().map(folderName);
|
|
33
|
+
const sentences = [];
|
|
34
|
+
if (files.length > 0)
|
|
35
|
+
sentences.push(`The public-safety scan cannot read ${plural(files.length, "file")} as text that ${files.length === 1 ? "is not a stream screenshot under screenshots/ or a registered recording" : "are not stream screenshots under screenshots/ or registered recordings"}: ${listed(files)}.`);
|
|
36
|
+
if (folders.length > 0)
|
|
37
|
+
sentences.push(`The public-safety scan could not list ${plural(folders.length, "folder")}, so it read none of the files in ${folders.length === 1 ? "it" : "them"}: ${listed(folders)}.`);
|
|
38
|
+
if (unscanned.stoppedIn !== undefined)
|
|
39
|
+
sentences.push(`The public-safety scan stopped at its limit of ${MAX_RUN_ENTRIES} files and folders while listing ${folderName(unscanned.stoppedIn)}, so it read nothing listed after that point.`);
|
|
40
|
+
if (sentences.length === 0)
|
|
41
|
+
return undefined;
|
|
42
|
+
const onlyFiles = folders.length === 0 && unscanned.stoppedIn === undefined;
|
|
43
|
+
sentences.push(onlyFiles ? "Review them before sharing." : "Review the run folder before sharing.");
|
|
44
|
+
return sentences.join(" ");
|
|
45
|
+
}
|
|
46
|
+
function folderName(relativeDirectory) {
|
|
47
|
+
return relativeDirectory === "" ? "the run folder" : `${relativeDirectory}/`;
|
|
48
|
+
}
|
|
49
|
+
function listed(paths) {
|
|
50
|
+
const more = paths.length - MAX_LISTED_UNSCANNED;
|
|
51
|
+
return `${paths.slice(0, MAX_LISTED_UNSCANNED).join(", ")}${more > 0 ? ` and ${more} more` : ""}`;
|
|
52
|
+
}
|
|
23
53
|
export async function missingLocalEvidenceArtifacts(runPaths, bundle) {
|
|
24
54
|
const recordings = new Map(bundle.streams.flatMap((stream) => stream.recording ? [[stream.recording.path, stream.recording]] : []));
|
|
25
55
|
const requiredPaths = new Map();
|
|
@@ -291,9 +321,10 @@ export function rawScreenshotStreamIds(bundle, redactedShapeFrames) {
|
|
|
291
321
|
/**
|
|
292
322
|
* Scans every run file for secret and path patterns and returns the findings. A file that is not
|
|
293
323
|
* registered stream media and that the scan cannot read as text (readPlainText), cannot read at
|
|
294
|
-
* all, or that holds more than RUN_ARTIFACT_MAX_BYTES goes to `unscanned`, so the caller can
|
|
295
|
-
* the run from grading share_ready.
|
|
296
|
-
*
|
|
324
|
+
* all, or that holds more than RUN_ARTIFACT_MAX_BYTES goes to `unscanned.files`, so the caller can
|
|
325
|
+
* keep the run from grading share_ready. A directory it cannot list goes to
|
|
326
|
+
* `unscanned.unlistedFolders`, and the directory it was listing when it passed MAX_RUN_ENTRIES
|
|
327
|
+
* entries is `unscanned.stoppedIn`.
|
|
297
328
|
*/
|
|
298
329
|
export async function scanRunPublicSafetyArtifacts(runPaths, derivedFindings, media, unscanned) {
|
|
299
330
|
const findings = [];
|
|
@@ -315,14 +346,14 @@ async function scanRunPublicSafetyDirectory(runPaths, relativeDirectory, finding
|
|
|
315
346
|
: runPaths.physicalRunRoot;
|
|
316
347
|
const entries = await readdir(current).catch(() => null);
|
|
317
348
|
if (entries === null) {
|
|
318
|
-
unscanned.push(
|
|
349
|
+
unscanned.unlistedFolders.push(relativeDirectory);
|
|
319
350
|
return;
|
|
320
351
|
}
|
|
321
352
|
for (const entryName of entries) {
|
|
322
353
|
walk.entries += 1;
|
|
323
354
|
if (walk.entries > MAX_RUN_ENTRIES) {
|
|
324
|
-
|
|
325
|
-
|
|
355
|
+
// Every directory the walk returns through comes here once more; the first one stopped it.
|
|
356
|
+
unscanned.stoppedIn ??= relativeDirectory;
|
|
326
357
|
return;
|
|
327
358
|
}
|
|
328
359
|
const relativePath = relativeDirectory ? `${relativeDirectory}/${entryName}` : entryName;
|
|
@@ -371,7 +402,7 @@ async function scanRunPublicSafetyDirectory(runPaths, relativeDirectory, finding
|
|
|
371
402
|
const bytes = await readSafeRunArtifactBytes(runPaths, relativePath).catch(() => null);
|
|
372
403
|
const decoded = bytes === null ? undefined : readPlainText(bytes);
|
|
373
404
|
if (decoded === undefined || !decoded.ok) {
|
|
374
|
-
unscanned.push(relativePath);
|
|
405
|
+
unscanned.files.push(relativePath);
|
|
375
406
|
continue;
|
|
376
407
|
}
|
|
377
408
|
// A receipt's id in any other file, or a raw id at a sandbox-id key, which an older run or
|
|
@@ -389,7 +420,7 @@ async function scanRunPublicSafetyDirectory(runPaths, relativeDirectory, finding
|
|
|
389
420
|
selectedFindings.push(`sensitive text ${relativePath}`);
|
|
390
421
|
}
|
|
391
422
|
else if (scan.opaque) {
|
|
392
|
-
unscanned.push(relativePath);
|
|
423
|
+
unscanned.files.push(relativePath);
|
|
393
424
|
}
|
|
394
425
|
}
|
|
395
426
|
}
|
package/dist/verify/verify.js
CHANGED
|
@@ -13,18 +13,15 @@ import { SANDBOX_RECEIPTS_ARTIFACT } from "../run/sandbox-receipts.js";
|
|
|
13
13
|
import { isRecord } from "../run/type-guards.js";
|
|
14
14
|
import { runNotFoundMessage } from "../run/run-not-found.js";
|
|
15
15
|
import { actorVerdictConsistencyFindings, noEngagementActorFindings, validateCodexAppServerEvidence, validateTerminalProductEvidence, } from "./actor.js";
|
|
16
|
-
import { invalidRunEvidenceReferences, MAX_REPORTED_FINDINGS, missingLocalEvidenceArtifacts, rawScreenshotPostureWarnings, rawScreenshotStreamIds, redactedShapeFramePaths, scanRunPublicSafetyArtifacts, streamScreenshotPaths, } from "./artifacts.js";
|
|
16
|
+
import { invalidRunEvidenceReferences, MAX_REPORTED_FINDINGS, missingLocalEvidenceArtifacts, rawScreenshotPostureWarnings, rawScreenshotStreamIds, redactedShapeFramePaths, scanRunPublicSafetyArtifacts, streamScreenshotPaths, unscannedMessage, } from "./artifacts.js";
|
|
17
17
|
import { flatContextWarnings } from "./context-growth.js";
|
|
18
18
|
import { runNotFinished } from "./liveness.js";
|
|
19
19
|
import { costAndReceiptFindings } from "./costs.js";
|
|
20
20
|
import { rerunLineageFindings } from "./rerun.js";
|
|
21
21
|
import { sharedWorldEvidenceFindings } from "./shared-world.js";
|
|
22
22
|
import { subjectStateFindings, undeclaredSubjectStateWarnings } from "./subject.js";
|
|
23
|
-
import { plural } from "../run/text.js";
|
|
24
23
|
import { cli } from "../cli/invocation.js";
|
|
25
24
|
export const VERIFY_SCHEMA = "humanish.verify-result.v1";
|
|
26
|
-
// The UNSCANNED_ARTIFACT reason names this many paths and counts the rest.
|
|
27
|
-
const MAX_LISTED_UNSCANNED = 10;
|
|
28
25
|
export async function verifyRun(cwdInput, runInput) {
|
|
29
26
|
const cwd = path.resolve(cwdInput);
|
|
30
27
|
let runPaths;
|
|
@@ -86,7 +83,7 @@ export async function verifyResolvedRun(cwd, runInput, runPaths) {
|
|
|
86
83
|
};
|
|
87
84
|
checks.push(...bundlePresenceChecks(bundleRead, review));
|
|
88
85
|
const derivedPublicSafetyFindings = [];
|
|
89
|
-
const unscannedArtifacts = [];
|
|
86
|
+
const unscannedArtifacts = { files: [], unlistedFolders: [] };
|
|
90
87
|
const sandboxIdFiles = [];
|
|
91
88
|
let journaledSandboxIds = [];
|
|
92
89
|
try {
|
|
@@ -96,7 +93,7 @@ export async function verifyResolvedRun(cwd, runInput, runPaths) {
|
|
|
96
93
|
if (!(error instanceof ContainedReadRefusedError))
|
|
97
94
|
throw error;
|
|
98
95
|
// Without the journal's ids, verify cannot clear any file of naming one.
|
|
99
|
-
unscannedArtifacts.push(SANDBOX_RECEIPTS_ARTIFACT);
|
|
96
|
+
unscannedArtifacts.files.push(SANDBOX_RECEIPTS_ARTIFACT);
|
|
100
97
|
}
|
|
101
98
|
const publicSafetyFindings = await scanRunPublicSafetyArtifacts(runPaths, derivedPublicSafetyFindings, {
|
|
102
99
|
recordingPaths: new Set(isRunBundle(bundle)
|
|
@@ -397,15 +394,9 @@ function buildShareSafety(args) {
|
|
|
397
394
|
message: "Continuous screen/audio recordings are retained for local review. Screenshot redaction does not redact this media.",
|
|
398
395
|
});
|
|
399
396
|
}
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
const more = paths.length - MAX_LISTED_UNSCANNED;
|
|
404
|
-
reasons.push({
|
|
405
|
-
code: "UNSCANNED_ARTIFACT",
|
|
406
|
-
message: `The public-safety scan cannot read ${plural(paths.length, "file")} as text that ${paths.length === 1 ? "is not a stream screenshot under screenshots/ or a registered recording" : "are not stream screenshots under screenshots/ or registered recordings"}: ${shown}${more > 0 ? ` and ${more} more` : ""}. Review them before sharing.`,
|
|
407
|
-
});
|
|
408
|
-
}
|
|
397
|
+
const unscanned = unscannedMessage(args.unscannedArtifacts);
|
|
398
|
+
if (unscanned !== undefined)
|
|
399
|
+
reasons.push({ code: "UNSCANNED_ARTIFACT", message: unscanned });
|
|
409
400
|
// Runs from 0.110 keep raw sandbox ids only in sandbox-receipts.ndjson. An earlier run records
|
|
410
401
|
// them in run.json, and any file that names one is not share-ready as it is.
|
|
411
402
|
const rawResources = (args.bundle.providerResources ?? []).some((resource) => resource.id !== REDACTED_SANDBOX_ID);
|
|
@@ -65,7 +65,7 @@ scenario:
|
|
|
65
65
|
goal: "<scenario goal>"
|
|
66
66
|
source: "<scenario source>"
|
|
67
67
|
sourceDigest: "<sha256>"
|
|
68
|
-
simulations: [] # simCount entries, each paired with its streams
|
|
68
|
+
simulations: [] # simCount entries, each paired with its streams; `arrival` when the study declares starts
|
|
69
69
|
streams: []
|
|
70
70
|
events: []
|
|
71
71
|
artifacts:
|
|
@@ -305,15 +305,16 @@ exactly; this hosted-desktop rule does not change that contract.
|
|
|
305
305
|
`subject` is an optional, additive top-level field: structured provenance for
|
|
306
306
|
what the computer-use, shared-world or scripted route actually drove (code pin plus state story). It
|
|
307
307
|
is absent on pre-existing bundles and on bundles from routes that have not
|
|
308
|
-
adopted it. The field shape, its
|
|
309
|
-
`local-tree`, `desktop-cli`), and the `humanish verify` checks that guard it are the
|
|
310
|
-
job, not this one: see the `subject` entry under
|
|
308
|
+
adopted it. The field shape, its five sources (`clone`, `app-url`,
|
|
309
|
+
`local-tree`, `desktop-cli`, `local-app`), and the `humanish verify` checks that guard it are the
|
|
310
|
+
schema doc's job, not this one: see the `subject` entry under
|
|
311
311
|
[`schemas.md`](schemas.md#run-bundle). In short, `clone` carries a
|
|
312
312
|
`repo`/`commit` pin, `local-tree` carries an `archiveSha256`/`dirty` pin
|
|
313
313
|
instead (a dirty working tree cannot be commit-pinned), `desktop-cli` carries the
|
|
314
|
-
declared `product` name, and `app-url`
|
|
315
|
-
of a desktop-cli study saved before
|
|
316
|
-
|
|
314
|
+
declared `product` name, and `app-url` and `local-app` (a running app the caller drives in
|
|
315
|
+
process) carry no code pin at all. A computer-use run of a desktop-cli study saved before
|
|
316
|
+
`desktop-cli` was recorded, or of a local-app study saved before `local-app` was recorded, says
|
|
317
|
+
`app-url`, and readers accept it. No path, basename, or other host-machine string ever
|
|
317
318
|
enters this field; identity is digests, a sha, a boolean, counts and the declared product name.
|
|
318
319
|
|
|
319
320
|
## Cost Estimate (advisory)
|
|
@@ -183,7 +183,7 @@ The rest of this section names keys by their v3 spelling:
|
|
|
183
183
|
owns the live sandbox, auth, cap, evidence, and cleanup lifecycle.
|
|
184
184
|
A `participants` count is the preview route's participant count (simCount),
|
|
185
185
|
and on the computer-use **E2B** route the homogeneous fan-out count (N
|
|
186
|
-
identical participants, each its own E2B desktop: separate worlds,
|
|
186
|
+
identical participants, each its own E2B desktop: separate worlds, at most 100).
|
|
187
187
|
The scripted route's `surfaces` is its surface list (`[desktop]`, the default,
|
|
188
188
|
or `[desktop, mobile]`). The in-process/local-app computer-use route stays
|
|
189
189
|
single participant (no E2B to fan out);
|
|
@@ -197,7 +197,8 @@ The rest of this section names keys by their v3 spelling:
|
|
|
197
197
|
so `--count` does not apply to it, and each entry's `instruction` is the steer;
|
|
198
198
|
an entry's `device` is XOR with a raw `execution.desktop.resolution`. Participant ids
|
|
199
199
|
default `lane-01`..`lane-NN` (`role-01`..`role-NN` for shared-world participants), must be unique, and name per-participant evidence paths
|
|
200
|
-
(`actors/<streamId>.json`, `screenshots/<laneId>/`).
|
|
200
|
+
(`actors/<streamId>.json`, `screenshots/<laneId>/`). At most 100 participants, on the
|
|
201
|
+
shared-world route too. The other
|
|
201
202
|
routes refuse a list (table above). `subject.clone.fanout` is rejected on the
|
|
202
203
|
computer-use, shared-world and scripted routes (declare fan-out with
|
|
203
204
|
`participants`). No current route reads `clone.fanout`;
|
|
@@ -214,11 +215,26 @@ The rest of this section names keys by their v3 spelling:
|
|
|
214
215
|
`<id>-01` … `<id>-NN`, even when n is 1, before the engine runs (`viewer-01`,
|
|
215
216
|
`viewer-02`, ...), so the runtime and run bundle keep one normalized participant
|
|
216
217
|
shape. Ids are checked for collisions after expansion;
|
|
218
|
+
- `participants[].startAfterMs` (computer-use and shared-world): when the participant
|
|
219
|
+
starts, in whole milliseconds after the run starts its participants, from 0 to 86,400,000
|
|
220
|
+
(24 hours). A `count` group's `startEveryMs` gives member k the start
|
|
221
|
+
`startAfterMs + (k - 1) * startEveryMs`, expanded at parse time like the ids. Refused: an
|
|
222
|
+
interval without `count`, a member past 24 hours, a start on a study of one participant, and a
|
|
223
|
+
later start for the external-public `host`. Participants start in order of their starts, each
|
|
224
|
+
at its time or when a slot frees under `execution.concurrency` and the E2B plan's limit,
|
|
225
|
+
whichever is later (`runOnSchedule` in `src/study/arrivals.ts`). Each participant's desktop is
|
|
226
|
+
created when it starts. The plan (`arrivals`, `planArrivals`) reports the first and last start
|
|
227
|
+
and the most participants at once with every session at its full budget, and the provisioned
|
|
228
|
+
shared world's subject sandbox serves until the last session can end, which the plan checks
|
|
229
|
+
against the sandbox ceiling. A rerun starts its selected participants together;
|
|
217
230
|
- `execution.concurrency` (computer-use E2B routes, including shared-world): a
|
|
218
231
|
cap on participants in flight at once. When omitted, every participant runs
|
|
219
|
-
simultaneously:
|
|
220
|
-
after any `--count` override
|
|
221
|
-
|
|
232
|
+
simultaneously: both routes resolve it when they plan, from the final participant count
|
|
233
|
+
after any `--count` override. On hosted desktops the plan runs at most
|
|
234
|
+
`HUMANISH_E2B_MAX_CONCURRENT_SANDBOXES` sandboxes at once (20 when unset; a provisioned
|
|
235
|
+
shared world's app sandbox counts against it), so a larger roster runs in waves and the plan
|
|
236
|
+
records a warning naming the setting. A declared value above that limit is refused at plan
|
|
237
|
+
time. Total sessions and spend are identical either way; only wall-clock
|
|
222
238
|
and simultaneity differ. Declaring a value below the participant count runs participants in
|
|
223
239
|
waves and emits a warning saying so, because a green waved run is otherwise
|
|
224
240
|
indistinguishable from the all-live run the author meant. Shared-world studies
|
|
@@ -512,7 +528,13 @@ Core-owned fields:
|
|
|
512
528
|
- `artifactRoot`
|
|
513
529
|
- `source.git`
|
|
514
530
|
- `lifecycle`
|
|
515
|
-
- `simulations
|
|
531
|
+
- `simulations`: one record per participant. On the computer-use and shared-world routes, a
|
|
532
|
+
study that declares `participants[].startAfterMs` gives each record `arrival:
|
|
533
|
+
{ startAfterMs, scheduledAt?, startedAt? }`: the participant's offset (0 when its entry declares
|
|
534
|
+
none), the moment the run started its participants plus that offset, and when its desktop was
|
|
535
|
+
requested, which is later than `scheduledAt` when it waited for a free slot. A dry run records
|
|
536
|
+
only the offset, and a participant that never started has no `startedAt`. The record's own
|
|
537
|
+
`startedAt` is the run's creation time.
|
|
516
538
|
- `streams`
|
|
517
539
|
- `events`
|
|
518
540
|
- `redaction`
|
|
@@ -529,7 +551,7 @@ Core-owned fields:
|
|
|
529
551
|
an older run, else `inferLegacyStudyId`, which reads the `persona.source` or `scenario.source`
|
|
530
552
|
convention `study:<id>`, or `lab:<id>` from runs written before 0.108, and nothing else.
|
|
531
553
|
- `subject` (optional, additive): structured subject provenance,
|
|
532
|
-
`{ source: clone | app-url | local-tree | desktop-cli, repo?, product?, commit?,
|
|
554
|
+
`{ source: clone | app-url | local-tree | desktop-cli | local-app, repo?, product?, commit?,
|
|
533
555
|
archiveSha256?, dirty?, envNames?, state }` where `state` is `{ provenance: seeded |
|
|
534
556
|
unpinned | declared-not-run | undeclared | external-public, seed?: [{ name, when,
|
|
535
557
|
commandDigest, ok?, exitCode?, timedOut?, durationMs? }], externalEnvNames?
|
|
@@ -541,7 +563,9 @@ Core-owned fields:
|
|
|
541
563
|
the archive content digest stands in for it. `product` is the desktop-cli field: the
|
|
542
564
|
`subject.product.name` the study declares, with state `undeclared`. A computer-use
|
|
543
565
|
run of a desktop-cli study saved before this field existed records `app-url` with no
|
|
544
|
-
`product`. `
|
|
566
|
+
`product`. `local-app` is an in-process run of a local-app study, the caller's running app
|
|
567
|
+
with no code pin and state `undeclared`; one saved before that value existed records
|
|
568
|
+
`app-url`. `commandDigest` is the
|
|
545
569
|
sha256-16 of the exact seed command; command text and env values never
|
|
546
570
|
appear. `humanish verify` fails closed when a live `local-tree` bundle carries
|
|
547
571
|
no well-formed `archiveSha256`, in addition to the existing `subject state
|
|
@@ -1676,7 +1700,7 @@ of it fails.
|
|
|
1676
1700
|
| `cuaLaneCount` | the `plan` `StudyEvent`, which lists every participant |
|
|
1677
1701
|
| `resolveSeatUrl` | `parseStudy`, which refuses a participant entry that is not same-origin loopback; build the URL with `new URL(entry, serveUrl)` |
|
|
1678
1702
|
| `cuaLaneValidationReason`, `sharedWorldValidationReason`, `concurrentSharedWorldValidationReason`, `externalPublicSharedWorldValidationReason` | `parseStudy`, which returns the same reason |
|
|
1679
|
-
| `MAX_CUA_LANES` | none; `parseStudy` refuses a roster of more than
|
|
1703
|
+
| `MAX_CUA_LANES` | none; `parseStudy` refuses a roster of more than 100 participants |
|
|
1680
1704
|
|
|
1681
1705
|
## Product-Adapter Extension Seam
|
|
1682
1706
|
|
|
@@ -27,8 +27,8 @@ The default provider is `openai`; its default model is `gpt-6-astra`, with high
|
|
|
27
27
|
captures to OpenAI, without tools, redirects, provider-side response storage, or
|
|
28
28
|
automatic retries. `--question` adds a reviewer question; it never changes the
|
|
29
29
|
participant assignment. For OpenAI API analysis, `--max-cost` is required, including for dry-run
|
|
30
|
-
admission. Admission
|
|
31
|
-
provider bill is not limited by it.
|
|
30
|
+
admission. Admission refuses the request when both its worst case and its expected cost plus a
|
|
31
|
+
10% margin are over it; the provider bill is not limited by it.
|
|
32
32
|
`--timeout-ms` and `--max-output-tokens` bound the request. A bill above the
|
|
33
33
|
worst case or the cap retains valid findings and usage but returns a partial
|
|
34
34
|
result and a nonzero command exit, including when that version is reused.
|
|
@@ -37,17 +37,27 @@ The estimate counts the instructions, evidence packet and result schema at 3
|
|
|
37
37
|
UTF-8 bytes per input token, adds 2,048 framing tokens and each capture's
|
|
38
38
|
high-detail image tokens, and prices input at the model's highest input rate.
|
|
39
39
|
The expected output is 12,000 tokens plus 1,000 per participant, at most the
|
|
40
|
-
output allowance; the worst case spends the whole allowance.
|
|
40
|
+
output allowance; the worst case spends the whole allowance. A run with more than
|
|
41
|
+
16 participants sends one request per cohort and a merge request
|
|
42
|
+
([runs with more than 16 participants](#runs-with-more-than-16-participants)),
|
|
43
|
+
and the estimate is their sum, each request priced on its own long-context tier.
|
|
44
|
+
The merge request's input is its instructions, packet and schema, counted the same
|
|
45
|
+
way, plus each cohort's report at that request's whole output allowance; it is
|
|
46
|
+
expected to write what one request covering every participant would. `admission` in
|
|
41
47
|
`--json` output has `estimatedCostUsd` (the expected cost), `worstCaseCostUsd`,
|
|
42
48
|
`admittedCostUsd` (the figure compared with the cap: the expected cost plus 10%,
|
|
43
49
|
or the worst case when that is lower), `maxCostUsd`, `inputTokenAllowance` (the
|
|
44
|
-
input tokens priced)
|
|
50
|
+
input tokens priced), `outputTokenAllowance` (each request's) and `requests` (the
|
|
51
|
+
requests the costs cover: 1, or each cohort's and the merge request; null when the
|
|
52
|
+
input or configuration was refused). The dry run's worst case for more than one
|
|
53
|
+
request says every request writes its whole allowance and names the cohort and merge
|
|
54
|
+
requests, in the words the plan's analysis line uses. On 148 billed gpt-6-astra
|
|
45
55
|
analyses the expected cost was 1.08 to 7.3 times the bill, and 1.08 to 1.32
|
|
46
56
|
times for those billed $1 or more.
|
|
47
57
|
A refusal's message gives the expected cost, the worst case, the cap, and
|
|
48
58
|
`humanish analyze --run <id> --max-cost <n>` with `n` the worst case rounded up.
|
|
49
59
|
|
|
50
|
-
The default deadline is ten minutes. With no explicit output-token limit, analysis
|
|
60
|
+
The default deadline is ten minutes for each request. With no explicit output-token limit, analysis
|
|
51
61
|
uses 32,768 tokens if admission fits the declared budget, or retains the prior
|
|
52
62
|
16,384-token allowance otherwise. Explicit limits are never adjusted. Selection
|
|
53
63
|
happens before dispatch; `admission.outputTokenAllowance` exposes it in dry-run,
|
|
@@ -64,6 +74,48 @@ success. Findings keep reported concerns and observed recovery distinct across
|
|
|
64
74
|
participants. Other supported models can be selected explicitly, but evidence
|
|
65
75
|
reference validation does not certify their interpretation of small visual details.
|
|
66
76
|
|
|
77
|
+
## Runs with more than 16 participants
|
|
78
|
+
|
|
79
|
+
One analysis covers up to 128 participants, the most streams it reads from a run
|
|
80
|
+
bundle. A request covers at most 16 (`EVIDENCE_LIMITS.cohortParticipants` in
|
|
81
|
+
`src/analysis/analysis-limits.ts`). A run with more is split into the fewest
|
|
82
|
+
cohorts of at most 16, with participants dealt in roster order: 17 participants
|
|
83
|
+
make cohorts of 9 and 8, 24 make two of 12, 40 make 14, 13 and 13, and 100 make
|
|
84
|
+
seven. Each cohort's packet is selected under the packet limits below on its own,
|
|
85
|
+
so each participant keeps at least the evidence a 16-participant run gives it.
|
|
86
|
+
Evidence IDs are numbered across the whole run, so they stay unique.
|
|
87
|
+
|
|
88
|
+
Each cohort request uses the standard instructions, schema and validation, with
|
|
89
|
+
only its own participants' evidence and captures. Up to four run at once for
|
|
90
|
+
OpenAI, which keeps four requests at the packet limits under the lowest paid
|
|
91
|
+
tier's gpt-6-astra rate limit of 1,000,000 tokens a minute; Codex runs one at a
|
|
92
|
+
time. Then one merge request reads the cohort reports, never the evidence or the
|
|
93
|
+
captures. It writes the summary, findings, design findings, concern reviews and
|
|
94
|
+
limitations for the whole run; the participant reviews are the cohorts' own.
|
|
95
|
+
Its findings, design findings and concern reviews may cite only evidence that a
|
|
96
|
+
cohort report cites in its own findings, design findings or concern reviews
|
|
97
|
+
(`analysis_validation_failed_merge_reference_invalid` otherwise), and the merged
|
|
98
|
+
report passes the same reference, membership and basis checks against the run's
|
|
99
|
+
whole packet as a single request's report does.
|
|
100
|
+
|
|
101
|
+
The attempt has one execution start, one receipt and one `analysis.json`, with the
|
|
102
|
+
run's participants, coverage and evidence and the summed usage of every request
|
|
103
|
+
sent. The artifact schema is unchanged. Its stored limits grew to hold eight
|
|
104
|
+
cohorts: 6,400 evidence items, 320 captures and 16 MiB. If a cohort request fails
|
|
105
|
+
or is rejected, no further request starts, requests already sent finish, no merge
|
|
106
|
+
request is sent and the attempt fails with that request's code, with no findings;
|
|
107
|
+
a warning names the cohort's size. A failed merge request fails the attempt the same way. The usage
|
|
108
|
+
still counts every request sent. Findings from part of the participants are not
|
|
109
|
+
kept as a report on the run.
|
|
110
|
+
|
|
111
|
+
The first live cohort analysis, on a 24-participant computer-use run, sent two
|
|
112
|
+
cohort requests of 12 participants and the merge request in 5 minutes 6 seconds.
|
|
113
|
+
Its expected cost was $6.08, 2.3 times its $2.65 bill, and each of its 447
|
|
114
|
+
citations resolved in the run's packet. A second analysis of the same run, with
|
|
115
|
+
the current merge instructions, billed $1.92, most of its input served from the
|
|
116
|
+
provider's prompt cache, and each of its 466 citations resolved. Two runs do not
|
|
117
|
+
calibrate the merge request's estimate.
|
|
118
|
+
|
|
67
119
|
## Explicit Codex account analysis
|
|
68
120
|
|
|
69
121
|
An existing completed recording can use a separate restricted Codex analyst:
|
|
@@ -85,8 +137,9 @@ converted to API authentication.
|
|
|
85
137
|
Omit `--max-cost` and `--max-output-tokens` for Codex. Numeric declarations are
|
|
86
138
|
rejected because this transport does not enforce them. `maxCostUsd` and
|
|
87
139
|
`maxOutputTokens` are stored as null. The existing evidence bounds, one analyst
|
|
88
|
-
turn
|
|
89
|
-
is not a claim of one upstream
|
|
140
|
+
turn per request (one per cohort, and the merge request), bounded response bytes
|
|
141
|
+
and whole-operation timeout still apply. One turn is not a claim of one upstream
|
|
142
|
+
billed request; account limits apply. Reported
|
|
90
143
|
tokens remain inspectable, but dollar estimates, admission dollars and rate dates
|
|
91
144
|
remain null. Interrupted token observations remain explicitly incomplete.
|
|
92
145
|
|
|
@@ -128,7 +181,7 @@ stopped, an operator can remove the empty lock directory and retry.
|
|
|
128
181
|
|
|
129
182
|
## Evidence and findings
|
|
130
183
|
|
|
131
|
-
|
|
184
|
+
Each request's packet admits up to 16 participants, 800 evidence items, 40 PNG
|
|
132
185
|
captures, 160 KiB of text and 20 MiB of images. Individual source files, image
|
|
133
186
|
dimensions and result sizes have separate limits. Count and text budgets are
|
|
134
187
|
distributed across included participants, with unused capacity from short
|
|
@@ -87,20 +87,20 @@ Right most of the time, overridable with declared friction (an explicit config f
|
|
|
87
87
|
env gate, a warning in the run output). Overriding a default is a supported use case;
|
|
88
88
|
silently drifting from one is not.
|
|
89
89
|
|
|
90
|
-
| Default | Why it is the default | Legitimate override
|
|
91
|
-
| ------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
92
|
-
| Dry-run | Spend safety (the live-spend rule sets the floor; dry-run keeps it far away) | `mode: live`
|
|
93
|
-
| Post-run analysis on supported live studies | Findings accompany the recording; a separate $3 cap on its expected cost is visible before execution | `review.analysis: false` disables it; an explicit mapping sets another analysis budget. Dry-run and unsupported routes never dispatch; missing default credentials produce a recorded skip
|
|
94
|
-
| Separate worlds (`per-lane-worlds`) | Isolation, attribution, reproducibility | `route: shared-world`: N participants against one mutable plane for scenarios that are about interaction between participants.
|
|
95
|
-
| External key placement | Smallest blast radius: when the keyed process (e.g. a computer-use provider loop) runs outside the sandbox, its key never enters | In-sandbox placement when the keyed process runs inside (an agent harness under test); declared per actor type, with a spend budget
|
|
96
|
-
| Loopback entry URLs | Public-safety: never drive third-party sites unbidden | `policies.allowPublicTargets` for an owner-declared deployment/preview (a Vercel preview of your own app). Multi-participant public/preview fan-out needs explicit `participants[].target` for every participant, so the adapter-owned topology is declared rather than inferred. Provisioned clone subjects always serve in-sandbox on loopback
|
|
97
|
-
| Full-fidelity screenshots, local | The common case is watching a run of your own app locally; blur destroys the deliverable. Raw frames live in gitignored `.humanish/` (this repo's CI adds a binary-asset commit scan; downstream projects rely on the scaffolded `.gitignore` and their own review) | `policies.redactScreenshots: true` blurs at capture; `export --format bundle --redact-screenshots` produces a separate verified copy of a completed run
|
|
98
|
-
| Synthetic, seeded state | Pinned provenance; no real user data in evidence paths | Declared external state, recorded as unpinned in provenance
|
|
99
|
-
| Single participant | Cost + evidence simplicity | Declared fan-out where the route supports it: `participants: N` (homogeneous), an explicit `participants` list (differentiated persona/device/instruction), or list entries with a `count` that expand into one entry per participant on the computer-use E2B route (separate worlds,
|
|
100
|
-
| Stock `desktop` template | The stock E2B desktop image is right for most subjects; absent `execution.desktop.template` keeps `Sandbox.create(opts)` byte-stable | `execution.desktop.template` names a custom E2B desktop image (any name/id, no allowlist) for a subject needing baked-in runtimes the stock image lacks (e.g. node/bun/a local Postgres); it is threaded to `Sandbox.create(template, opts)` on every desktop-creating route and recorded in the bundle as `desktopTemplate` (public-safe)
|
|
101
|
-
| Desktop default URL opener | Preserve the route/image's historical browser/default opener behavior when unset | `execution.desktop.browser` set to `chrome`, `chromium` or `firefox` makes the hosted CUA/shared-world browser choice explicit, fail-closed, and recorded as `desktopBrowser` in the run bundle
|
|
102
|
-
| Loopback-only observer serving | Run bundles are local by default (public-safety plank 3); the serve/watch surfaces bind `127.0.0.1` unconditionally, and exposing them is publishing-adjacent, so exposure is fail-closed and carries declared friction | `humanish observe --all --expose` behind tunnel-edge auth (ngrok `--oauth google` with `--allow-email`/`--allow-domain` allow rules, or an operator `--public-url` you secure), or `--expose --safe` (open, gated on verify-`share_ready` runs only). `watch --expose` streams a live run behind edge auth (a live run is never `share_ready`, so `--safe` alone is refused). humanish carries no in-process auth; both print computed warnings naming exactly what became reachable
|
|
103
|
-
| Generous wall-clock safety cap | `execution.timeoutMs` bounds runaway spend, not a goal; ~30 min is reasonable for open-ended "watch it play" | A session that reaches the cap after productive activity is recorded as `budget_reached` (status `passed`, a non-failure completion, exit `0`), distinct from a zero-progress `timed_out` (a failure, exit `2`). Goal-directed studies set a tight `timeoutMs` and read `completionReason` to confirm the goal, not the cap
|
|
90
|
+
| Default | Why it is the default | Legitimate override |
|
|
91
|
+
| ------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
92
|
+
| Dry-run | Spend safety (the live-spend rule sets the floor; dry-run keeps it far away) | `mode: live` |
|
|
93
|
+
| Post-run analysis on supported live studies | Findings accompany the recording; a separate $3 cap on its expected cost is visible before execution | `review.analysis: false` disables it; an explicit mapping sets another analysis budget. Dry-run and unsupported routes never dispatch; missing default credentials produce a recorded skip |
|
|
94
|
+
| Separate worlds (`per-lane-worlds`) | Isolation, attribution, reproducibility | `route: shared-world`: N participants against one mutable plane for scenarios that are about interaction between participants. Participants run at once, as many as `execution.concurrency` (two or more) and the E2B plan's limit allow, unless the study declares when each one starts (`participants[].startAfterMs`). The plane is one getHost-exposed subject sandbox with synthetic seeded data, or an operator-owned public deployment (external-public), driven by N actor sandboxes. The bundle declares the weaker `attributionClass: shared-world` and a verify-enforced `attributionLimits` ceiling (`best-effort-causal-attribution` and the rest of the concurrent set). Sequential turns at `execution.concurrency: 1` were removed in 0.106.0. |
|
|
95
|
+
| External key placement | Smallest blast radius: when the keyed process (e.g. a computer-use provider loop) runs outside the sandbox, its key never enters | In-sandbox placement when the keyed process runs inside (an agent harness under test); declared per actor type, with a spend budget |
|
|
96
|
+
| Loopback entry URLs | Public-safety: never drive third-party sites unbidden | `policies.allowPublicTargets` for an owner-declared deployment/preview (a Vercel preview of your own app). Multi-participant public/preview fan-out needs explicit `participants[].target` for every participant, so the adapter-owned topology is declared rather than inferred. Provisioned clone subjects always serve in-sandbox on loopback |
|
|
97
|
+
| Full-fidelity screenshots, local | The common case is watching a run of your own app locally; blur destroys the deliverable. Raw frames live in gitignored `.humanish/` (this repo's CI adds a binary-asset commit scan; downstream projects rely on the scaffolded `.gitignore` and their own review) | `policies.redactScreenshots: true` blurs at capture; `export --format bundle --redact-screenshots` produces a separate verified copy of a completed run |
|
|
98
|
+
| Synthetic, seeded state | Pinned provenance; no real user data in evidence paths | Declared external state, recorded as unpinned in provenance |
|
|
99
|
+
| Single participant | Cost + evidence simplicity | Declared fan-out where the route supports it: `participants: N` (homogeneous), an explicit `participants` list (differentiated persona/device/instruction), or list entries with a `count` that expand into one entry per participant on the computer-use E2B route (separate worlds, at most 100; `execution.concurrency` and the E2B plan bound concurrent paid participants) |
|
|
100
|
+
| Stock `desktop` template | The stock E2B desktop image is right for most subjects; absent `execution.desktop.template` keeps `Sandbox.create(opts)` byte-stable | `execution.desktop.template` names a custom E2B desktop image (any name/id, no allowlist) for a subject needing baked-in runtimes the stock image lacks (e.g. node/bun/a local Postgres); it is threaded to `Sandbox.create(template, opts)` on every desktop-creating route and recorded in the bundle as `desktopTemplate` (public-safe) |
|
|
101
|
+
| Desktop default URL opener | Preserve the route/image's historical browser/default opener behavior when unset | `execution.desktop.browser` set to `chrome`, `chromium` or `firefox` makes the hosted CUA/shared-world browser choice explicit, fail-closed, and recorded as `desktopBrowser` in the run bundle |
|
|
102
|
+
| Loopback-only observer serving | Run bundles are local by default (public-safety plank 3); the serve/watch surfaces bind `127.0.0.1` unconditionally, and exposing them is publishing-adjacent, so exposure is fail-closed and carries declared friction | `humanish observe --all --expose` behind tunnel-edge auth (ngrok `--oauth google` with `--allow-email`/`--allow-domain` allow rules, or an operator `--public-url` you secure), or `--expose --safe` (open, gated on verify-`share_ready` runs only). `watch --expose` streams a live run behind edge auth (a live run is never `share_ready`, so `--safe` alone is refused). humanish carries no in-process auth; both print computed warnings naming exactly what became reachable |
|
|
103
|
+
| Generous wall-clock safety cap | `execution.timeoutMs` bounds runaway spend, not a goal; ~30 min is reasonable for open-ended "watch it play" | A session that reaches the cap after productive activity is recorded as `budget_reached` (status `passed`, a non-failure completion, exit `0`), distinct from a zero-progress `timed_out` (a failure, exit `2`). Goal-directed studies set a tight `timeoutMs` and read `completionReason` to confirm the goal, not the cap |
|
|
104
104
|
|
|
105
105
|
## The placement rule (worked example)
|
|
106
106
|
|
|
@@ -23,12 +23,19 @@ review:
|
|
|
23
23
|
Omitting `review.analysis` uses these defaults. Set `review.analysis: false` to
|
|
24
24
|
run participants without the additional analysis request. An explicit analysis
|
|
25
25
|
mapping using the default OpenAI API provider requires `maxCostUsd`. Admission
|
|
26
|
-
|
|
27
|
-
provider's final bill, and it is separate from participant spending limits. The
|
|
26
|
+
refuses the request when both its worst case and its expected cost plus a 10% margin are over
|
|
27
|
+
it; it does not limit the provider's final bill, and it is separate from participant spending limits. The
|
|
28
28
|
estimate gives two numbers: the expected cost, and the worst case if the analyst
|
|
29
29
|
spends its whole output allowance. `humanish study check` gives the expected cost
|
|
30
30
|
range for the study's participant count, from a run that keeps no evidence to one
|
|
31
|
-
|
|
31
|
+
that keeps the most evidence admission admits under the cap, at most the evidence limits. When
|
|
32
|
+
the cap admits no analysis at all, it says so instead. A study of more than 16 participants is
|
|
33
|
+
analysed in cohorts of at most 16, one request each, and one more request merges their reports;
|
|
34
|
+
the range covers every request and the analysis line names them (see
|
|
35
|
+
[runs with more than 16 participants](../contracts/study-analysis.md#runs-with-more-than-16-participants)).
|
|
36
|
+
The default $3 cap refuses the analysis of 17 participants even for a run that keeps no evidence,
|
|
37
|
+
so a default analysis of more than 16 participants is skipped with
|
|
38
|
+
`AUTOMATIC_ANALYSIS_ADMISSION_REFUSED` unless `review.analysis.maxCostUsd` admits it. Use `analyze --dry-run --max-cost <usd>` on retained
|
|
32
39
|
evidence to inspect the expected cost, the worst case and the selected token
|
|
33
40
|
allowance before deliberately choosing a larger budget. Explicit
|
|
34
41
|
`maxOutputTokens` and `--max-output-tokens` limits are honored exactly. The output allowance
|