humanish 0.94.0 → 0.95.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.
@@ -6,6 +6,7 @@ import type { TuiActionResult } from "./tui-actions.js";
6
6
  import type { TuiProjectState } from "./tui-project.js";
7
7
  import type { ReadRunIndexOptions, RunIndexResult } from "./run-index.js";
8
8
  import type { LaunchRunOptions, LaunchRunResult } from "./tui-launch.js";
9
+ import type { CommsSetupResult, CommsSetupStatus } from "./comms-connections.js";
9
10
  /** The humanish version string shown in the frame, so a screenshot in a bug report is datable. */
10
11
  export interface TuiVersionInfo {
11
12
  cli: string;
@@ -16,6 +17,11 @@ export interface TuiVersionInfo {
16
17
  * one place, and anything absent here is something the TUI simply cannot do.
17
18
  */
18
19
  export interface TuiCapabilities {
20
+ /** Optional for older embedders. Credentials are never returned to the view. */
21
+ comms?: {
22
+ read(): Promise<CommsSetupStatus>;
23
+ save(): Promise<CommsSetupResult>;
24
+ };
19
25
  /** Read every run in the project, cheapest source first. */
20
26
  readRunIndex(cwd: string, options?: ReadRunIndexOptions): Promise<RunIndexResult>;
21
27
  /**
@@ -62,6 +68,9 @@ export interface TuiCapabilities {
62
68
  initProject(cwd: string): Promise<TuiActionResult>;
63
69
  }
64
70
  export interface TuiOptions {
71
+ /** Return to setup after the host-owned hidden prompt has finished. */
72
+ initialScreen?: "connections";
73
+ connectionNotice?: string;
65
74
  /** The project the surface is reading. Already resolved by the CLI. */
66
75
  cwd: string;
67
76
  version: TuiVersionInfo;
@@ -80,7 +89,10 @@ export interface TuiOptions {
80
89
  * Start the surface. Resolves with the process exit code when the operator quits — the TUI owns the
81
90
  * screen until then, so the CLI must not write to stdout while this is pending.
82
91
  */
83
- export type StartTui = (options: TuiOptions) => Promise<number>;
92
+ export type TuiHandoff = {
93
+ action: "agentmail-key";
94
+ };
95
+ export type StartTui = (options: TuiOptions) => Promise<number | TuiHandoff>;
84
96
  /** The shape `dist/tui-app.js` exports. Asserted at the load boundary in program.ts. */
85
97
  export interface TuiModule {
86
98
  startTui: StartTui;
@@ -1 +1 @@
1
- {"version":3,"file":"tui-contract.js","sourceRoot":"","sources":["../src/tui-contract.ts"],"names":[],"mappings":"AAAA,2DAA2D;AAC3D,EAAE;AACF,6FAA6F;AAC7F,mGAAmG;AACnG,+EAA+E;AAC/E,EAAE;AACF,gGAAgG;AAChG,+FAA+F;AAC/F,gGAAgG;AAChG,mGAAmG;AACnG,6DAA6D;AAC7D,EAAE;AACF,mGAAmG;AACnG,mBAAmB;AA+FnB,mFAAmF;AACnF,MAAM,CAAC,MAAM,kBAAkB,GAAG,EAAE,CAAC;AAErC;;;GAGG;AACH,MAAM,UAAU,eAAe,CAAC,gBAAwB,OAAO,CAAC,OAAO;IACrE,MAAM,KAAK,GAAG,MAAM,CAAC,QAAQ,CAAC,aAAa,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,EAAE,EAAE,CAAC,CAAC;IACvF,OAAO,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,KAAK,IAAI,kBAAkB,CAAC;AAC/D,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,YAAY,CAAC,OAAe;IAC1C,OAAO,IAAI,GAAG,CAAC,cAAc,EAAE,OAAO,CAAC,CAAC;AAC1C,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,sBAAsB,CAAC,KAKtC;IACC,IAAI,CAAC,KAAK,CAAC,SAAS,EAAE,CAAC;QACrB,OAAO,+BAA+B,kBAAkB,cAAc,KAAK,CAAC,WAAW,mCAAmC,CAAC;IAC7H,CAAC;IACD,IAAI,CAAC,KAAK,CAAC,aAAa,EAAE,CAAC;QACzB,OAAO,8GAA8G,CAAC;IACxH,CAAC;IACD,OAAO,KAAK,CAAC,WAAW;QACtB,CAAC,CAAC,yEAAyE;QAC3E,CAAC,CAAC,kJAAkJ,CAAC;AACzJ,CAAC"}
1
+ {"version":3,"file":"tui-contract.js","sourceRoot":"","sources":["../src/tui-contract.ts"],"names":[],"mappings":"AAAA,2DAA2D;AAC3D,EAAE;AACF,6FAA6F;AAC7F,mGAAmG;AACnG,+EAA+E;AAC/E,EAAE;AACF,gGAAgG;AAChG,+FAA+F;AAC/F,gGAAgG;AAChG,mGAAmG;AACnG,6DAA6D;AAC7D,EAAE;AACF,mGAAmG;AACnG,mBAAmB;AAyGnB,mFAAmF;AACnF,MAAM,CAAC,MAAM,kBAAkB,GAAG,EAAE,CAAC;AAErC;;;GAGG;AACH,MAAM,UAAU,eAAe,CAAC,gBAAwB,OAAO,CAAC,OAAO;IACrE,MAAM,KAAK,GAAG,MAAM,CAAC,QAAQ,CAAC,aAAa,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,EAAE,EAAE,CAAC,CAAC;IACvF,OAAO,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,KAAK,IAAI,kBAAkB,CAAC;AAC/D,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,YAAY,CAAC,OAAe;IAC1C,OAAO,IAAI,GAAG,CAAC,cAAc,EAAE,OAAO,CAAC,CAAC;AAC1C,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,sBAAsB,CAAC,KAKtC;IACC,IAAI,CAAC,KAAK,CAAC,SAAS,EAAE,CAAC;QACrB,OAAO,+BAA+B,kBAAkB,cAAc,KAAK,CAAC,WAAW,mCAAmC,CAAC;IAC7H,CAAC;IACD,IAAI,CAAC,KAAK,CAAC,aAAa,EAAE,CAAC;QACzB,OAAO,8GAA8G,CAAC;IACxH,CAAC;IACD,OAAO,KAAK,CAAC,WAAW;QACtB,CAAC,CAAC,yEAAyE;QAC3E,CAAC,CAAC,kJAAkJ,CAAC;AACzJ,CAAC"}
@@ -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.94.0`; it is not an exhaustive inventory of command/result envelopes. Exported types,
6
+ `0.95.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.
@@ -265,10 +265,13 @@ A lab is a composition over code primitives, not a hardcoded kind:
265
265
  `redactScreenshots: true` (blur unimplemented there) and
266
266
  `allowPublicTargets: true` fail-closed rather than ignoring them.
267
267
  - `comms` (#297; hosted on the clone/local-tree computer-use lanes and the
268
- CONCURRENT shared-world getHost plane — warned inert everywhere else,
269
- including app-url/operator-provided subjects and the sequential
270
- `concurrency: 1` shared world, neither of which has a catch to host): off-app
271
- email/SMS the app itself SENDS, made a persona-driven testable surface.
268
+ concurrent shared-world getHost plane, or connected to an external catch on
269
+ app-url/operator-provided subjects; unwired on sequential `concurrency: 1`
270
+ shared worlds): off-app
271
+ email the app itself sends, made a persona-driven testable surface. Lab
272
+ configuration rejects `comms.sms` and unknown channel names; message-bus SMS
273
+ types do not imply a supported SMS execution route. SMTP capture is supported
274
+ on per-lane provisioned routes and rejected for shared-world studies.
272
275
  `comms.email` = `{ kind: fake, injectEnv?, port?, recipients?, linkOrigin?, external? }`.
273
276
  `injectEnv` is the ADOPTER-NAMED env var the app reads for its email-API base
274
277
  URL (e.g. `RESEND_API_URL`); the harness sets it to an in-sandbox catch (so it
@@ -310,6 +313,31 @@ A lab is a composition over code primitives, not a hardcoded kind:
310
313
  persona saw the email is its screenshots of the inbox page. Requires `python3`
311
314
  in the subject sandbox (the stock E2B desktop template has it).
312
315
 
316
+ ### Communication connection setup
317
+
318
+ `.humanish/local/comms.yaml` is a project-local, non-secret configuration file:
319
+
320
+ ```yaml
321
+ schema: humanish.comms-connections.v1
322
+ connections:
323
+ agentmail:
324
+ provider: agentmail
325
+ apiKeyEnv: AGENTMAIL_API_KEY
326
+ ```
327
+
328
+ Only AgentMail setup is currently supported. Connection names use lowercase
329
+ letters, digits and hyphens, beginning with a letter, up to 48 characters.
330
+ `apiKeyEnv` names an environment variable; it never contains its value.
331
+ Invalid fields, unsupported providers and unsafe filesystem paths are rejected.
332
+ Adding an existing name with different settings refuses to overwrite it.
333
+
334
+ `comms connections list --json` returns `humanish.comms-setup.v1`: configured
335
+ profiles and local key presence/source, not provider authentication or delivery.
336
+ `comms providers --json` returns `humanish.comms-providers.v1` with explicit
337
+ setup/receiving availability. The key store accepts `humanish keys set agentmail`.
338
+ Saving a connection neither modifies a lab nor creates a provider resource;
339
+ `comms.email.connection` in a study remains unsupported and is rejected.
340
+
313
341
  Lab backends report results in their own schemas (`humanish.run-result.v1`,
314
342
  `humanish.oss-lab-result.v1`, `humanish.oss-meta-lab-result.v1`,
315
343
  `humanish.cua-lab-result.v2`, `humanish.scripted-lab-result.v1`,
@@ -1,9 +1,9 @@
1
1
  # Current Goals
2
2
 
3
- Status date: 2026-09-19. Release baseline: `0.94.0`.
3
+ Status date: 2026-09-20. Release baseline: `0.95.0`.
4
4
 
5
5
  This page guides work on current merged source. Published behavior is described
6
- in the [release notes](../release/0.94.0-reliability.md).
6
+ in the [release notes](../release/0.95.0-connections-setup.md).
7
7
  The [September 9 history](https://github.com/danielgwilson/humanish/blob/main/docs/goals/current-history-2026-09-09.md)
8
8
  preserves the former status log; its queues do not supersede this page.
9
9
 
@@ -88,7 +88,7 @@ requires decision-equivalent retained evidence and a real deletion branch.
88
88
  No first-party deletion branch has met that gate. Public demonstrations do not
89
89
  substitute for it.
90
90
 
91
- ## Current Program Truth (source `0.94.0`)
91
+ ## Current Program Truth (source `0.95.0`)
92
92
 
93
93
  | Surface | Available in merged source | Remaining boundary |
94
94
  | --- | --- | --- |
@@ -100,8 +100,8 @@ substitute for it.
100
100
  | Observer | Live/recorded views, shared grid and participant playback, participant assignments, action-specific links, saved moments, zoom, comparison and phone-width review | Sparse captures cannot prove every action's effect; visual comparison alone is not a controlled experiment |
101
101
  | Review and feedback | Verification grades, feedback drafts, portable HTML, redacted bundle derivatives and computer-use completion-source labels | Sharing requires the appropriate grade; participant reports and condition matches still need task adjudication |
102
102
  | Study findings | Default post-run analysis on supported live routes with a separate disclosed $3 admission estimate limit and opt-out; explicit `analyze`, fairer evidence selection, concern review and versioned findings with exact source links | Model interpretation needs review; bounded selection and source truncation limit coverage; opening Observer never dispatches analysis |
103
- | TUI and serving | Detached starts, run stopping, reclamation, Observer attachment, loopback serving and run library | Stopping a process does not itself prove sandbox cleanup; TUI views over CLI `stats`/`export` remain follow-ups |
104
- | Off-app communication | Recipient-scoped synthetic inboxes, supported inline raster images, in-sandbox email/SMS catch and digest-only thread evidence | Scope prevents accidental cross-recipient browsing; it is not tenant authentication or real-provider delivery |
103
+ | TUI and serving | Detached starts, run stopping, reclamation, Observer attachment, loopback serving, run library and AgentMail connection/key setup | Stopping a process does not itself prove sandbox cleanup; TUI views over CLI `stats`/`export` remain follow-ups |
104
+ | Off-app communication | Recipient-scoped synthetic email inboxes, supported inline raster images, email capture and digest-only thread evidence | Scope prevents accidental cross-recipient browsing; it is not tenant authentication or real-provider delivery. AgentMail setup does not enable receiving; SMS is not a configured execution route |
105
105
  | Mobile and media | Hosted viewport/emulation, desktop geometry checks, bounded dwell and declared camera feed; a synthetic video-only call with separate hosted peers is proven | Audio, TURN, provider-specific rooms, physical-device and touch fidelity remain unproven; unsupported media declarations are rejected |
106
106
 
107
107
  Use the [task support matrix](../architecture/task-protocol-support.md),
@@ -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.94.0` (2026-09-19). The Observer is phone-usable as a stated requirement (observer/AGENTS.md); interactive primitives start from Base UI. The Observer renderer is the observer/ workspace artifact only; the legacy string-concat renderer was deleted at cutover (#426), and rollback is a version pin to 0.42.0. The containment boundary introduced in
5
+ Package/source version in this tree: `0.95.0` (2026-09-20). The Observer is phone-usable as a stated requirement (observer/AGENTS.md); interactive primitives start from Base UI. The Observer renderer is the observer/ workspace artifact only; the legacy string-concat renderer was deleted at cutover (#426), and rollback is a version pin to 0.42.0. 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
@@ -47,6 +47,11 @@ If a change does not improve one of those loops, it probably belongs elsewhere.
47
47
 
48
48
  ## Current State
49
49
 
50
+ The [0.95.0 release note](../release/0.95.0-connections-setup.md) describes
51
+ AgentMail connection setup, hidden key entry and return to the TUI, matching
52
+ CLI setup commands, and truthful credential status. Real email receiving is
53
+ still unavailable; saved connections cannot be used in studies yet.
54
+
50
55
  The [0.94.0 release note](../release/0.94.0-reliability.md) describes analysis
51
56
  request deadlines and budget-aware output, smaller portable recordings,
52
57
  recipient-scoped synthetic inboxes, route-aware setup checks, decoded capture
@@ -154,11 +159,12 @@ Implemented:
154
159
  deterministic proof, while concurrent has deterministic and kept live proof;
155
160
  - `subject.source: local-tree`, which packages one selected working tree with a
156
161
  content pin before using the same provision-and-serve path as clone subjects;
157
- - an off-app comms funnel for email/SMS-gated flows: a vendor-neutral in-sandbox
162
+ - an off-app comms funnel for email-gated flows: a vendor-neutral in-sandbox
158
163
  catch redirects the app's own send API, a persona reads a minimal inbox surface
159
164
  and clicks through, and a digest-only `humanish.comms-thread.v1` artifact
160
165
  records the thread with no raw address, link, or code — wired into the
161
- computer-use and shared-world routes and live-proven on computer-use;
166
+ computer-use and concurrent shared-world routes and live-proven on computer-use.
167
+ SMS is not yet a configured execution route;
162
168
  - resolved-persona directives that actually shape the actor prompt on the
163
169
  terminal-product route (traits are applied and recorded in the actor trace, not
164
170
  decorative), reusing the same `persona.ts` compiler as the computer-use lane;
@@ -0,0 +1,38 @@
1
+ # 0.95.0 — Connections and hidden key entry
2
+
3
+ Open `humanish tui` and press **c** to configure AgentMail. **Add API key**
4
+ opens a hidden terminal prompt, then returns to Connections. Ctrl+C cancels
5
+ without changing the existing key or profile. Existing keys can be reused or
6
+ replaced. Entry uses the host's key store, outside the TUI rendering contract.
7
+
8
+ Keys live in the existing user-level store with file mode 0600. Project
9
+ connection metadata lives in `.humanish/local/comms.yaml` and contains only a
10
+ provider name and environment-variable reference. Explicit environment/env-file
11
+ values retain precedence; strict key mode continues to disable discovery.
12
+ The screen identifies those cases instead of claiming a newly stored key is active.
13
+
14
+ Agents have matching commands:
15
+
16
+ ```bash
17
+ humanish comms providers --json
18
+ humanish keys set agentmail
19
+ humanish comms connections add agentmail --json
20
+ humanish comms connections list --json
21
+ ```
22
+
23
+ This is connection setup only. Authentication, permissions, capacity and
24
+ delivery are unverified, and this version does not acquire mailboxes or enable
25
+ provider-backed email in studies. Unsupported study connection/SMS selectors
26
+ and shared-world SMTP declarations fail before execution. Supported local
27
+ email capture remains available.
28
+
29
+ The release also includes TUI `--env-file` propagation and route-specific key
30
+ requirements from #793. Dry-run, local-agent and terminal participants no longer
31
+ inherit a hardcoded OpenAI-plus-E2B key requirement.
32
+
33
+ Validation covers local configuration, containment, credential precedence,
34
+ hidden entry/cancellation/paste, persistence and TUI frames at 80 and 45
35
+ columns. `pnpm tui:connections:proof` drives the built CLI in a real PTY with
36
+ synthetic keys and an isolated store, checking cancellation, replacement,
37
+ restart and absence of credential values from terminal output. It requires
38
+ Python 3 with Unix PTY support; it does not contact AgentMail.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "humanish",
3
- "version": "0.94.0",
3
+ "version": "0.95.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": [
@@ -81,7 +81,8 @@
81
81
  "observer:browser:proof": "node scripts/observer-browser-proof.mjs",
82
82
  "observer:iframe:proof": "node scripts/observer-iframe-proof.mjs",
83
83
  "observer:chrome:proof": "node scripts/observer-chrome-proof.mjs",
84
- "observer:reliability:proof": "node scripts/observer-reliability-proof.mjs"
84
+ "observer:reliability:proof": "node scripts/observer-reliability-proof.mjs",
85
+ "tui:connections:proof": "python3 scripts/tui-connections-proof.py"
85
86
  },
86
87
  "repository": {
87
88
  "type": "git",
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: humanish
3
- description: Install and configure Humanish CLI in a JavaScript app as an open-source-safe persona simulation harness. Use when an agent needs to add humanish, run safe first setup, create synthetic personas or scenarios, configure env var names without values, capture the email or SMS an app sends so a persona can complete an email-gated flow (e.g. a signup verification link or one-time code), run verification and Observer commands, or draft public-safe feedback issues without GitHub mutation.
3
+ description: Install and configure Humanish CLI in a JavaScript app as an open-source-safe persona simulation harness. Use when an agent needs to add humanish, run safe first setup, create synthetic personas or scenarios, configure env var names without values, capture the email an app sends so a persona can complete an email-gated flow (e.g. a signup verification link or one-time code), run verification and Observer commands, or draft public-safe feedback issues without GitHub mutation.
4
4
  ---
5
5
 
6
6
  # Humanish CLI
@@ -36,10 +36,21 @@ Everything it shows has a machine-readable equivalent, which is what you want:
36
36
  | browsing runs | `npx humanish runs --json` |
37
37
  | starting a run | `npx humanish lab run <lab> --json --no-open` |
38
38
  | a run's outcome | `npx humanish review --run <id> --json` |
39
+ | communication setup | `npx humanish comms providers --json` and `npx humanish comms connections list --json` |
39
40
 
40
41
  If a human asks you to "open the TUI", tell them the command to type; do not run
41
42
  it on their behalf.
42
43
 
44
+ For AgentMail credential setup, a human can open `humanish tui`, press `c`,
45
+ and choose **Add API key**. Hidden entry returns to Connections after saving or
46
+ cancelling. The key is stored for that OS user, while the connection profile is
47
+ project-local. Never ask for the key in chat. The CLI alternative is
48
+ `humanish keys set agentmail` (hidden prompt; agents may use `--stdin` from an
49
+ authorized credential source), followed by
50
+ `humanish comms connections add agentmail --json`. Existing env/file precedence
51
+ and `HUMANISH_STRICT_KEYS=1` still apply. Read installed provider capabilities:
52
+ this release supports setup only, not authenticated checks or real email delivery.
53
+
43
54
  ## Setup Workflow
44
55
 
45
56
  1. Inspect public target-repo files only: `package.json`, docs, route/app
@@ -203,9 +214,9 @@ npx humanish watch first-run
203
214
  npx humanish lab run first-run --json --no-open
204
215
  ```
205
216
 
206
- ### Off-app email/SMS verification (comms)
217
+ ### Off-app email verification (comms)
207
218
 
208
- When a flow is gated behind an email or SMS the app itself SENDS — a signup
219
+ When a flow is gated behind an email the app itself sends — a signup
209
220
  verification link, a one-time code, a magic link — add a `comms:` block. The
210
221
  harness redirects the app's email-API sends into a catch INSIDE the sandbox (no
211
222
  mail leaves the machine), gives the persona a synthetic inbox to open and click
@@ -213,6 +224,11 @@ through, and writes a digest-only `humanish.comms-thread.v1` evidence artifact (
213
224
  raw address/link/code persists). Reach for this whenever a persona must read mail
214
225
  the app sent it to finish a step.
215
226
 
227
+ Lab execution supports email capture only. `comms.sms`, saved connection
228
+ selectors and unknown channel names are rejected. AgentMail connection/key
229
+ setup is available separately; it does not enable real receiving yet. Real SMS
230
+ delivery is also unavailable; a message-bus type does not establish route support.
231
+
216
232
  ```yaml
217
233
  comms:
218
234
  email:
@@ -247,10 +263,15 @@ The app keeps calling its email API normally (Resend/SendGrid-shaped, or a custo
247
263
  profile); only the base URL is redirected. Route support: the clone/local-tree
248
264
  computer-use route (inbox on the sandbox's own loopback) and the CONCURRENT
249
265
  shared-world route (inbox getHost-exposed from the subject sandbox; the default
250
- since every seat now runs live at once). Declared anywhere else — app-url /
251
- operator-provided subjects, or a sequential `concurrency: 1` shared world — it is
252
- warned inert at parse: no catch exists there and no actor hears about an inbox.
253
- It needs `python3` in the subject sandbox (the stock E2B desktop has it).
266
+ since every seat now runs live at once). SMTP capture is supported on per-lane
267
+ provisioned routes; shared-world SMTP is rejected because it is not wired there.
268
+ For app-url/operator-provided subjects, run `humanish comms catch` on a reachable
269
+ host, point the app's email sends at that catch, and declare
270
+ `comms.email.external.catchBaseUrl` (plus `inboxBaseUrl` if different). A declared
271
+ `authTokenEnv` is an environment variable name, never a credential value.
272
+ Sequential `concurrency: 1` shared-world email remains unwired; do not silently
273
+ change study concurrency to work around that limitation. The in-sandbox catch
274
+ needs `python3` (the stock E2B desktop has it).
254
275
  Evidence is digest-only (`humanish.comms-thread.v1` — counts and digests, never
255
276
  raw mail); the *readable* proof a persona saw the email is its screenshots of the
256
277
  inbox page. See `docs/contracts/schemas.md` for the full `comms:` shape and