humanish 0.34.0 → 0.35.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.
@@ -3,7 +3,7 @@
3
3
  Date: 2026-06-02 (current-state note updated 2026-07-14)
4
4
 
5
5
  Status: reference map for the major contracts shipped through source version
6
- `0.34.0`; it is not an exhaustive inventory of command/result envelopes. Exported types,
6
+ `0.35.0`; it is not an exhaustive inventory of command/result envelopes. Exported types,
7
7
  schema constants, parsers, and validators in `src/` are authoritative. Rows
8
8
  marked "reserved" name layering intent only — no code emits or validates them
9
9
  yet. Do not emit a reserved schema.
@@ -160,10 +160,19 @@ A lab is a composition over code primitives, not a hardcoded kind:
160
160
  deterministic `lanes[]` before the engine runs (`viewer-01`, `viewer-02`,
161
161
  ...), so the runtime and run bundle keep one normalized lane shape. `roster`
162
162
  is XOR with explicit `lanes`, homogeneous `count`, and `laneFocus`;
163
- - `execution.concurrency` (computer-use E2B route): bounds in-flight (paid)
164
- fan-out lanes; default `min(laneCount, 3)`. The env override
165
- `HUMANISH_CUA_MAX_CONCURRENCY` may only LOWER the effective bound, never raise
166
- concurrent paid desktops (invariant 3). Inert (warned) on other routes.
163
+ - `execution.concurrency` (computer-use E2B routes, including shared-world): a
164
+ CAP on lanes in flight at once. When omitted, every declared seat runs
165
+ simultaneously (the parser fills `concurrency = laneCount` for multi-seat
166
+ labs) total sessions and spend are identical either way; only wall-clock
167
+ and simultaneity differ. Declaring a value below the seat count runs seats in
168
+ waves and emits a warning saying so, because a green waved run is otherwise
169
+ indistinguishable from the all-live run the author meant. On shared-world
170
+ labs this field is also the sequential/concurrent selector: `concurrency: 1`
171
+ is the sequential turn-taking PoC; anything higher (including the filled
172
+ default) is the concurrent substrate. The env override
173
+ `HUMANISH_CUA_MAX_CONCURRENCY` may only LOWER the effective bound, never
174
+ raise concurrent paid desktops (invariant 3), and a lowering is recorded on
175
+ the plan (`envLoweredConcurrencyFrom`). Inert (warned) on other routes.
167
176
  `execution.timeoutMs` is the PER-LANE session budget on this route (semantics
168
177
  change: it was the single-session budget pre-fan-out); there is no run-level
169
178
  wall clock. `policies.allowPublicTargets` cannot combine with N>1 against one
@@ -243,22 +252,35 @@ A lab is a composition over code primitives, not a hardcoded kind:
243
252
  only command-scoped). The scripted-browser route is loopback-only and rejects
244
253
  `redactScreenshots: true` (blur unimplemented there) and
245
254
  `allowPublicTargets: true` fail-closed rather than ignoring them.
246
- - `comms` (clone/local-tree + shared-world computer-use routes; #297): off-app
255
+ - `comms` (#297; hosted on the clone/local-tree computer-use lanes and the
256
+ CONCURRENT shared-world getHost plane — warned inert everywhere else,
257
+ including app-url/operator-provided subjects and the sequential
258
+ `concurrency: 1` shared world, neither of which has a catch to host): off-app
247
259
  email/SMS the app itself SENDS, made a persona-driven testable surface.
248
260
  `comms.email` = `{ kind: fake, injectEnv, port?, recipients?, linkOrigin? }`.
249
261
  `injectEnv` is the ADOPTER-NAMED env var the app reads for its email-API base
250
262
  URL (e.g. `RESEND_API_URL`); the harness sets it to an in-sandbox catch (so it
251
263
  is NOT declared in `subject.env`) that captures the app's sends without touching
252
- the internet. `kind` must be `fake` (`real`/provider-backed is rejected until
253
- implemented); `port` 65534 (the catch reserves `port+1` for the read-only
254
- inbox listener the shared-world route getHost-exposes); `recipients[]` =
255
- `{ lane, address? }` declare the literal address each lane's app emails (so the
256
- drain can match it, and the persona is told an inbox URL to read + click);
257
- `linkOrigin` is an optional operator-declared origin the app bakes into links
258
- when it differs from the serve origin. Captured mail is drained into a
264
+ the internet verify the app actually reads that variable, because a run whose
265
+ catch captured zero sends warns at teardown for exactly that. `kind` must be
266
+ `fake` (`real`/provider-backed is rejected until implemented); `port` ≤ 65534
267
+ (the catch reserves `port+1` for the read-only inbox listener the shared-world
268
+ route getHost-exposes). `recipients[]` = `{ lane, address? }`: OMIT the list
269
+ and the parser fills one deterministic address per lane
270
+ (`<laneId>@example.test`) so every seat can do email (#351). When declared, a
271
+ `lane` must be one of the lab's real lane ids (roster ids, or the generated
272
+ `lane-01..lane-NN` under `count`) — an unknown lane is a hard parse error
273
+ listing them, zero addressed lanes is a hard error, partial coverage warns
274
+ with the uncovered lanes. Each addressed lane's actor prompt is extended with
275
+ the full handoff: its address ("enter exactly that"), the inbox URL, and the
276
+ wait steering ("waiting for an email is normal, not a blocker"). `linkOrigin`
277
+ is an optional operator-declared origin the app bakes into links when it
278
+ differs from the serve origin; the harness rewrites captured links through it
279
+ so a clicked link resolves to a reachable host. Captured mail is drained into a
259
280
  digest-only `humanish.comms-thread.v1` artifact (from/to/subject/link DIGESTS +
260
- an OTP COUNT — no raw address/link/code persists). Requires `python3` in the
261
- subject sandbox (the stock E2B desktop template has it).
281
+ an OTP COUNT — no raw address/link/code persists); the READABLE proof a
282
+ persona saw the email is its screenshots of the inbox page. Requires `python3`
283
+ in the subject sandbox (the stock E2B desktop template has it).
262
284
 
263
285
  Lab backends report results in their own schemas (`humanish.run-result.v1`,
264
286
  `humanish.oss-lab-result.v1`, `humanish.oss-meta-lab-result.v1`,
@@ -16,7 +16,7 @@ Humanish should be the open-source CLI that lets a maintainer ask:
16
16
  The answer should be observable, verifiable, public-safe, and easy to turn into
17
17
  actionable feedback.
18
18
 
19
- ## Current Program Truth (source `0.34.0`)
19
+ ## Current Program Truth (source `0.35.0`)
20
20
 
21
21
  The package source and repository implementation in this tree agree on these
22
22
  points:
@@ -82,7 +82,7 @@ silently drifting from one is not.
82
82
  | Default | Why it is the default | Legitimate override |
83
83
  |---|---|---|
84
84
  | Dry-run | Spend safety (invariant 3 sets the floor; dry-run keeps the floor far away) | `scenario.mode: live` |
85
- | Per-lane worlds | Isolation, attribution, reproducibility | `subject.topology: shared-world` — N seats against ONE provisioned, mutable plane for scenarios that ARE about interaction between roles (#164). `execution.concurrency: 1` (default) = SEQUENTIAL turns (one sandbox); `> 1` = CONCURRENT (one getHost-exposed subject sandbox + N actor sandboxes driving it at once, synthetic-subject only). The bundle declares the weaker `attributionClass: shared-world` + a verify-enforced `attributionLimits` ceiling (the concurrent set drops `sequential-only` and adds `best-effort-causal-attribution` etc.), so the looser per-role attribution is honest, not hidden. |
85
+ | Per-lane worlds | Isolation, attribution, reproducibility | `subject.topology: shared-world` — N seats against ONE provisioned, mutable plane for scenarios that ARE about interaction between roles (#164). `execution.concurrency: 1` (an explicit choice) = SEQUENTIAL turns (one sandbox); higher = CONCURRENT — and since #350 an omitted concurrency fills to the seat count, so every declared seat runs live at once by default (one getHost-exposed subject sandbox + N actor sandboxes driving it at once, synthetic-subject only). The bundle declares the weaker `attributionClass: shared-world` + a verify-enforced `attributionLimits` ceiling (the concurrent set drops `sequential-only` and adds `best-effort-causal-attribution` etc.), so the looser per-role attribution is honest, not hidden. |
86
86
  | 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 |
87
87
  | 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-lane public/preview fan-out needs explicit `actors[0].lanes[].target` for every lane, so the adapter-owned topology is declared rather than inferred. Provisioned clone subjects always serve in-sandbox on loopback |
88
88
  | Full-fidelity screenshots, local | The common case is watching a sim 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 for share-as-is bundles (a redact-on-export step for raw bundles is planned) |
@@ -2,7 +2,7 @@
2
2
 
3
3
  Status: public-safe contributor and agent ramp.
4
4
 
5
- Package/source version in this tree: `0.34.0` (2026-08-05). The containment boundary introduced in
5
+ Package/source version in this tree: `0.35.0` (2026-08-05). The containment boundary introduced in
6
6
  `0.15.1` remains in force: managed run and output paths bind to validated
7
7
  physical filesystem identities, and stored provider IDs are evidence, not
8
8
  cleanup authority. The bundled OSS meta-lab is dry-run only until
@@ -17,7 +17,7 @@ context.
17
17
  Read these in order:
18
18
 
19
19
  1. [`AGENTS.md`](../../AGENTS.md) for public boundary and engineering rules.
20
- 2. [`docs/principles/invariants-and-defaults.md`](../principles/invariants-and-defaults.md) — the constitution: which rules are invariants and which are overridable defaults.
20
+ 2. [`docs/principles/invariants-and-defaults.md`](../principles/invariants-and-defaults.md) — which rules are invariants and which are overridable defaults, each with the reason it exists and the check that enforces it. (The enforcement is what makes a rule real here — when a doc sentence and a test disagree, trust the test and say so.)
21
21
  3. [`README.md`](../../README.md) for install, commands, and package shape.
22
22
  4. [`docs/goals/current.md`](../goals/current.md) for the active product goal.
23
23
  5. [`docs/goals/proof-roadmap/goal.md`](https://github.com/danielgwilson/humanish/blob/main/docs/goals/proof-roadmap/goal.md) for the ratified proof architecture (repo-only; not shipped in the npm package, hence the absolute link).
@@ -113,8 +113,9 @@ Implemented:
113
113
  Still not good enough:
114
114
 
115
115
  The [current proof-roadmap checkpoint](https://github.com/danielgwilson/humanish/blob/main/docs/goals/proof-roadmap/README.md)
116
- supersedes implementation-status phrases in the immutable 2026-06-10 roadmap
117
- packet without changing its success standard.
116
+ supersedes implementation-status phrases in the 2026-06-10 roadmap packet
117
+ (kept as written — it is a dated record; its README carries current status)
118
+ without changing its success standard.
118
119
 
119
120
  - capability receipts are not adopter replacement: no first-party deletion
120
121
  branch has yet removed a bespoke generic harness while preserving
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "humanish",
3
- "version": "0.34.0",
3
+ "version": "0.35.0",
4
4
  "description": "Open-source-safe CLI for persona simulation, observer review, and public-safe feedback drafts.",
5
5
  "author": "Daniel G Wilson <daniel@danielgwilson.com>",
6
6
  "keywords": [
@@ -151,6 +151,24 @@ A lab is a composition (`subject` × `actors` × `execution` × `scenario` ×
151
151
  `npx humanish lab inspect <lab>` to see how a manifest parses, including
152
152
  warnings for fields the engine does not consume yet.
153
153
 
154
+ ### Many actors at once (fan-out, shared worlds, concurrency)
155
+
156
+ - **Every declared seat runs live at once by default.** A 6-lane roster is 6
157
+ simultaneous actors; total sessions and spend are the same either way, only
158
+ wall-clock and simultaneity differ. `execution.concurrency` is a CAP, not a
159
+ mode: declare it only to bound simultaneous paid desktops, and expect a parse
160
+ warning when the cap makes seats run in waves (a green waved run looks
161
+ identical to the all-live run you meant, so the harness says so up front).
162
+ - **Per-lane worlds vs one shared world.** A plain multi-lane computer-use lab
163
+ gives each actor its OWN app instance (independent studies in parallel). Add
164
+ `subject.topology: shared-world` for N actors in ONE world (a lobby, a shared
165
+ DB, actors seeing each other's changes). `execution.concurrency: 1` on a
166
+ shared-world lab is the sequential turn-taking variant — one actor at a time,
167
+ and note comms/email has no wiring there.
168
+ - **Watching it:** each live lane is its own Observer tile/stream; lanes beyond
169
+ a declared cap start when a slot frees, which on a capped run looks like idle
170
+ tiles — another reason to leave the cap out unless you need it.
171
+
154
172
  Use committed `humanish/labs/*.yaml` for public-safe, reproducible labs. Use
155
173
  ignored `.humanish/labs/*.yaml` or `.humanish/local/labs/*.yaml` for private repo
156
174
  targets, local-only dogfood, or machine-specific settings. Never commit private
@@ -179,19 +197,45 @@ the app sent it to finish a step.
179
197
  ```yaml
180
198
  comms:
181
199
  email:
182
- injectEnv: RESEND_API_URL # adopter-named: whatever env var YOUR app reads for its
183
- # email-API base URL. The harness sets it to the in-sandbox
184
- # catch — do NOT also list it in subject.env.
200
+ injectEnv: RESEND_API_URL # adopter-named: whatever env var YOUR app reads for its
201
+ # email-API base URL. The harness sets it to the in-sandbox catch — do NOT also
202
+ # list it in subject.env. VERIFY the app actually reads this variable: a stock
203
+ # email SDK does not honor a base-URL env unless the app passes it through, and
204
+ # an app that ignores it sends real mail (or throws) while the inbox stays empty.
205
+ # A run where the catch captured zero sends warns at teardown for exactly this.
206
+ ```
207
+
208
+ That is the whole block for the common case. Every lane automatically gets a
209
+ deterministic inbox address (`<laneId>@example.test`), and each actor's prompt is
210
+ extended with the full handoff: its address ("when the app asks for an email
211
+ address, enter exactly that"), the inbox URL to open, and the wait steering
212
+ ("waiting for an email is normal, not a blocker"). Declare `recipients` only to
213
+ customize addresses or limit which lanes do email:
214
+
215
+ ```yaml
185
216
  recipients:
186
- - lane: lane-01 # the actor lane that signs up
187
- address: user@example.test # the literal address the app emails (what the persona uses)
217
+ - lane: signup-01 # this lab's REAL lane id a roster lane's `id`, or the
218
+ # generated lane-01..lane-NN names when you use `count`. An unknown lane
219
+ # is a hard parse error listing the lab's actual lane ids (a mismatch
220
+ # would silently disable the funnel for that seat, which is how a
221
+ # 6-actor field run lost every inbox at once). Lanes you leave out get
222
+ # no inbox and are never told one exists — the parser warns which.
223
+ address: user@example.test # what the actor signs up with; the evidence
224
+ # drain matches captured mail against it.
188
225
  ```
189
226
 
190
227
  The app keeps calling its email API normally (Resend/SendGrid-shaped, or a custom
191
- profile); only the base URL is redirected. Works on the clone/local-tree route and
192
- the concurrent shared-world route. It needs `python3` in the subject sandbox (the
193
- stock E2B desktop has it). See `docs/contracts/schemas.md` for the full `comms:`
194
- shape and `humanish <cmd> --help` for run flags this skill does not restate them.
228
+ profile); only the base URL is redirected. Route support: the clone/local-tree
229
+ computer-use route (inbox on the sandbox's own loopback) and the CONCURRENT
230
+ shared-world route (inbox getHost-exposed from the subject sandbox; the default
231
+ since every seat now runs live at once). Declared anywhere else app-url /
232
+ operator-provided subjects, or a sequential `concurrency: 1` shared world — it is
233
+ warned inert at parse: no catch exists there and no actor hears about an inbox.
234
+ It needs `python3` in the subject sandbox (the stock E2B desktop has it).
235
+ Evidence is digest-only (`humanish.comms-thread.v1` — counts and digests, never
236
+ raw mail); the *readable* proof a persona saw the email is its screenshots of the
237
+ inbox page. See `docs/contracts/schemas.md` for the full `comms:` shape and
238
+ `humanish <cmd> --help` for run flags — this skill does not restate them.
195
239
 
196
240
  ## First Proof Run
197
241