humanish 0.118.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.
Files changed (78) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/dist/analysis/admission.d.ts +16 -2
  3. package/dist/analysis/admission.js +35 -6
  4. package/dist/analysis/analysis-limits.d.ts +32 -0
  5. package/dist/analysis/analysis-limits.js +41 -3
  6. package/dist/analysis/automatic-config.d.ts +5 -0
  7. package/dist/analysis/automatic-config.js +15 -1
  8. package/dist/analysis/cohorts.d.ts +103 -0
  9. package/dist/analysis/cohorts.js +80 -0
  10. package/dist/analysis/evidence.d.ts +5 -10
  11. package/dist/analysis/evidence.js +37 -34
  12. package/dist/analysis/execute.d.ts +7 -2
  13. package/dist/analysis/execute.js +242 -297
  14. package/dist/analysis/responses.d.ts +19 -0
  15. package/dist/analysis/responses.js +201 -0
  16. package/dist/analysis/store.js +2 -1
  17. package/dist/analysis/validation.js +13 -7
  18. package/dist/cli/commands/analyze.js +8 -3
  19. package/dist/cli/commands/study-route-computer-use.js +2 -2
  20. package/dist/cli/commands/study-route-scripted.js +2 -2
  21. package/dist/cli/commands/study-route-shared-world.js +2 -2
  22. package/dist/cli/commands/study-route-terminal.js +2 -2
  23. package/dist/cli/commands/study-run.js +3 -3
  24. package/dist/cli/commands/study.js +2 -2
  25. package/dist/cli/io.js +2 -3
  26. package/dist/observer-app.html +1 -1
  27. package/dist/routes/computer-use/fanout-bundle.js +2 -1
  28. package/dist/routes/computer-use/fanout-records.js +4 -0
  29. package/dist/routes/computer-use/participant-execution.d.ts +6 -4
  30. package/dist/routes/computer-use/participant-execution.js +42 -14
  31. package/dist/routes/computer-use/participant-model.js +18 -8
  32. package/dist/routes/computer-use/participant-runs.js +12 -0
  33. package/dist/routes/computer-use/plan.js +23 -15
  34. package/dist/routes/computer-use/rerun-selection.js +14 -3
  35. package/dist/routes/computer-use/types.d.ts +13 -1
  36. package/dist/routes/shared-world/bundle-records.js +14 -0
  37. package/dist/routes/shared-world/bundle.js +2 -1
  38. package/dist/routes/shared-world/external-public.js +15 -3
  39. package/dist/routes/shared-world/handoff.d.ts +2 -2
  40. package/dist/routes/shared-world/plan.d.ts +7 -0
  41. package/dist/routes/shared-world/plan.js +79 -34
  42. package/dist/routes/shared-world/provisioned.js +15 -8
  43. package/dist/routes/shared-world/route.js +5 -2
  44. package/dist/routes/shared-world/types.d.ts +2 -0
  45. package/dist/run/bundle-shape.js +10 -1
  46. package/dist/run/bundle.d.ts +15 -0
  47. package/dist/run/participant-records.js +1 -0
  48. package/dist/run/route-shell.js +5 -10
  49. package/dist/run/run.js +4 -2
  50. package/dist/study/arrivals.d.ts +114 -0
  51. package/dist/study/arrivals.js +204 -0
  52. package/dist/study/doctor.js +2 -15
  53. package/dist/study/keys.d.ts +1 -0
  54. package/dist/study/keys.js +2 -0
  55. package/dist/study/migrate/v2.js +3 -2
  56. package/dist/study/parse/actors.d.ts +1 -1
  57. package/dist/study/parse/actors.js +9 -3
  58. package/dist/study/parse/front.js +29 -1
  59. package/dist/study/plan-base.d.ts +1 -3
  60. package/dist/study/plan-base.js +0 -3
  61. package/dist/study/plan-participants.d.ts +2 -0
  62. package/dist/study/plan-participants.js +1 -0
  63. package/dist/study/plan-types.d.ts +8 -3
  64. package/dist/study/preflight-result.js +2 -2
  65. package/dist/study/preflight.js +12 -0
  66. package/dist/study/summary.d.ts +3 -3
  67. package/dist/study/summary.js +5 -3
  68. package/dist/study/types.d.ts +14 -0
  69. package/dist/study/validation.js +5 -0
  70. package/dist/study/warnings.js +11 -3
  71. package/docs/contracts/run-bundle.md +1 -1
  72. package/docs/contracts/schemas.md +19 -1
  73. package/docs/contracts/study-analysis.md +59 -6
  74. package/docs/principles/invariants-and-defaults.md +14 -14
  75. package/docs/product/automatic-analysis.md +7 -1
  76. package/package.json +1 -1
  77. package/dist/study/automatic-analysis-plan.d.ts +0 -28
  78. package/dist/study/automatic-analysis-plan.js +0 -57
@@ -302,11 +302,19 @@ export function forwardDeclaredWarnings(config) {
302
302
  ];
303
303
  // A declared cap below the participant count is legal but loud: the roster promises N live actors
304
304
  // and the cap delivers waves of M. Say so up front (inspect + dry-run + run): a green run in
305
- // waves is otherwise indistinguishable from the all-live run the author meant.
305
+ // waves is otherwise indistinguishable from the all-live run the author meant. A roster that
306
+ // declares when its participants start never promised them all at once; the plan's schedule
307
+ // says who waits for a slot.
306
308
  {
307
- const participantCount = participantList(config)?.length ?? declaredParticipantCount(config) ?? 1;
309
+ const roster = participantList(config);
310
+ const participantCount = roster?.length ?? declaredParticipantCount(config) ?? 1;
308
311
  const cap = config.execution?.concurrency;
309
- if (routes.cua && cap !== undefined && participantCount > 1 && cap < participantCount) {
312
+ const scheduled = roster?.some((entry) => entry.startAfterMs !== undefined) === true;
313
+ if (routes.cua &&
314
+ !scheduled &&
315
+ cap !== undefined &&
316
+ participantCount > 1 &&
317
+ cap < participantCount) {
310
318
  warnings.push(`execution.concurrency ${cap} caps a ${participantCount}-participant roster: participants run in waves of ${cap}, never all live at once. Remove execution.concurrency (the default runs all ${participantCount} participants simultaneously) or set it to ${participantCount}; declare a lower cap only to bound simultaneous paid desktops.`);
311
319
  }
312
320
  }
@@ -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:
@@ -215,6 +215,18 @@ The rest of this section names keys by their v3 spelling:
215
215
  `<id>-01` … `<id>-NN`, even when n is 1, before the engine runs (`viewer-01`,
216
216
  `viewer-02`, ...), so the runtime and run bundle keep one normalized participant
217
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;
218
230
  - `execution.concurrency` (computer-use E2B routes, including shared-world): a
219
231
  cap on participants in flight at once. When omitted, every participant runs
220
232
  simultaneously: both routes resolve it when they plan, from the final participant count
@@ -516,7 +528,13 @@ Core-owned fields:
516
528
  - `artifactRoot`
517
529
  - `source.git`
518
530
  - `lifecycle`
519
- - `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.
520
538
  - `streams`
521
539
  - `events`
522
540
  - `redaction`
@@ -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. `admission` in
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) and `outputTokenAllowance`. On 148 billed gpt-6-astra
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, bounded response bytes and whole-operation timeout still apply. One turn
89
- is not a claim of one upstream billed request; account limits apply. Reported
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
- The packet currently admits up to 16 participants, 800 evidence items, 40 PNG
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. Every participant runs at once: `execution.concurrency` must be two or more, and an omitted value fills to the number of participants. 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 |
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
 
@@ -29,7 +29,13 @@ 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. Use `analyze --dry-run --max-cost <usd>` on retained
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
33
39
  evidence to inspect the expected cost, the worst case and the selected token
34
40
  allowance before deliberately choosing a larger budget. Explicit
35
41
  `maxOutputTokens` and `--max-output-tokens` limits are honored exactly. The output allowance
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "humanish",
3
- "version": "0.118.0",
3
+ "version": "0.119.0",
4
4
  "description": "Synthetic user research for apps, CLIs, and agent-facing product flows.",
5
5
  "keywords": [
6
6
  "cli",
@@ -1,28 +0,0 @@
1
- import { type AutomaticAnalysisResult } from "../analysis/automatic-completion.js";
2
- import { type AutomaticAnalysisBudget } from "../analysis/automatic-config.js";
3
- import type { StudyRoute } from "./routing.js";
4
- /** The reason a run records for an analysis its study has too many participants for. */
5
- export declare const PARTICIPANT_LIMIT_SKIP = "AUTOMATIC_ANALYSIS_PARTICIPANT_LIMIT";
6
- /** The CLI's words for that reason, after "analysis: ". */
7
- export declare const PARTICIPANT_LIMIT_SKIP_TEXT: string;
8
- /** The skip a study of `participants` gets, or undefined when its analysis runs. */
9
- export declare function analysisSkipFor(participants: number): typeof PARTICIPANT_LIMIT_SKIP | undefined;
10
- /** The analysis budget the plan line shows, and the skip when the analysis will not run. */
11
- export type StudyAnalysisBudget = AutomaticAnalysisBudget & {
12
- readonly skip?: typeof PARTICIPANT_LIMIT_SKIP;
13
- };
14
- /**
15
- * The budget of a study's automatic analysis after a live run of `participants`, or undefined
16
- * when none is planned. A skipped analysis carries no expected cost, since it spends nothing.
17
- */
18
- export declare function studyAnalysisBudget(raw: unknown, route: StudyRoute, participants: number): StudyAnalysisBudget | undefined;
19
- /** The line `run` and `study check` print about the analysis after a live run. */
20
- export declare function formatStudyAnalysisBudget(budget: StudyAnalysisBudget): string;
21
- /** A run's result with its analysis recorded as skipped for its participant count. */
22
- export declare function withParticipantLimitSkip<T>(result: T, trigger: "default" | "explicit"): T & AutomaticAnalysisResult;
23
- /**
24
- * Whether a run's automatic analysis lets the command exit 0. A participant-count skip of the
25
- * analysis a study did not ask for passes, like the default analysis's missing-key skip; a skip of
26
- * an explicit request fails, like that request's missing key.
27
- */
28
- export declare function studyAnalysisSucceeded(result: AutomaticAnalysisResult): boolean;
@@ -1,57 +0,0 @@
1
- // What automatic analysis does for a study of N participants. One analysis reads at most
2
- // EVIDENCE_LIMITS.participants of them (the analysis module owns that number). A study with more
3
- // runs every participant and records its analysis as skipped for that reason, where the analysis
4
- // would otherwise cover the first participants and drop the rest. The plan line, the run's
5
- // analysis record and the CLI's exit code all read the skip from here. Once the analysis covers
6
- // every participant a study may have, the skip never applies and this module can go.
7
- import { automaticAnalysisSucceeded, } from "../analysis/automatic-completion.js";
8
- import { automaticAnalysisBudget, formatAutomaticAnalysisBudget, } from "../analysis/automatic-config.js";
9
- import { EVIDENCE_LIMITS } from "../analysis/evidence.js";
10
- /** The reason a run records for an analysis its study has too many participants for. */
11
- export const PARTICIPANT_LIMIT_SKIP = "AUTOMATIC_ANALYSIS_PARTICIPANT_LIMIT";
12
- /** The CLI's words for that reason, after "analysis: ". */
13
- export const PARTICIPANT_LIMIT_SKIP_TEXT = `not run, because the study has more than ${EVIDENCE_LIMITS.participants} participants and automatic analysis reads at most ${EVIDENCE_LIMITS.participants}`;
14
- /** The skip a study of `participants` gets, or undefined when its analysis runs. */
15
- export function analysisSkipFor(participants) {
16
- return participants > EVIDENCE_LIMITS.participants ? PARTICIPANT_LIMIT_SKIP : undefined;
17
- }
18
- /**
19
- * The budget of a study's automatic analysis after a live run of `participants`, or undefined
20
- * when none is planned. A skipped analysis carries no expected cost, since it spends nothing.
21
- */
22
- export function studyAnalysisBudget(raw, route, participants) {
23
- const skip = analysisSkipFor(participants);
24
- if (skip === undefined)
25
- return automaticAnalysisBudget(raw, route, participants);
26
- const budget = automaticAnalysisBudget(raw, route);
27
- return budget && { ...budget, participants, skip };
28
- }
29
- /** The line `run` and `study check` print about the analysis after a live run. */
30
- export function formatStudyAnalysisBudget(budget) {
31
- if (budget.skip === undefined)
32
- return formatAutomaticAnalysisBudget(budget);
33
- const next = budget.trigger === "default"
34
- ? "Set review.analysis: false to leave it out of this plan."
35
- : "The analysis this study asks for will be recorded as skipped and the run will exit 2; set review.analysis: false or run fewer participants.";
36
- return `After live runs: automatic analysis will not run. This study has ${budget.participants ?? "more"} participants, and automatic analysis reads at most ${EVIDENCE_LIMITS.participants}. ${next}`;
37
- }
38
- /** A run's result with its analysis recorded as skipped for its participant count. */
39
- export function withParticipantLimitSkip(result, trigger) {
40
- return {
41
- ...result,
42
- ...(trigger === "default" ? { automaticAnalysisTrigger: trigger } : {}),
43
- automaticAnalysis: { state: "skipped", reason: PARTICIPANT_LIMIT_SKIP },
44
- };
45
- }
46
- /**
47
- * Whether a run's automatic analysis lets the command exit 0. A participant-count skip of the
48
- * analysis a study did not ask for passes, like the default analysis's missing-key skip; a skip of
49
- * an explicit request fails, like that request's missing key.
50
- */
51
- export function studyAnalysisSucceeded(result) {
52
- const value = result.automaticAnalysis;
53
- return (automaticAnalysisSucceeded(result) ||
54
- (result.automaticAnalysisTrigger === "default" &&
55
- value?.state === "skipped" &&
56
- value.reason === PARTICIPANT_LIMIT_SKIP));
57
- }