@ishlabs/cli 0.17.7 → 0.19.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 (64) hide show
  1. package/README.md +54 -54
  2. package/dist/commands/ask.d.ts +4 -4
  3. package/dist/commands/ask.js +66 -66
  4. package/dist/commands/chat.js +10 -10
  5. package/dist/commands/config.js +1 -1
  6. package/dist/commands/docs.js +1 -1
  7. package/dist/commands/iteration.js +57 -57
  8. package/dist/commands/mcp.d.ts +23 -0
  9. package/dist/commands/mcp.js +676 -0
  10. package/dist/commands/person.d.ts +5 -0
  11. package/dist/commands/{profile.js → person.js} +197 -162
  12. package/dist/commands/source.d.ts +6 -2
  13. package/dist/commands/source.js +35 -30
  14. package/dist/commands/study-analyze.d.ts +1 -1
  15. package/dist/commands/study-analyze.js +3 -3
  16. package/dist/commands/study-participant.d.ts +8 -0
  17. package/dist/commands/{study-tester.js → study-participant.js} +50 -50
  18. package/dist/commands/study-run.d.ts +6 -6
  19. package/dist/commands/study-run.js +341 -290
  20. package/dist/commands/study.js +106 -72
  21. package/dist/commands/workspace.js +13 -13
  22. package/dist/connect.js +5 -5
  23. package/dist/index.js +6 -4
  24. package/dist/lib/accessibility-profile.d.ts +1 -1
  25. package/dist/lib/accessibility-profile.js +1 -1
  26. package/dist/lib/alias-hydrate.js +4 -4
  27. package/dist/lib/alias-store.d.ts +5 -5
  28. package/dist/lib/alias-store.js +8 -8
  29. package/dist/lib/api-client.d.ts +1 -1
  30. package/dist/lib/api-client.js +1 -1
  31. package/dist/lib/billing.d.ts +11 -11
  32. package/dist/lib/billing.js +16 -16
  33. package/dist/lib/chat-endpoint-templates.js +1 -1
  34. package/dist/lib/command-helpers.d.ts +18 -18
  35. package/dist/lib/command-helpers.js +49 -37
  36. package/dist/lib/docs.js +570 -387
  37. package/dist/lib/enums.d.ts +2 -2
  38. package/dist/lib/enums.js +2 -2
  39. package/dist/lib/local-sim/browser.d.ts +1 -1
  40. package/dist/lib/local-sim/browser.js +1 -1
  41. package/dist/lib/local-sim/debug-report.d.ts +2 -2
  42. package/dist/lib/local-sim/debug-report.js +3 -3
  43. package/dist/lib/local-sim/loop.d.ts +5 -5
  44. package/dist/lib/local-sim/loop.js +38 -38
  45. package/dist/lib/local-sim/types.d.ts +12 -12
  46. package/dist/lib/mcp-clients.d.ts +51 -0
  47. package/dist/lib/mcp-clients.js +175 -0
  48. package/dist/lib/modality.d.ts +10 -10
  49. package/dist/lib/modality.js +46 -46
  50. package/dist/lib/output.d.ts +16 -15
  51. package/dist/lib/output.js +291 -226
  52. package/dist/lib/profile-sources.d.ts +64 -16
  53. package/dist/lib/profile-sources.js +91 -30
  54. package/dist/lib/skill-content.js +216 -168
  55. package/dist/lib/study-events.d.ts +3 -3
  56. package/dist/lib/study-events.js +1 -1
  57. package/dist/lib/study-inputs.d.ts +11 -1
  58. package/dist/lib/study-inputs.js +68 -17
  59. package/dist/lib/study-participants.d.ts +32 -0
  60. package/dist/lib/study-participants.js +12 -0
  61. package/dist/lib/types.d.ts +104 -34
  62. package/package.json +1 -1
  63. package/dist/commands/profile.d.ts +0 -5
  64. package/dist/commands/study-tester.d.ts +0 -8
package/dist/lib/docs.js CHANGED
@@ -7,7 +7,7 @@
7
7
  */
8
8
  const OVERVIEW = `# ish — overview for agents
9
9
 
10
- ish is a CLI for running studies and asks against AI tester audiences.
10
+ ish is a CLI for running studies and asks against AI people.
11
11
  The agent (you) is the primary user. Every command supports \`--json\`,
12
12
  exits non-zero on failure, and resolves IDs from short aliases.
13
13
 
@@ -15,16 +15,16 @@ exits non-zero on failure, and resolves IDs from short aliases.
15
15
 
16
16
  \`\`\`
17
17
  Workspace (= product)
18
- ├── Tester Profiles ────── reusable audience personas (alias: tp-…)
18
+ ├── People ────── reusable personas (alias: tp-…)
19
19
  │ └── Sources ──────── transcripts/audio/images that seed generation
20
20
  ├── Study ──────────────── persistent research artifact (alias: s-…)
21
21
  │ ├── modality ──────── interactive | text | video | audio | image | document | chat
22
- │ ├── assignments ───── tasks the tester does
23
- │ ├── questionnaire ─── questions the tester answers
22
+ │ ├── assignments ───── tasks the participant does
23
+ │ ├── questionnaire ─── questions the participant answers
24
24
  │ └── Iterations ────── one configured run (URL or content) (alias: i-…)
25
- │ └── Testers ─── instance of a Profile in this iteration (alias: t-…)
25
+ │ └── Participants ─── instance of a Profile in this iteration (alias: t-…)
26
26
  └── Ask ────────────────── lightweight reaction artifact (alias: a-…)
27
- └── Rounds ────────── unit of execution; audience fixed at ask creation
27
+ └── Rounds ────────── unit of execution; participants fixed at ask creation
28
28
  \`\`\`
29
29
 
30
30
  Two top-level run verbs:
@@ -37,7 +37,7 @@ Two top-level run verbs:
37
37
  2. Run \`ish docs get-page <slug>\` to read a specific page (e.g. \`concepts/study\`).
38
38
  3. Run \`ish docs search <query>\` for keyword lookup across all pages.
39
39
  4. Every command prints structured JSON when stdout is piped or \`--json\` is set.
40
- 5. Aliases like \`s-b2c\`, \`a-6ec\`, \`tp-795\`, \`i-d4e\`, \`t-a17\` are accepted
40
+ 5. Aliases like \`s-b2c\`, \`a-6ec\`, \`p-795\`, \`i-d4e\`, \`pt-a17\` are accepted
41
41
  anywhere an ID is expected. See \`ish docs get-page reference/aliases\`.
42
42
 
43
43
  ## Where to look next
@@ -73,7 +73,7 @@ workspace.
73
73
  A workspace carries:
74
74
  - \`base_url\` — default origin used by site-access rules and study URLs.
75
75
  - Site-access credentials (encrypted at rest) — see \`concepts/site-access\`.
76
- - Tester profiles + sources visible to every study/ask in the workspace.
76
+ - People + sources visible to every study/ask in the workspace.
77
77
 
78
78
  ## Selecting a workspace per command
79
79
 
@@ -113,15 +113,15 @@ ish workspace info --json
113
113
  {
114
114
  "studies_used": 2,
115
115
  "studies_max": 3,
116
- "testers_used": 0,
117
- "testers_max": 3,
116
+ "participants_used": 0,
117
+ "participants_max": 3,
118
118
  "tier": "free"
119
119
  }
120
120
  \`\`\`
121
121
 
122
122
  A \`null\` value on a \`*_max\` field means "unlimited" (paid tiers).
123
123
  Branch on \`studies_used >= studies_max\` before \`study create\`,
124
- likewise for \`testers_used\` before \`study run --sample\`.
124
+ likewise for \`participants_used\` before \`study run --sample\`.
125
125
 
126
126
  ## Cold start — \`workspace_create\` is not safe to call blind
127
127
 
@@ -138,13 +138,13 @@ Each row in the list response carries:
138
138
  - \`last_activity_at\` — most recent run, iteration, ask, or write on
139
139
  this workspace. Pick the most recently active workspace if you want
140
140
  one the user is likely already thinking about.
141
- - \`child_counts\` — \`{ studies, asks, tester_profiles }\`. Zero across
141
+ - \`child_counts\` — \`{ studies, asks, persons }\`. Zero across
142
142
  the board = a quiet/empty workspace, safe to reuse without
143
143
  cluttering anyone's view.
144
144
  - \`has_headroom\` — \`true\` if the workspace is below
145
145
  \`maxStudiesPerProduct\`, \`maxIterationsPerStudy\`, and
146
- \`maxCustomTesterProfiles\` for the caller's tier. Branch on this
147
- before \`study create\` / \`profile generate\` — \`false\` here will be
146
+ \`maxCustomPersons\` for the caller's tier. Branch on this
147
+ before \`study create\` / \`person generate\` — \`false\` here will be
148
148
  \`usage_limit_reached\` on the next call.
149
149
 
150
150
  For the idempotent create-or-reuse-by-name path, use
@@ -167,14 +167,14 @@ transcript) lives at \`guides/cold-start\`.
167
167
  const CONCEPT_STUDY = `# concept: study
168
168
 
169
169
  A **study** is the persistent research artifact. It defines:
170
- - \`modality\`: \`interactive\` (the tester drives a real browser), one of
170
+ - \`modality\`: \`interactive\` (the participant drives a real browser), one of
171
171
  \`text | video | audio | image | document\` (media reaction studies),
172
172
  or \`chat\` (multi-turn conversation — either with an external chatbot
173
- endpoint or between two AI personas via tester_pair mode).
173
+ endpoint or between two AI personas via participant_pair mode).
174
174
  - \`content_type\` (media studies only): \`email | social_post | ad | …\` —
175
- controls the framing the tester is given.
176
- - \`assignments\`: the tasks the tester performs. See \`concepts/assignment\`.
177
- - \`questionnaire\`: the questions the tester answers. See \`concepts/questionnaire\`.
175
+ controls the framing the participant is given.
176
+ - \`assignments\`: the tasks the participant performs. See \`concepts/assignment\`.
177
+ - \`questionnaire\`: the questions the participant answers. See \`concepts/questionnaire\`.
178
178
 
179
179
  A study does **not** carry the URL or media being tested — that lives on
180
180
  its iterations. Think: a study is the recipe; an iteration is one batch.
@@ -204,7 +204,7 @@ test artifact and don't need to A/B iterations:
204
204
  | \`video\` | \`--content-url <url>\` |
205
205
  | \`audio\` | \`--content-url <url>\` |
206
206
  | \`document\` | \`--content-url <url>\` |
207
- | \`chat\` | \`--endpoint <id>\` or \`--endpoint-config <file>\` (external_chatbot mode), or \`--chat-mode tester_pair --audience-a/-b --scenario-a/-b\` (two-AI rehearsal) |
207
+ | \`chat\` | \`--endpoint <id>\` or \`--endpoint-config <file>\` (external_chatbot mode), or \`--chat-mode participant_pair --group-a/-b --scenario-a/-b\` (two-AI rehearsal) |
208
208
 
209
209
  \`\`\`
210
210
  # Text — single email artifact:
@@ -256,8 +256,8 @@ Every study response carries two status-shaped fields:
256
256
 
257
257
  - \`status\` — the raw lifecycle column on the row, values
258
258
  \`draft | running | completed | cancelled\`. Updated lazily; can
259
- disagree with what the testers actually did.
260
- - \`runtime_status\` — derived by aggregating the iteration testers'
259
+ disagree with what the participants actually did.
260
+ - \`runtime_status\` — derived by aggregating the iteration participants'
261
261
  states. Values: \`draft | running | completed |
262
262
  completed_with_errors | cancelled\`. **Never reports \`failed\` while
263
263
  completed runs exist** (the Bk2 invariant). Prefer this for any
@@ -307,8 +307,8 @@ the chat payload (chat) — while the study carries the persistent
307
307
  shape (assignments, questionnaire, modality).
308
308
 
309
309
  For chat modality, the iteration's \`details.mode_details\` discriminator
310
- selects between **external_chatbot** (testers probe a customer chatbot
311
- endpoint) and **tester_pair** (two AI tester audiences converse with
310
+ selects between **external_chatbot** (participants probe a customer chatbot
311
+ endpoint) and **participant_pair** (two AI people converse with
312
312
  each other, one Conversation per pair index). Wire-shape examples and
313
313
  pair-mode rules live under the "## Chat modality" section below; the
314
314
  full chat-author workflow is at \`guides/chat\`.
@@ -322,7 +322,7 @@ full chat-author workflow is at \`guides/chat\`.
322
322
 
323
323
  Because you want to A/B different URLs or content variants while keeping
324
324
  the same task definitions and questionnaire. Each iteration also owns
325
- its own roster of testers, so you can compare audiences as well.
325
+ its own roster of participants, so you can compare groups as well.
326
326
 
327
327
  ## Common commands
328
328
 
@@ -358,9 +358,9 @@ ish iteration create --content-url ./report.pdf
358
358
  # Chat (external_chatbot) — probe a saved chatbot endpoint:
359
359
  ish iteration create --chat-endpoint-id ce-... --max-turns 10 --early-termination
360
360
 
361
- # Chat (tester_pair) — rehearse a conversation between two AI audiences:
362
- ish iteration create --chat-mode tester_pair \\
363
- --audience-a tp-a1,tp-a2 --audience-b tp-b1,tp-b2 \\
361
+ # Chat (participant_pair) — rehearse a conversation between two AI groups:
362
+ ish iteration create --chat-mode participant_pair \\
363
+ --group-a p-a1,p-a2 --group-b p-b1,p-b2 \\
364
364
  --scenario-a "You're a senior sales rep pitching ish." \\
365
365
  --scenario-b "You're a skeptical CTO evaluating ish."
366
366
 
@@ -376,7 +376,7 @@ can be collected per **segment** instead of over the whole asset. A
376
376
  segment is a contiguous slice of the iteration's content — a 30-second
377
377
  window of a video, a paragraph range of an email, a section of a PDF.
378
378
  Each segment can carry a human-readable **label** ("Intro", "Pricing
379
- section", "Call to action") that surfaces in the tester UI and in
379
+ section", "Call to action") that surfaces in the participant UI and in
380
380
  results.
381
381
 
382
382
  Segments live inside the iteration's \`segmentation\` field — there is
@@ -423,7 +423,7 @@ reactions; otherwise the default just works.
423
423
 
424
424
  ### content_config — early termination + selected segments
425
425
 
426
- A sibling of \`segmentation\` that controls how the tester progresses
426
+ A sibling of \`segmentation\` that controls how the participant progresses
427
427
  through segments:
428
428
 
429
429
  - \`early_termination: true\` — stop the session once every selected
@@ -437,7 +437,7 @@ Pass via \`--content-config-json '<json>'\`.
437
437
 
438
438
  - **Text modality**: pair plain \`--content-text\` with rich
439
439
  \`--content-html\` to render emails / articles with formatting. The
440
- plain text is what testers reason over; the HTML is what they see.
440
+ plain text is what participants reason over; the HTML is what they see.
441
441
  - **Media captions** (video, audio, image): \`--copy-text\` and
442
442
  \`--copy-html\` attach a caption to the media — the social-post
443
443
  pattern. Add \`--social-platform\` (instagram/tiktok/facebook/linkedin/x)
@@ -455,12 +455,12 @@ Chat iterations hold a multi-turn conversation. The conversation can
455
455
  take one of two shapes, picked by the \`mode_details.mode\` discriminator
456
456
  on the iteration:
457
457
 
458
- - **\`external_chatbot\`** — a tester talks to a customer chatbot
458
+ - **\`external_chatbot\`** — a participant talks to a customer chatbot
459
459
  endpoint (the original chat behaviour). The endpoint config or saved
460
460
  chatbot-endpoint reference lives at
461
461
  \`details.mode_details.endpoint\` / \`details.mode_details.chatbot_endpoint_id\`.
462
- - **\`tester_pair\`** — two AI tester profiles talk to each other.
463
- audience_a and audience_b pair 1:1 by index when counts match (N
462
+ - **\`participant_pair\`** — two AI people talk to each other.
463
+ group_a and group_b pair 1:1 by index when counts match (N
464
464
  pairs → N conversations); a side of exactly 1 broadcasts across the
465
465
  other side (so 1 × N → N conversations all sharing the lone profile).
466
466
  Each side carries its own scenario + goal; the other side does not
@@ -483,13 +483,13 @@ Wire-shape:
483
483
  "early_termination": true
484
484
  }
485
485
 
486
- // tester_pair (with explicit audiences)
486
+ // participant_pair (with explicit groups)
487
487
  {
488
488
  "type": "chat",
489
489
  "mode_details": {
490
- "mode": "tester_pair",
491
- "audience_a": ["tp-uuid-1", "tp-uuid-2"],
492
- "audience_b": ["tp-uuid-3", "tp-uuid-4"],
490
+ "mode": "participant_pair",
491
+ "group_a": ["p-uuid-1", "p-uuid-2"],
492
+ "group_b": ["p-uuid-3", "p-uuid-4"],
493
493
  "scenario_a": "You are a senior sales rep pitching ish.",
494
494
  "scenario_b": "You are a skeptical CTO evaluating ish.",
495
495
  "initiator_side": "a"
@@ -498,13 +498,13 @@ Wire-shape:
498
498
  "early_termination": true
499
499
  }
500
500
 
501
- // tester_pair (with role criteria — backend resolves the pool)
501
+ // participant_pair (with role criteria — backend resolves the pool)
502
502
  {
503
503
  "type": "chat",
504
504
  "mode_details": {
505
- "mode": "tester_pair",
506
- "audience_a": [],
507
- "audience_b": [],
505
+ "mode": "participant_pair",
506
+ "group_a": [],
507
+ "group_b": [],
508
508
  "role_criteria_a": {
509
509
  "occupation": ["founder", "ceo"],
510
510
  "min_age": 28, "max_age": 55,
@@ -520,23 +520,23 @@ Wire-shape:
520
520
  }
521
521
  \`\`\`
522
522
 
523
- ## Audience selection (tester_pair)
523
+ ## Audience selection (participant_pair)
524
524
 
525
- Each side of a pair needs **either** an explicit audience list **or** a
525
+ Each side of a pair needs **either** an explicit people list **or** a
526
526
  role-criteria filter (or both). Three input modes:
527
527
 
528
528
  | Side A input | Side B input | Behaviour |
529
529
  | --- | --- | --- |
530
- | \`--audience-a\` (UUIDs) | \`--audience-b\` (UUIDs) | Explicit pairing. Equal counts zip 1:1 by index; a side of exactly 1 broadcasts to the other. |
530
+ | \`--group-a\` (UUIDs) | \`--group-b\` (UUIDs) | Explicit pairing. Equal counts zip 1:1 by index; a side of exactly 1 broadcasts to the other. |
531
531
  | \`--role-criteria-a\` (JSON) | \`--role-criteria-b\` (JSON) | Backend resolves matching pool from each side's criteria and persists the IDs back to the iteration. |
532
532
  | Either flag pair | Either flag pair | Mixed (e.g. explicit A + criteria B). Backend handles each side independently. |
533
533
  | Both flags on one side | (any) | Criteria validates the explicit list; mismatch blocks run with a clear error. |
534
534
 
535
- **Persona-first principle**: the tester's persona is sacred — never
535
+ **Persona-first principle**: the participant's persona is sacred — never
536
536
  altered by the scenario. Criteria filter the *eligible pool* upstream
537
- so that by the time a tester reaches the LLM prompt, their persona is
537
+ so that by the time a participant reaches the LLM prompt, their persona is
538
538
  already plausible for the role. The prompt construction itself does
539
- not change between explicit-audience and criteria-driven flows.
539
+ not change between explicit-people and criteria-driven flows.
540
540
 
541
541
  \`RoleCriteria\` keys (all optional):
542
542
 
@@ -554,7 +554,7 @@ not change between explicit-audience and criteria-driven flows.
554
554
  If the resolved pool is smaller than the requested conversation count
555
555
  for a side, \`ish study run\` exits 2 with the backend's error envelope
556
556
  intact. No silent fallback. Broaden the criteria, generate more
557
- profiles, or pass an explicit \`--audience-*\` list to recover.
557
+ profiles, or pass an explicit \`--group-a/-b\` list to recover.
558
558
 
559
559
  ## Pair-mode flag names (CLI ↔ MCP alignment)
560
560
 
@@ -564,7 +564,7 @@ agent doesn't pay a translation tax when switching surfaces:
564
564
 
565
565
  | CLI flag | MCP field | What it carries |
566
566
  |---------------------------|----------------------------|-----------------------------------------------------|
567
- | \`--audience-a\` / \`-b\` | \`audience_a\` / \`audience_b\` | Explicit tester profile IDs (UUIDs or aliases) for that side. |
567
+ | \`--group-a\` / \`-b\` | \`group_a\` / \`group_b\` | Explicit person IDs (UUIDs or aliases) for that side. |
568
568
  | \`--role-criteria-a\` / \`-b\` | \`role_criteria_a\` / \`role_criteria_b\` | JSON filter (occupation, country, …) the backend resolves into a pool. |
569
569
  | \`--scenario-a\` / \`-b\` | \`scenario_a\` / \`scenario_b\` | The system-prompt-shaped role text injected into one side's prompt only (asymmetry contract). |
570
570
  | \`--initiator-side\` | \`initiator_side\` | Which side speaks first (\`a\` default). |
@@ -572,9 +572,9 @@ agent doesn't pay a translation tax when switching surfaces:
572
572
  | \`--early-termination\` | \`early_termination\` | Allow the worker to end early when parties signal. |
573
573
 
574
574
  The pre-2026-05 \`--profile-a\` / \`--profile-b\` CLI flags were
575
- renamed to \`--audience-a\` / \`--audience-b\` to match the MCP and
576
- the wire shape (\`mode_details.audience_a\` /
577
- \`mode_details.audience_b\`). Same intent, same accepted inputs
575
+ renamed to \`--group-a\` / \`--group-b\` to match the MCP and
576
+ the wire shape (\`mode_details.group_a\` /
577
+ \`mode_details.group_b\`). Same intent, same accepted inputs
578
578
  (comma-separated UUIDs or aliases, repeatable). \`--role-criteria-a\`
579
579
  / \`--role-criteria-b\` were already aligned with MCP and did not
580
580
  change.
@@ -592,14 +592,14 @@ ish iteration create --endpoint-config ./bot.json
592
592
  ish iteration create --chat-endpoint-id ep-abc --max-turns 10
593
593
  ish iteration create --chat-endpoint-json '{"url":"https://..."}'
594
594
 
595
- # tester_pair — two AI audiences, asymmetric per-side scenarios:
596
- ish iteration create --chat-mode tester_pair \\
597
- --audience-a tp-a1,tp-a2 --audience-b tp-b1,tp-b2 \\
595
+ # participant_pair — two AI groups, asymmetric per-side scenarios:
596
+ ish iteration create --chat-mode participant_pair \\
597
+ --group-a p-a1,p-a2 --group-b p-b1,p-b2 \\
598
598
  --scenario-a @./sales_rep.md --scenario-b @./skeptical_cto.md \\
599
599
  --max-turns 14
600
600
 
601
- # tester_pair — criteria-driven audience (persona-first filtering):
602
- ish iteration create --chat-mode tester_pair \\
601
+ # participant_pair — criteria-driven group (persona-first filtering):
602
+ ish iteration create --chat-mode participant_pair \\
603
603
  --role-criteria-a '{"occupation":["founder","ceo"],"min_age":28}' \\
604
604
  --role-criteria-b @./criteria_investor.json \\
605
605
  --scenario-a @./sales_rep.md --scenario-b @./skeptical_cto.md \\
@@ -608,7 +608,7 @@ ish iteration create --chat-mode tester_pair \\
608
608
 
609
609
  Tunables (both modes):
610
610
  - \`--max-turns N\` — cap the conversation length (default 12 for
611
- external_chatbot, 14 for tester_pair; persona drift starts ~20 turns
611
+ external_chatbot, 14 for participant_pair; persona drift starts ~20 turns
612
612
  so cap accordingly).
613
613
  - \`--early-termination\` — let the worker end the session early when
614
614
  the parties signal the conversation is over.
@@ -617,27 +617,27 @@ Pair-mode rules:
617
617
  - Each side needs **either** \`--profile-*\` (explicit IDs) **or**
618
618
  \`--role-criteria-*\` (filter the backend resolves). The two can also
619
619
  be combined — criteria then acts as validation on the explicit list.
620
- - When both sides use explicit \`--audience-a\` / \`--audience-b\`, they
620
+ - When both sides use explicit \`--group-a\` / \`--group-b\`, they
621
621
  must be the same length (≥ 1). Same profile on both sides is allowed
622
622
  (self-talk rehearsal). When either side defers to criteria, the
623
623
  length match is enforced server-side after pool resolution.
624
624
  - **1×N broadcast**: pass exactly one profile on one side and N
625
625
  profiles on the other to rehearse the fixed side against N
626
626
  variations. The CLI auto-broadcasts the singleton to match length
627
- N. Example: \`--audience-a tp-rep --audience-b tp-cto1,tp-cto2,tp-cto3\`
628
- produces 3 conversations, all sharing tp-rep on side A. The CLI
627
+ N. Example: \`--group-a p-rep --group-b p-cto1,p-cto2,p-cto3\`
628
+ produces 3 conversations, all sharing p-rep on side A. The CLI
629
629
  prints a stderr notice so you know broadcasting kicked in.
630
630
  - Both \`--scenario-a\` and \`--scenario-b\` are required and asymmetric.
631
631
  - \`--initiator-side\` defaults to \`a\` (side A speaks first).
632
- - \`--chat-mode\` accepts both \`tester_pair\` and \`tester-pair\`
632
+ - \`--chat-mode\` accepts both \`participant_pair\` and \`participant-pair\`
633
633
  (hyphenated variants are normalised). Same normalisation applies to
634
634
  \`--screen-format\` (\`mobile_portrait\` ↔ \`mobile-portrait\`),
635
635
  \`--kind\` on \`source upload\` (\`text_file\` ↔ \`text-file\`), and the
636
636
  \`type\` field in \`--questionnaire\` / \`--questions\` manifests
637
637
  (\`single-choice\` ↔ \`single_choice\`).
638
638
  - Audiences are pinned to the iteration. \`ish study run\` refuses
639
- run-time audience overrides (\`--profile\` / \`--sample\` / \`--all\` /
640
- filters) on a pair iteration — change the audiences via
639
+ run-time people overrides (\`--profile\` / \`--sample\` / \`--all\` /
640
+ filters) on a pair iteration — change the peoples via
641
641
  \`ish iteration update <id> --details-json '{...}'\` instead.
642
642
  - \`--max-turns\` / \`--early-termination\` on \`ish study run\` override
643
643
  the iteration's saved values for that single dispatch (they are not
@@ -704,7 +704,7 @@ persona drift starts to dominate.
704
704
  A scenario describes **voice, knowledge, and goal** for one role —
705
705
  *not* the demographics of who plays it. Demographic constraints
706
706
  ("you are 35-year-old Swedish founder") belong in
707
- \`--role-criteria-a\` / \`--role-criteria-b\` instead. The tester's
707
+ \`--role-criteria-a\` / \`--role-criteria-b\` instead. The participant's
708
708
  persona stays sacred; criteria filter the eligible pool upstream so
709
709
  the persona is already plausible for the role by the time the LLM
710
710
  sees the prompt. Mixing demographics into the scenario text
@@ -749,20 +749,22 @@ Treat this as actionable, not transient — re-running won't change anything.
749
749
 
750
750
  - \`concepts/study\` — the parent artifact.
751
751
  - \`concepts/run-verbs\` — how \`ish study run\` selects the iteration.
752
- - \`concepts/audience\` — how testers are picked for a run.
752
+ - \`concepts/people\` — how participants are picked for a run.
753
753
  - \`reference/billing-limits\` — \`maxIterationsPerStudy\` cap on iteration creation.
754
- - \`reference/credits\` — per-iteration-run credit cost & preview shape (\`pair_preview.credit_estimate\` for tester-pair, top-level \`credit_estimate\` otherwise).
754
+ - \`reference/credits\` — per-iteration-run credit cost & preview shape (\`pair_preview.credit_estimate\` for participant-pair, top-level \`credit_estimate\` otherwise).
755
755
  `;
756
756
  const CONCEPT_ASSIGNMENT = `# concept: assignment
757
757
 
758
- An **assignment** is a single task a tester performs during an
758
+ An **assignment** is a single task a participant performs during an
759
759
  interactive study (or considers, for media studies). A study has 1..N
760
760
  assignments and they run in order.
761
761
 
762
762
  Each assignment has:
763
- - \`name\` — short label shown to the tester ("Sign up").
764
- - \`instructions\` — what the tester is asked to do ("Complete the signup
763
+ - \`name\` — short label shown to the participant ("Sign up").
764
+ - \`instructions\` — what the participant is asked to do ("Complete the signup
765
765
  flow using a personal email").
766
+ - \`steps\` (optional) — an ordered checklist of atomic actions the participant
767
+ should accomplish (see "Steps" below).
766
768
 
767
769
  ## CLI input formats
768
770
 
@@ -782,27 +784,59 @@ ish study create --name "Checkout" --modality interactive \\
782
784
  ish study create … --assignments '[{"name":"Browse","instructions":"…"}]'
783
785
  \`\`\`
784
786
 
785
- \`assignments.json\` shape:
787
+ \`assignments.json\` shape (note the optional \`steps\` checklist):
786
788
  \`\`\`json
787
789
  [
788
790
  { "name": "Browse", "instructions": "Find a product you like" },
789
- { "name": "Buy", "instructions": "Add to cart and checkout" }
791
+ {
792
+ "name": "Buy",
793
+ "instructions": "Add to cart and checkout",
794
+ "steps": [
795
+ { "name": "Find a product", "description": "Browse to any item" },
796
+ { "name": "Add to cart" },
797
+ { "name": "Complete checkout" }
798
+ ]
799
+ }
790
800
  ]
791
801
  \`\`\`
792
802
 
803
+ ## Steps (checklist)
804
+
805
+ An assignment may carry an ordered \`steps\` list — atomic actions the participant
806
+ should accomplish. Each step is \`{ "name": "...", "description"?: "..." }\`
807
+ (name 1–80 chars, description ≤500). Steps are authored **only via the JSON
808
+ forms** (\`--assignments-file\` / \`--assignments\`); the inline
809
+ \`--assignment "Name:Instructions"\` shorthand cannot express them.
810
+
811
+ Steps are honored for **interactive** and **external_chatbot chat** studies
812
+ only. The backend rejects steps on media modalities (text/video/audio/image/
813
+ document) and on chat \`participant_pair\`.
814
+
815
+ After a run, an LLM verifier grades each step per participant. \`ish study get <id>\`
816
+ then surfaces, on each assignment:
817
+ - \`steps\` — the resolved checklist, each with a server-assigned \`id\` (a slug
818
+ like \`add-to-cart\`).
819
+ - \`step_completion\` — a per-step rollup: \`step_id\`, \`total\`, \`passed\`,
820
+ \`rate\` (null until graded), and up to 3 \`sample_failures\`
821
+ (\`{participant_id, reason}\`). Human output shows a pass-rate line per step;
822
+ \`sample_failures[].participant_id\` is a UUID, so it only appears under
823
+ \`--verbose\` in JSON mode.
824
+
793
825
  ## Update or replace
794
826
 
795
827
  \`ish study update <id> --assignment …\` (or \`--assignments-file\`)
796
- replaces the full assignment list — additive editing is not supported.
828
+ replaces the full assignment list — additive editing is not supported. Steps
829
+ ride along when present in the JSON forms.
797
830
 
798
831
  ## Related
799
832
 
800
833
  - \`concepts/study\` — assignments are immutable to the run; questionnaire is too.
801
834
  - \`concepts/questionnaire\` — the other half of the study definition.
835
+ - \`reference/json-mode\` — how \`step_completion\` renders in lean vs --verbose.
802
836
  `;
803
837
  const CONCEPT_QUESTIONNAIRE = `# concept: questionnaire
804
838
 
805
- The **questionnaire** is the list of \`interview_questions\` a tester
839
+ The **questionnaire** is the list of \`interview_questions\` a participant
806
840
  answers before or after their assignments. A study has 0..N
807
841
  questions, each with a type and a timing.
808
842
 
@@ -848,20 +882,20 @@ so either works in your manifest.
848
882
  const CONCEPT_ASK = `# concept: ask
849
883
 
850
884
  An **ask** is a lightweight reaction artifact — much less ceremony than
851
- a study. It has an audience (fixed at creation) and a sequence of
852
- **rounds**. Each round shows the audience a prompt plus 1..N variants
885
+ a study. It has a group of people (fixed at creation) and a sequence of
886
+ **rounds**. Each round shows the people a prompt plus 1..N variants
853
887
  (text or image) and collects their reactions.
854
888
 
855
889
  - Alias prefix: \`a-\`
856
- - Audience is fixed at ask creation — you cannot swap audiences between
857
- rounds. (You can extend it via \`ish ask add-testers\`.)
890
+ - Audience is fixed at ask creation — you cannot swap groups between
891
+ rounds. (You can extend it via \`ish ask add-people\`.)
858
892
  - Up to 5 rounds per ask.
859
893
 
860
894
  ## When to use ask vs study
861
895
 
862
896
  - Reach for **ask** for: tagline A/B, hero-image picks, copy comparisons,
863
897
  quick reactions to creative variants.
864
- - Reach for **study** for: anything that needs a tester to *do* something
898
+ - Reach for **study** for: anything that needs a participant to *do* something
865
899
  (interactive flow, multi-step task, time-on-page).
866
900
 
867
901
  See \`concepts/run-verbs\` for the side-by-side.
@@ -903,11 +937,11 @@ intact — no \`--verbose\` needed to see it.
903
937
 
904
938
  ## Stage-then-dispatch (draft asks)
905
939
 
906
- When you want a human to review the audience and prompt **before** any
940
+ When you want a human to review the people and prompt **before** any
907
941
  credits are spent, separate creation from dispatch:
908
942
 
909
943
  \`\`\`
910
- # 1. Stage — materializes testers, no worker enqueue, no bill yet
944
+ # 1. Stage — materializes participants, no worker enqueue, no bill yet
911
945
  ish ask create --workspace w-6ec --name "tagline AB" \\
912
946
  --prompt "Which sounds better?" \\
913
947
  --variant text:"Short and punchy." \\
@@ -921,7 +955,7 @@ ish ask create --workspace w-6ec --name "tagline AB" \\
921
955
  ish ask dispatch a-6ec --wait
922
956
  \`\`\`
923
957
 
924
- \`--no-dispatch\` requires audience flags (testers are still materialized
958
+ \`--no-dispatch\` requires people flags (participants are still materialized
925
959
  at create time — only the worker enqueue and billing are deferred). It
926
960
  is incompatible with \`--wait\` since there is nothing to wait for.
927
961
 
@@ -986,7 +1020,7 @@ When the ask has 2+ rounds, \`ask results\` also includes a top-level
986
1020
  \`ish ask retry <ask> --round N\` re-dispatches only the ERRORED
987
1021
  responses on a round. COMPLETED responses are left untouched (their
988
1022
  answers are the source of truth). Use this after a partial failure
989
- (e.g. 4 of 5 testers errored on round 1) — fix the underlying cause,
1023
+ (e.g. 4 of 5 participants errored on round 1) — fix the underlying cause,
990
1024
  then \`ask retry\` to backfill the missing rows. Idempotent: zero-errored
991
1025
  is a no-op. Add \`--wait\` to block until the retried round settles.
992
1026
 
@@ -1004,7 +1038,7 @@ abort without parsing prose.
1004
1038
  \`ish ask add-questions --round N --questions ./qs.json\` is **additive
1005
1039
  by default**: prior phase-1 outputs (comment, pick, ratings) are
1006
1040
  preserved on every non-errored response, and the worker only answers
1007
- the newly-added questions for each tester. Existing picks stay stable.
1041
+ the newly-added questions for each participant. Existing picks stay stable.
1008
1042
 
1009
1043
  Pass \`--redispatch-all\` for the legacy reset behavior — useful when a
1010
1044
  question is sufficiently different that you want fresh first
@@ -1033,11 +1067,11 @@ round-trips when you know them up front:
1033
1067
  agent-tool result budgets. Pass
1034
1068
  \`include_accessibility_profile=true\` to include it. Mirrors the
1035
1069
  existing \`include_bio=false\` default — same opt-in pattern.
1036
- - **\`ask_testers\` uses \`dispatch_into_round\`, not \`round\`.** The
1070
+ - **\`ask_participants\` uses \`dispatch_into_round\`, not \`round\`.** The
1037
1071
  parameter name was renamed from the ambiguous \`round\` (which read
1038
1072
  as "start from round N") to the verbatim \`dispatch_into_round\`
1039
- ("add these new testers into round N"). Behavior is unchanged —
1040
- it appends testers to the named round on an existing ask, it does
1073
+ ("add these new participants into round N"). Behavior is unchanged —
1074
+ it appends participants to the named round on an existing ask, it does
1041
1075
  not roll back or restart any prior round.
1042
1076
 
1043
1077
  ## Variant syntax
@@ -1052,14 +1086,14 @@ round-trips when you know them up front:
1052
1086
  ## Related
1053
1087
 
1054
1088
  - \`concepts/round\` — what a round is and how it executes.
1055
- - \`concepts/audience\` — how testers are chosen at ask creation.
1089
+ - \`concepts/people\` — how participants are chosen at ask creation.
1056
1090
  - \`concepts/run-verbs\` — \`ish ask run\` vs \`ish study run\`.
1057
1091
  - \`reference/credits\` — ask rounds bill 1 credit per successful response.
1058
1092
  `;
1059
1093
  const CONCEPT_ROUND = `# concept: round
1060
1094
 
1061
1095
  A **round** is the unit of execution within an ask. Each round shows the
1062
- ask's audience a prompt + variants and collects reactions.
1096
+ ask.s participants a prompt + variants and collects reactions.
1063
1097
 
1064
1098
  - Up to 5 rounds per ask.
1065
1099
  - Rounds are 1-indexed in user-facing flags (\`--round 2\`) but stored
@@ -1081,7 +1115,7 @@ ish ask results a-6ec --round 1
1081
1115
 
1082
1116
  Appending questions to a completed round preserves prior data — variant
1083
1117
  comments, picks, ratings, and earlier-question answers all stay. Only
1084
- the new question(s) get dispatched to the existing testers. Cost is
1118
+ the new question(s) get dispatched to the existing participants. Cost is
1085
1119
  roughly N phase-2 LLM calls instead of 2N (no phase-1 re-run). Errored
1086
1120
  responses are skipped entirely; completed responses flip to PENDING and
1087
1121
  re-finalize after the new question is answered.
@@ -1089,51 +1123,67 @@ re-finalize after the new question is answered.
1089
1123
  ## Related
1090
1124
 
1091
1125
  - \`concepts/ask\` — the parent artifact.
1092
- - \`concepts/audience\` — fixed at ask creation; rounds reuse it.
1126
+ - \`concepts/people\` — fixed at ask creation; rounds reuse it.
1093
1127
  `;
1094
- const CONCEPT_PROFILE = `# concept: tester profile
1128
+ const CONCEPT_PROFILE = `# concept: person
1095
1129
 
1096
- A **tester profile** is a reusable audience persona — the simulated
1097
- human whose behaviour drives a tester instance during a study or ask.
1130
+ A **person** is a reusable persona — the simulated
1131
+ human whose behaviour drives a participant instance during a study or ask.
1098
1132
 
1099
1133
  - Alias prefix: \`tp-\`
1100
1134
  - Lives at the workspace level, reusable across studies and asks.
1101
- - Distinct from a "tester" (\`t-\`) — a tester is one *instance* of a
1135
+ - Distinct from a "participant" (\`pt-\`) — a participant is one *instance* of a
1102
1136
  profile inside one iteration.
1103
1137
 
1104
1138
  ## Generation vs. manual creation
1105
1139
 
1106
- \`ish profile generate\` runs the same audience-generation flow used in
1107
- the web UI: an LLM reads your description and any uploaded sources
1108
- (transcripts, customer records, audio, images) and returns either a
1109
- single profile or a full audience.
1140
+ \`ish person generate\` runs an agentic generation job: it reads your
1141
+ description (a researcher brief) and any uploaded sources (transcripts,
1142
+ emails, customer records, audio, images) describing how real people
1143
+ reacted, then produces people PLUS scenarios grounded in those
1144
+ reactions. It is async — ish enqueues the job, polls until it's done
1145
+ (~30-60s), then prints the resulting profile(s) with their scenarios.
1146
+ Provide \`--description\` (>=10 chars) and/or at least one \`--source\`.
1110
1147
 
1111
1148
  \`\`\`
1112
1149
  # 5 profiles from a written brief:
1113
- ish profile generate \\
1150
+ ish person generate \\
1114
1151
  --description "Tech-savvy millennials in the US who use mobile banking" \\
1115
1152
  --count 5
1116
1153
 
1117
1154
  # One profile from a transcript (auto-uploaded):
1118
- ish profile generate --source ./interviews/sarah.txt --count 1
1155
+ ish person generate --source ./interviews/sarah.txt --count 1
1119
1156
 
1120
1157
  # Audio with diarization:
1121
- ish profile generate --description "Voices behind support tickets" \\
1158
+ ish person generate --description "Voices behind support tickets" \\
1122
1159
  --source ./call.mp3 --diarize --count 3
1160
+
1161
+ # Ground a profile in how someone reacted to a real artifact:
1162
+ ish source upload ./proposal.eml --description "called this proposal lazy and vague"
1163
+ # → ps-3a4
1164
+ ish person generate --description "Skeptical enterprise buyer" \\
1165
+ --source ps-3a4 --count 1 --json
1123
1166
  \`\`\`
1124
1167
 
1168
+ The per-source \`--description\` (set on \`source upload\`, or
1169
+ \`--source-description\` on an inline path) is the researcher note: how
1170
+ that person reacted to THAT file. The job grounds each scenario in those
1171
+ notes plus the source content. Pass \`--no-scenarios\` to skip fetching
1172
+ the scenarios. The job keeps running server-side past \`--timeout\`
1173
+ (default 600s) — re-poll the job, don't re-enqueue.
1174
+
1125
1175
  For explicit control over uploads (reusing one source across runs):
1126
1176
 
1127
1177
  \`\`\`
1128
1178
  ish source upload ./call.mp3 --diarize
1129
- # → tps-3a4 (status: processed)
1130
- ish profile generate --source tps-3a4 --count 4
1179
+ # → ps-3a4 (status: processed)
1180
+ ish person generate --source ps-3a4 --count 4
1131
1181
  \`\`\`
1132
1182
 
1133
1183
  ## Manual create
1134
1184
 
1135
1185
  \`\`\`
1136
- ish profile create --file profile.json
1186
+ ish person create --file profile.json
1137
1187
  \`\`\`
1138
1188
 
1139
1189
  Expected JSON: \`{ "name": "...", "type": "ai", "gender": "female",
@@ -1141,10 +1191,12 @@ Expected JSON: \`{ "name": "...", "type": "ai", "gender": "female",
1141
1191
 
1142
1192
  ## Generation behavior to expect
1143
1193
 
1144
- - **Latency**: \`profile generate\` is LLM-backed and typically takes
1145
- 10–20s for 1–5 profiles. The CLI emits stderr progress lines
1146
- (\`generating N profiles…\` then \`generated N profiles\`) so you
1147
- know it's not stuck. Suppress with \`--quiet\`.
1194
+ - **Latency**: \`person generate\` runs an async job that typically
1195
+ takes ~30-60s. The CLI enqueues, then polls every ~2.5s and prints a
1196
+ stderr status line each time the job's progress message changes, so you
1197
+ know it's not stuck. Suppress with \`--quiet\`. Bound the wait with
1198
+ \`--timeout\` (seconds, default 600); on timeout the job keeps running
1199
+ server-side, so re-poll rather than re-enqueueing.
1148
1200
  - **Brief fidelity**: bios reference domain-specific terms from your
1149
1201
  description verbatim or as close paraphrase. If you mention
1150
1202
  \`F-skatt\`, "manual Excel invoicing", "Stripe payouts", or similar
@@ -1152,16 +1204,16 @@ Expected JSON: \`{ "name": "...", "type": "ai", "gender": "female",
1152
1204
  each generated bio's daily-routine framing — not sanded down to
1153
1205
  generic prose.
1154
1206
  - **DOB diversity**: month-and-day are derived from a deterministic
1155
- per-profile hash so birthdays spread across the year (no more
1156
- every-profile-on-\`06-15\`). Year follows the requested age.
1207
+ per-person hash so birthdays spread across the year (no more
1208
+ every-person-on-\`06-15\`). Year follows the requested age.
1157
1209
  Re-generating the same name/country/occupation/age yields the
1158
1210
  same DOB.
1159
1211
 
1160
- ## Structured profile fields
1212
+ ## Structured person fields
1161
1213
 
1162
1214
  Five universal enums + a versioned accessibility JSONB live on every
1163
- TesterProfile. Values are snake_case and match
1164
- \`https://ishlabs.io/spec/profile-enums.v1.json\` byte-for-byte.
1215
+ Person. Values are snake_case and match
1216
+ \`https://ishlabs.io/spec/person-enums.v1.json\` byte-for-byte.
1165
1217
 
1166
1218
  - \`education_level\`: \`less_than_secondary\`, \`secondary\`,
1167
1219
  \`some_post_secondary\`, \`vocational_or_associate\`, \`bachelor\`, \`graduate\`
@@ -1182,12 +1234,12 @@ TesterProfile. Values are snake_case and match
1182
1234
  \`auditory\`, \`motor\`, \`cognitive\`, \`data\` groups, plus
1183
1235
  \`assistive_tech: string[]\` and \`notes\`. Empty \`{}\` means "no
1184
1236
  accessibility configuration declared". Schema:
1185
- \`https://ishlabs.io/spec/accessibility-profile-schema.v1.json\`.
1237
+ \`https://ishlabs.io/spec/accessibility-person-schema.v1.json\`.
1186
1238
 
1187
- Set them on \`ish profile update\`:
1239
+ Set them on \`ish person update\`:
1188
1240
 
1189
1241
  \`\`\`
1190
- ish profile update tp-1b9 \\
1242
+ ish person update p-1b9 \\
1191
1243
  --education-level bachelor \\
1192
1244
  --household couple_with_kids \\
1193
1245
  --locale-type suburban \\
@@ -1195,33 +1247,33 @@ ish profile update tp-1b9 \\
1195
1247
  --employment-status employed_full_time
1196
1248
 
1197
1249
  # accessibility_profile accepts inline JSON or a path:
1198
- ish profile update tp-1b9 --accessibility-profile '{
1250
+ ish person update p-1b9 --accessibility-profile '{
1199
1251
  "version": "1.0",
1200
1252
  "visual": {"uses_screen_reader": true, "text_size": "large"},
1201
1253
  "cognitive": {"reduce_motion": true},
1202
1254
  "assistive_tech": ["VoiceOver"]
1203
1255
  }'
1204
1256
 
1205
- ish profile update tp-1b9 --accessibility-profile ./a11y.json
1257
+ ish person update p-1b9 --accessibility-profile ./a11y.json
1206
1258
  \`\`\`
1207
1259
 
1208
1260
  The legacy \`--tech-savviness\` flag was removed in
1209
- \`profile-schema-v2\`; passing it now produces commander's standard
1261
+ \`person-schema-v2\`; passing it now produces commander's standard
1210
1262
  "unknown option" error.
1211
1263
 
1212
1264
  ## Related
1213
1265
 
1214
- - \`concepts/source\` — the inputs to \`profile generate\`.
1215
- - \`concepts/audience\` — how profiles get selected into a run.
1216
- - \`guides/build-specific-tester\` — iterative probe loop
1217
- (\`profile suggest-scenarios\` + \`profile evidence add\`/\`list\`)
1266
+ - \`concepts/source\` — the inputs to \`person generate\`.
1267
+ - \`concepts/people\` — how profiles get selected into a run.
1268
+ - \`guides/build-specific-person\` — iterative probe loop
1269
+ (\`person suggest-scenarios\` + \`person evidence add\`/\`list\`)
1218
1270
  for crafting one specific persona, distinct from the
1219
- audience-generation flow.
1220
- - \`reference/billing-limits\` — \`maxCustomTesterProfiles\` cap on profile creation.
1271
+ people-generation flow.
1272
+ - \`reference/billing-limits\` — \`maxCustomPersons\` cap on person creation.
1221
1273
  `;
1222
1274
  const CONCEPT_SOURCE = `# concept: source
1223
1275
 
1224
- A **source** is an input to \`ish profile generate\`: a transcript,
1276
+ A **source** is an input to \`ish person generate\`: a transcript,
1225
1277
  audio file, image, or PDF that an LLM reads to ground generated profiles
1226
1278
  in real customer evidence.
1227
1279
 
@@ -1231,36 +1283,36 @@ in real customer evidence.
1231
1283
 
1232
1284
  ## Two ways to use a source
1233
1285
 
1234
- 1. **Inline** — pass a local file directly to \`profile generate\`. The
1286
+ 1. **Inline** — pass a local file directly to \`person generate\`. The
1235
1287
  file is uploaded and processed in-line:
1236
1288
  \`\`\`
1237
- ish profile generate --source ./call.mp3 --diarize --count 3
1289
+ ish person generate --source ./call.mp3 --diarize --count 3
1238
1290
  \`\`\`
1239
1291
 
1240
1292
  2. **Upload-then-reuse** — upload once, reference the alias from many
1241
1293
  \`generate\` runs:
1242
1294
  \`\`\`
1243
1295
  ish source upload ./call.mp3 --diarize
1244
- # → tps-3a4 (status: processed)
1245
- ish profile generate --source tps-3a4 --count 4
1296
+ # → ps-3a4 (status: processed)
1297
+ ish person generate --source ps-3a4 --count 4
1246
1298
  \`\`\`
1247
1299
 
1248
1300
  ## Inspect
1249
1301
 
1250
1302
  \`\`\`
1251
- ish source get tps-3a4
1303
+ ish source get ps-3a4
1252
1304
  \`\`\`
1253
1305
 
1254
1306
  ## Related
1255
1307
 
1256
- - \`concepts/profile\` — sources feed profile generation.
1308
+ - \`concepts/person\` — sources feed profile generation.
1257
1309
  `;
1258
- const CONCEPT_AUDIENCE = `# concept: audience selection
1310
+ const CONCEPT_PEOPLE = `# concept: people selection
1259
1311
 
1260
- Both \`ish study run\` and \`ish ask run --new\` accept the same audience
1312
+ Both \`ish study run\` and \`ish ask run --new\` accept the same people-selection
1261
1313
  flags. Two ways to select:
1262
1314
 
1263
- 1. **Explicit profile IDs** — \`--profile tp-795,tp-af2\` (or repeated).
1315
+ 1. **Explicit profile IDs** — \`--person p-795,p-af2\` (or repeated).
1264
1316
  2. **Demographic-filtered sample from the workspace pool** — combine any
1265
1317
  of the filters with \`--sample <N>\` or \`--all\` / \`--all-simulatable\`:
1266
1318
  - \`--country SE,NO\` (repeatable)
@@ -1285,14 +1337,14 @@ the filter set, not both.
1285
1337
  When a filter combination matches zero profiles, the error message
1286
1338
  includes the top three populated countries that satisfy your *other*
1287
1339
  filters — so you can pivot to a country with actual coverage without a
1288
- second \`profile list\` round-trip:
1340
+ second \`person list\` round-trip:
1289
1341
 
1290
1342
  \`\`\`
1291
1343
  $ ish study run --country XX --min-age 35 --sample 5
1292
- Error: No simulatable AI tester profiles in workspace w-b32 match:
1344
+ Error: No simulatable AI people in workspace w-b32 match:
1293
1345
  --country XX --min-age 35.
1294
1346
  Populated countries with these other filters: SE (12), DE (8), NL (3).
1295
- Broaden your filters or run \`ish profile list\` to inspect the pool.
1347
+ Broaden your filters or run \`ish person list\` to inspect the pool.
1296
1348
  \`\`\`
1297
1349
 
1298
1350
  The suggestion is best-effort — it never replaces the original error,
@@ -1300,13 +1352,13 @@ just augments it.
1300
1352
 
1301
1353
  ## Audience-build behaviors to know before dispatch
1302
1354
 
1303
- Two adjacent footguns surface most often on first-time audience
1355
+ Two adjacent footguns surface most often on first-time people
1304
1356
  construction. Both are documented here because they cost a round-trip
1305
1357
  to discover by experiment.
1306
1358
 
1307
1359
  ### \`--occupation\` is a loose substring match
1308
1360
 
1309
- \`audience_build\` and the \`--occupation\` flag treat the value as a
1361
+ \`group_build\` and the \`--occupation\` flag treat the value as a
1310
1362
  **loose, case-insensitive substring filter**, not a whole-token or
1311
1363
  taxonomy match. \`--occupation manager\` will match hotel managers,
1312
1364
  retail store managers, bank branch managers: anything containing the
@@ -1321,7 +1373,7 @@ you usually want:
1321
1373
  - **Pair with other filters**: \`--occupation manager --min-age 28
1322
1374
  --country US --country SE\` narrows even a loose substring
1323
1375
  meaningfully.
1324
- - **Preview before dispatch**: \`audience_build\` returns a
1376
+ - **Preview before dispatch**: \`group_build\` returns a
1325
1377
  \`match_preview\` summary on the response — a 1-line histogram of
1326
1378
  matched occupations (e.g. \`"matched 17 — software developer (12),
1327
1379
  DevOps engineer (3), other (2)"\`). Read it before
@@ -1330,34 +1382,34 @@ you usually want:
1330
1382
 
1331
1383
  ### The public profile pool skews non-tech / non-Western
1332
1384
 
1333
- The default public tester-profile pool was built from a broad
1385
+ The default public person pool was built from a broad
1334
1386
  demographic sample — so a substring like \`"software engineering
1335
1387
  manager"\` may return only a handful of matches, while \`"hotel
1336
1388
  manager"\` or \`"retail associate"\` return many. Two adaptations:
1337
1389
 
1338
- - **Don't assume Silicon Valley defaults.** A criteria-driven audience
1390
+ - **Don't assume Silicon Valley defaults.** A criteria-driven group
1339
1391
  that works on a private testing pool may resolve to a much smaller
1340
1392
  count in the public pool. Read the \`match_preview\` (or count) on
1341
- every \`audience_build\` before dispatching a run that depends on
1393
+ every \`group_build\` before dispatching a run that depends on
1342
1394
  reaching N matches.
1343
1395
  - **Seed your own pool when you need a specific archetype.** If the
1344
- public pool is genuinely thin for your role, generate the audience
1345
- yourself via \`ish profile generate --description "..."\` — that
1396
+ public pool is genuinely thin for your role, generate the people
1397
+ yourself via \`ish person generate --description "..."\` — that
1346
1398
  produces profiles plausible for the role you described, regardless
1347
- of public-pool composition. See \`concepts/profile\`.
1399
+ of public-pool composition. See \`concepts/person\`.
1348
1400
 
1349
1401
  ## Defaults
1350
1402
 
1351
- - \`ish study run\` with no audience flags → reuses the iteration's
1352
- existing testers. Ideal for re-running the same audience.
1353
- - \`ish ask run\` (without \`--new\`) → cannot change audience; the ask
1403
+ - \`ish study run\` with no people flags → reuses the iteration's
1404
+ existing participants. Ideal for re-running the same participants.
1405
+ - \`ish ask run\` (without \`--new\`) → cannot change participants; the ask
1354
1406
  fixes it at creation. Audience flags only apply with \`--new\`.
1355
1407
 
1356
1408
  ## Examples
1357
1409
 
1358
1410
  \`\`\`
1359
1411
  # Explicit:
1360
- ish study run --profile tp-795,tp-af2
1412
+ ish study run --person p-795,p-af2
1361
1413
 
1362
1414
  # Sample 3 Swedish profiles aged 35-50:
1363
1415
  ish study run --country SE --min-age 35 --max-age 50 --sample 3
@@ -1371,7 +1423,7 @@ ish study run --bio "screen reader" --all
1371
1423
  # Every female profile in the workspace:
1372
1424
  ish study run --gender female --all
1373
1425
 
1374
- # Ask + audience in one shot:
1426
+ # Ask + people in one shot:
1375
1427
  ish ask run --new --name "SE 35-50" --prompt "Which sounds better?" \\
1376
1428
  --variant text:"A" --variant text:"B" \\
1377
1429
  --country SE --min-age 35 --max-age 50 --sample 10
@@ -1379,14 +1431,14 @@ ish ask run --new --name "SE 35-50" --prompt "Which sounds better?" \\
1379
1431
 
1380
1432
  ## Related
1381
1433
 
1382
- - \`concepts/profile\` — generate profiles to fill the pool.
1383
- - \`concepts/run-verbs\` — when audience flags apply.
1434
+ - \`concepts/person\` — generate profiles to fill the pool.
1435
+ - \`concepts/run-verbs\` — when people flags apply.
1384
1436
  `;
1385
1437
  const CONCEPT_SITE_ACCESS = `# concept: site access
1386
1438
 
1387
1439
  For interactive studies that target a gated URL — HTTP basic auth,
1388
1440
  session-cookie walls (Vercel preview, Lovable, etc.), or login forms —
1389
- configure credentials on the workspace once. Testers reuse them when a
1441
+ configure credentials on the workspace once. Participants reuse them when a
1390
1442
  study points at a matching origin.
1391
1443
 
1392
1444
  Credentials are encrypted at rest. The CLI never reads them back; it
@@ -1404,7 +1456,7 @@ ish workspace site-access basic-auth --username alice --password hunter2
1404
1456
  # Session cookie:
1405
1457
  ish workspace site-access cookie --name session --value abc123
1406
1458
 
1407
- # Login form (typed by the tester):
1459
+ # Login form (typed by the participant):
1408
1460
  ish workspace site-access login --username demo --password demo
1409
1461
 
1410
1462
  # Mark as public (silence the "credentials needed?" prompt):
@@ -1495,7 +1547,7 @@ or shouldn't be committed to a config JSON file, use a secret.
1495
1547
  `;
1496
1548
  const CONCEPT_RUN_VERBS = `# concept: run verbs — \`study run\` vs \`ask run\`
1497
1549
 
1498
- Both verbs dispatch simulations against an audience, but the lifecycle
1550
+ Both verbs dispatch simulations against a group of people, but the lifecycle
1499
1551
  and what they target differ.
1500
1552
 
1501
1553
  ## Side by side
@@ -1505,23 +1557,23 @@ and what they target differ.
1505
1557
  | Default | latest iteration of the active study | append a round to the active ask |
1506
1558
  | Fresh setup | \`ish iteration create …\` first, then run | \`--new\` (creates ask + round 1 in one shot) |
1507
1559
  | Specific target| \`--iteration <id>\` | positional ask id (\`a-6ec\`) |
1508
- | Audience | \`--profile\` OR filters with \`--sample\`/\`--all\` — else reuse iteration's testers | only at \`--new\`; fixed for the ask afterwards |
1509
- | Output unit | per-tester interactions + questionnaire answers | per-tester reactions per round |
1560
+ | Audience | \`--profile\` OR filters with \`--sample\`/\`--all\` — else reuse iteration's participants | only at \`--new\`; fixed for the ask afterwards |
1561
+ | Output unit | per-participant interactions + questionnaire answers | per-participant reactions per round |
1510
1562
 
1511
1563
  ## Decision rule
1512
1564
 
1513
- - The tester needs to **do** something on a real surface
1565
+ - The participant needs to **do** something on a real surface
1514
1566
  (URL/app/document)? → study.
1515
- - The tester needs to **react** to one or more variants
1567
+ - The participant needs to **react** to one or more variants
1516
1568
  (text/image) of creative? → ask.
1517
1569
 
1518
1570
  ## Common commands
1519
1571
 
1520
1572
  \`\`\`
1521
- # Study — reuse iteration testers, block until done:
1573
+ # Study — reuse iteration participants, block until done:
1522
1574
  ish study run --wait
1523
1575
 
1524
- # Study — fresh audience by demographic:
1576
+ # Study — fresh group by demographic:
1525
1577
  ish study run --country SE --min-age 35 --sample 3
1526
1578
 
1527
1579
  # Ask — append a round:
@@ -1533,35 +1585,35 @@ ish ask run --new --name "tagline AB" \\
1533
1585
  --variant text:"A" --variant text:"B" --sample 30 --wants-pick --wait
1534
1586
  \`\`\`
1535
1587
 
1536
- ## Tracking individual testers after \`study run\`
1588
+ ## Tracking individual participants after \`study run\`
1537
1589
 
1538
- \`ish study run --json\` returns a top-level \`tester_aliases[]\` and
1539
- \`tester_ids[]\` for the testers it just dispatched. Pass either to the
1590
+ \`ish study run --json\` returns a top-level \`participant_aliases[]\` and
1591
+ \`participant_ids[]\` for the participants it just dispatched. Pass either to the
1540
1592
  low-level lifecycle verbs:
1541
1593
 
1542
1594
  \`\`\`
1543
- ish study run --study s-b2c -y --json | jq -r '.tester_aliases[]' # → t-072, t-1ed, ...
1595
+ ish study run --study s-b2c -y --json | jq -r '.participant_aliases[]' # → pt-072, pt-1ed, ...
1544
1596
 
1545
- ish study poll <tester_id> # one-shot status for one tester
1546
- ish study wait <tester_id> --timeout 600 # block until that tester finishes
1547
- ish study cancel <tester_id> # cancel a running simulation
1548
- ish study extend <tester_id> --add-steps 10 # resume a terminal tester with N more steps
1597
+ ish study poll <participant_id> # one-shot status for one participant
1598
+ ish study wait <participant_id> --timeout 600 # block until that participant finishes
1599
+ ish study cancel <participant_id> # cancel a running simulation
1600
+ ish study extend <participant_id> --add-steps 10 # resume a terminal participant with N more steps
1549
1601
  \`\`\`
1550
1602
 
1551
- \`<tester_id>\` accepts a tester alias (\`t-…\`) or a full UUID. The
1603
+ \`<participant_id>\` accepts a participant alias (\`t-…\`) or a full UUID. The
1552
1604
  study-level \`poll\`/\`wait\` forms also exist (\`--study <id>\` /
1553
1605
  \`--iteration <id>\`) for whole-batch progress.
1554
1606
 
1555
1607
  \`cancel\` and \`extend\` form a reversible stop/start pair. \`cancel\`
1556
- walks a running tester to a terminal \`cancelled\` status (no row
1557
- removed); \`extend\` then spawns a fresh tester branched from the
1558
- cancelled tester's last interaction. See
1608
+ walks a running participant to a terminal \`cancelled\` status (no row
1609
+ removed); \`extend\` then spawns a fresh participant branched from the
1610
+ cancelled participant's last interaction. See
1559
1611
  \`concepts/extending-a-simulation\` for the full mental model.
1560
1612
 
1561
1613
  ## Related
1562
1614
 
1563
1615
  - \`reference/json-mode\` — output modes (display vs capture vs chain).
1564
- Use \`--get tester_aliases\` to capture the run's testers without
1616
+ Use \`--get participant_aliases\` to capture the run's participants without
1565
1617
  piping through \`jq\`. \`--human\` forces table output even through
1566
1618
  \`tee\`/redirection.
1567
1619
  - \`concepts/extending-a-simulation\` — \`study extend\` flow, when to
@@ -1569,58 +1621,58 @@ cancelled tester's last interaction. See
1569
1621
  `;
1570
1622
  const CONCEPT_EXTENDING_SIMULATION = `# concept: extending a simulation
1571
1623
 
1572
- \`ish study extend <tester_id>\` resumes a **terminal** tester with
1624
+ \`ish study extend <participant_id>\` resumes a **terminal** participant with
1573
1625
  more interactions — and optionally a mid-run instruction. The source
1574
- tester is left untouched; a **new** tester row is spawned under the
1626
+ participant is left untouched; a **new** participant row is spawned under the
1575
1627
  same iteration, branched from the source's last interaction. Use it
1576
- when a run hits the \`--max-interactions\` cap before the tester
1628
+ when a run hits the \`--max-interactions\` cap before the participant
1577
1629
  finished, or when you want to probe a "what if I had told them X
1578
1630
  mid-run?" scenario without restarting from scratch.
1579
1631
 
1580
1632
  ## When extend is the right verb
1581
1633
 
1582
- - Run hit the step cap (\`--max-interactions\`) before the tester
1634
+ - Run hit the step cap (\`--max-interactions\`) before the participant
1583
1635
  completed the assignment — give it 10 more steps to push through.
1584
- - Tester veered off into a dead-end — cancel it, then extend with an
1636
+ - Participant veered off into a dead-end — cancel it, then extend with an
1585
1637
  instruction redirecting it ("Stop browsing the blog. Open the pricing
1586
1638
  page and try to add a seat.").
1587
- - You want to test how a tester reacts to a mid-run change you didn't
1639
+ - You want to test how a participant reacts to a mid-run change you didn't
1588
1640
  capture in the original assignment — without re-running the whole
1589
1641
  cohort.
1590
1642
 
1591
1643
  When extend is **not** the right verb:
1592
1644
 
1593
- - Source tester is still RUNNING. \`cancel\` it first, then extend.
1645
+ - Source participant is still RUNNING. \`cancel\` it first, then extend.
1594
1646
  Extend refuses non-terminal sources server-side.
1595
- - You want a fresh cohort with new audience flags. Use \`study run\`
1647
+ - You want a fresh cohort with new people flags. Use \`study run\`
1596
1648
  with \`--profile\` / \`--sample\` / \`--all\` instead — extend is a
1597
- per-tester resume, not a batch op.
1649
+ per-participant resume, not a batch op.
1598
1650
  - You want to change the iteration's URL or content. Edit the iteration
1599
1651
  itself (\`iteration update\` or a fresh iteration) — extend always
1600
1652
  inherits the source's iteration config.
1601
1653
 
1602
1654
  ## Mental model — cancel + extend are a reversible pair
1603
1655
 
1604
- \`cancel\` and \`extend\` are siblings in the tester lifecycle:
1656
+ \`cancel\` and \`extend\` are siblings in the participant lifecycle:
1605
1657
 
1606
1658
  \`\`\`
1607
- RUNNING ──(cancel)──▶ CANCELLED ──(extend)──▶ new RUNNING tester
1659
+ RUNNING ──(cancel)──▶ CANCELLED ──(extend)──▶ new RUNNING participant
1608
1660
  (branched from the
1609
- cancelled tester's
1661
+ cancelled participant's
1610
1662
  last interaction)
1611
1663
 
1612
- COMPLETED / FAILED ──(extend)──▶ new RUNNING tester
1664
+ COMPLETED / FAILED ──(extend)──▶ new RUNNING participant
1613
1665
  \`\`\`
1614
1666
 
1615
- \`cancel\` is non-destructive — the tester row, every interaction, every
1667
+ \`cancel\` is non-destructive — the participant row, every interaction, every
1616
1668
  screenshot, and the questionnaire answers all survive. \`extend\` then
1617
- forks from the last interaction to keep the new tester's history
1669
+ forks from the last interaction to keep the new participant's history
1618
1670
  seamlessly continuous.
1619
1671
 
1620
1672
  ## Flags
1621
1673
 
1622
1674
  \`\`\`
1623
- ish study extend <tester_id>
1675
+ ish study extend <participant_id>
1624
1676
  [--add-steps <n>] # extra steps, 1-50, default 10
1625
1677
  [--instruction <text|@path|->] # optional mid-run user message
1626
1678
  [--wait] # block until terminal
@@ -1633,17 +1685,17 @@ CLI:
1633
1685
 
1634
1686
  \`\`\`bash
1635
1687
  # Inline:
1636
- ish study extend t-072 --instruction "Switch to the German pricing page."
1688
+ ish study extend pt-072 --instruction "Switch to the German pricing page."
1637
1689
 
1638
1690
  # From a file (long-form prompts, version-controlled):
1639
- ish study extend t-072 --instruction @/tmp/redirect.md
1691
+ ish study extend pt-072 --instruction @/tmp/redirect.md
1640
1692
 
1641
1693
  # From stdin (pipe-friendly):
1642
- echo "Try the search bar instead." | ish study extend t-072 --instruction -
1694
+ echo "Try the search bar instead." | ish study extend pt-072 --instruction -
1643
1695
  \`\`\`
1644
1696
 
1645
1697
  The instruction is sent to the backend as \`user_message\`. The new
1646
- tester treats it as **overriding direction** for the rest of the run —
1698
+ participant treats it as **overriding direction** for the rest of the run —
1647
1699
  the backend surfaces it in a dedicated \`<user_added_instructions>\`
1648
1700
  block on every prompt, not just the first turn, so the LLM doesn't
1649
1701
  forget about it as the run goes on.
@@ -1654,10 +1706,10 @@ Default (no \`--wait\`):
1654
1706
 
1655
1707
  \`\`\`json
1656
1708
  {
1657
- "tester_id": "<new-uuid>",
1658
- "tester_alias": "t-xyz",
1659
- "source_tester_id": "<source-uuid>",
1660
- "source_alias": "t-abc",
1709
+ "participant_id": "<new-uuid>",
1710
+ "participant_alias": "pt-xyz",
1711
+ "source_participant_id": "<source-uuid>",
1712
+ "source_alias": "pt-abc",
1661
1713
  "study_id": "<study-uuid>",
1662
1714
  "job_id": "<job-uuid>",
1663
1715
  "additional_steps": 10,
@@ -1666,7 +1718,7 @@ Default (no \`--wait\`):
1666
1718
  }
1667
1719
  \`\`\`
1668
1720
 
1669
- With \`--wait\`, a \`result\` field is appended once the new tester
1721
+ With \`--wait\`, a \`result\` field is appended once the new participant
1670
1722
  reaches a terminal status:
1671
1723
 
1672
1724
  \`\`\`json
@@ -1675,31 +1727,31 @@ reaches a terminal status:
1675
1727
  "result": {
1676
1728
  "status": "completed",
1677
1729
  "interaction_count": 14,
1678
- "tester_name": "Anna, 34, Munich"
1730
+ "participant_name": "Anna, 34, Munich"
1679
1731
  }
1680
1732
  }
1681
1733
  \`\`\`
1682
1734
 
1683
- UUID fields (\`tester_id\`, \`source_tester_id\`, \`study_id\`, \`job_id\`)
1684
- are preserved in lean output because the new \`tester_id\` is the
1735
+ UUID fields (\`participant_id\`, \`source_participant_id\`, \`study_id\`, \`job_id\`)
1736
+ are preserved in lean output because the new \`participant_id\` is the
1685
1737
  load-bearing return value — same exception \`study run\` makes.
1686
1738
 
1687
1739
  ## Errors
1688
1740
 
1689
1741
  | Backend | CLI behavior | Exit |
1690
1742
  |---|---|---|
1691
- | Source not terminal (RUNNING / QUEUED) | \`Tester is still running — cancel it first or wait for completion.\` | 2 |
1692
- | Source tester not found | \`Tester not found: <id>\` | 4 |
1743
+ | Source not terminal (RUNNING / QUEUED) | \`Participant is still running — cancel it first or wait for completion.\` | 2 |
1744
+ | Source participant not found | \`Participant not found: <id>\` | 4 |
1693
1745
  | \`additional_steps\` out of range | Client-side parser rejects before the network call | 2 |
1694
1746
  | Insufficient credits | Bubbles the server message; retry only after topping up | 5 |
1695
- | Wait timed out (\`--wait\` only) | \`WaitTimeoutError\` envelope with current status under \`progress.rows[0]\` — the run keeps going server-side; resume with \`study wait <new-tester>\` | 5 |
1747
+ | Wait timed out (\`--wait\` only) | \`WaitTimeoutError\` envelope with current status under \`progress.rows[0]\` — the run keeps going server-side; resume with \`study wait <new-participant>\` | 5 |
1696
1748
 
1697
1749
  ## Cost model
1698
1750
 
1699
1751
  \`extend\` charges credits for **only \`additional_steps\`**, not for
1700
1752
  the source's original \`max_interactions\` cap. The formula is the same
1701
1753
  as \`study run\` for interactive runs: \`max(1, round(N / 10))\` per
1702
- tester. So \`--add-steps 10\` costs **1 credit**; \`--add-steps 50\`
1754
+ participant. So \`--add-steps 10\` costs **1 credit**; \`--add-steps 50\`
1703
1755
  costs **5 credits**. See \`reference/credits\` for the full table.
1704
1756
 
1705
1757
  ## Worked example — push past the step cap
@@ -1707,25 +1759,25 @@ costs **5 credits**. See \`reference/credits\` for the full table.
1707
1759
  \`\`\`bash
1708
1760
  # 1. Run a study with a small step cap to feel the limit:
1709
1761
  ish study run --sample 1 --max-interactions 5 --wait
1710
- # → tester t-072 (status: completed_with_errors, hit cap on step 5)
1762
+ # → participant pt-072 (status: completed_with_errors, hit cap on step 5)
1711
1763
 
1712
1764
  # 2. Inspect what happened:
1713
- ish study tester t-072 --summary
1765
+ ish study participant pt-072 --summary
1714
1766
 
1715
1767
  # 3. Give it 15 more steps:
1716
- ish study extend t-072 --add-steps 15 --wait --timeout 600
1717
- # → new tester t-9af, status: completed, 18 interactions total
1768
+ ish study extend pt-072 --add-steps 15 --wait --timeout 600
1769
+ # → new participant pt-9af, status: completed, 18 interactions total
1718
1770
 
1719
- # 4. Read the new tester's transcript:
1720
- ish study tester t-9af --summary
1771
+ # 4. Read the new participant's transcript:
1772
+ ish study participant pt-9af --summary
1721
1773
  \`\`\`
1722
1774
 
1723
1775
  ## Worked example — redirect mid-run
1724
1776
 
1725
1777
  \`\`\`bash
1726
- # Tester wandered into the wrong flow. Cancel, then redirect:
1727
- ish study cancel t-072
1728
- ish study extend t-072 \\
1778
+ # Participant wandered into the wrong flow. Cancel, then redirect:
1779
+ ish study cancel pt-072
1780
+ ish study extend pt-072 \\
1729
1781
  --instruction "Stop browsing the blog. Open the pricing page and try to upgrade to Pro." \\
1730
1782
  --add-steps 10 --wait
1731
1783
  \`\`\`
@@ -1737,8 +1789,8 @@ ish study extend t-072 \\
1737
1789
  - \`reference/credits\` — per-modality cost formulas. \`extend\` follows
1738
1790
  the interactive formula scaled to \`additional_steps\`.
1739
1791
  - \`reference/aliases\` — the \`t-…\` prefix and how aliases resolve.
1740
- - \`reference/json-mode\` — capture-mode (\`--get tester_alias\`) for
1741
- chaining the new tester into the next call.
1792
+ - \`reference/json-mode\` — capture-mode (\`--get participant_alias\`) for
1793
+ chaining the new participant into the next call.
1742
1794
  `;
1743
1795
  const REFERENCE_ALIASES = `# reference: aliases
1744
1796
 
@@ -1752,9 +1804,9 @@ time the CLI sees an entity.
1752
1804
  - \`w-\` workspace
1753
1805
  - \`s-\` study
1754
1806
  - \`i-\` iteration
1755
- - \`t-\` tester (instance of a profile in an iteration)
1756
- - \`tp-\` tester profile
1757
- - \`tps-\` tester-profile source
1807
+ - \`pt-\` participant (instance of a person in an iteration)
1808
+ - \`tp-\` person
1809
+ - \`tps-\` person source
1758
1810
  - \`a-\` ask
1759
1811
  - \`r-\` ask round
1760
1812
  - \`c-\` config (simulation config)
@@ -1766,8 +1818,8 @@ time the CLI sees an entity.
1766
1818
  \`\`\`
1767
1819
  ish iteration create --study s-b2c --url https://example.com
1768
1820
  ish iteration get i-d4e
1769
- ish study tester t-a17
1770
- ish profile generate --source tps-3a4 --count 4
1821
+ ish study participant pt-a17
1822
+ ish person generate --source ps-3a4 --count 4
1771
1823
  \`\`\`
1772
1824
 
1773
1825
  The full UUID is also always accepted. Add \`--verbose\` to JSON output
@@ -1776,7 +1828,7 @@ to see UUIDs alongside aliases.
1776
1828
  const REFERENCE_SCREENSHOTS = `# reference: screenshots and iteration media
1777
1829
 
1778
1830
  Interactive study runs produce per-frame screenshots server-side. They
1779
- let you (or an agent) see what testers actually saw alongside the
1831
+ let you (or an agent) see what participants actually saw alongside the
1780
1832
  sentiment summary.
1781
1833
 
1782
1834
  ## Screenshots — interactive studies only
@@ -1794,14 +1846,14 @@ ish study screenshots download <study-id> --all --out ./shots/
1794
1846
  \`\`\`
1795
1847
 
1796
1848
  The list is grouped by frame. Each frame represents a distinct viewport
1797
- testers landed on (e.g. the hero, the pricing block, a documentation
1849
+ participants landed on (e.g. the hero, the pricing block, a documentation
1798
1850
  page). Pulling every screenshot can be heavy — start with the listing,
1799
1851
  then download representative frames.
1800
1852
 
1801
1853
  ### MCP (agent-facing)
1802
1854
 
1803
1855
  \`ish-mcp\` exposes the same artifacts as MCP Resources so an agent can
1804
- look at them inline. \`study_get(view='summary' | 'per_tester')\` on an
1856
+ look at them inline. \`study_get(view='summary' | 'per_participant')\` on an
1805
1857
  interactive study now carries:
1806
1858
 
1807
1859
  - \`screenshots_resource: ish://study/<id>/screenshots\` — JSON index
@@ -1862,7 +1914,7 @@ post-process CLI output with \`jq\` or \`python\` for routine tasks:
1862
1914
  |-------------------------------------------|--------------------------------------------------|
1863
1915
  | Show the user a list of workspaces | bare command (TTY) or \`--human\` if redirecting |
1864
1916
  | Capture an alias for a follow-up command | \`--get alias\` |
1865
- | Inspect a specific nested field | \`--get tester_profile.name\` |
1917
+ | Inspect a specific nested field | \`--get person.name\` |
1866
1918
  | Compare 2+ fields, or pipe into jq | \`--json\` (or auto-on when piped) |
1867
1919
  | Force human output through \`tee\` | \`--human\` |
1868
1920
  | Force JSON on a TTY | \`--json\` |
@@ -1902,7 +1954,7 @@ ish ask results "$ASK" --human | tee /tmp/transcript.txt
1902
1954
  \`--get\`.
1903
1955
  - \`--get <field>\` — extract a single field from the JSON response
1904
1956
  and print only its bare value. Supports dotted
1905
- paths (\`tester_profile.name\`). On a paginated
1957
+ paths (\`person.name\`). On a paginated
1906
1958
  \`{items: [...]}\` response, the path
1907
1959
  auto-descends into \`items\` so \`--get alias\`
1908
1960
  on a list yields one value per line. Implies
@@ -1955,10 +2007,12 @@ The CLI guarantees these contracts so agents can chain safely:
1955
2007
  \`--fields\` set, you can identify the affected resource. Default
1956
2008
  write-path JSON is compact (\`{id, alias, name, updated_at,
1957
2009
  ...changed_fields}\`); pass \`--verbose\` for the full server payload.
1958
- - **\`profile generate\` trims \`simulation_config\` by default** (~9×
1959
- smaller than the raw response). Pass \`--include-simulation-config\`
1960
- if you need it.
1961
- - **\`<entity> get\` accepts multiple IDs.** \`profile get\`, \`study get\`,
2010
+ - **\`person generate\` returns \`{job: {id, status, person_ids},
2011
+ profiles: [...]}\`** in \`--json\` mode. Each profile is the
2012
+ lean \`person\` shape (pass \`--verbose\` for the full record,
2013
+ including \`simulation_config\`) with its evidence-grounded
2014
+ \`scenarios\` attached; pass \`--no-scenarios\` to omit them.
2015
+ - **\`<entity> get\` accepts multiple IDs.** \`person get\`, \`study get\`,
1962
2016
  \`iteration get\`, and \`ask get\` all take \`<ids...>\` — pass two or
1963
2017
  more aliases (space- or comma-separated) and the response is a
1964
2018
  \`{items:[...], total:N}\` envelope. Use this instead of piping
@@ -1969,7 +2023,7 @@ The CLI guarantees these contracts so agents can chain safely:
1969
2023
 
1970
2024
  \`\`\`json
1971
2025
  {
1972
- "testers_count": 3,
2026
+ "participants_count": 3,
1973
2027
  "responses_total": 9,
1974
2028
  "responses_complete": 9,
1975
2029
  "rounds": [
@@ -1979,13 +2033,13 @@ The CLI guarantees these contracts so agents can chain safely:
1979
2033
  \`\`\`
1980
2034
 
1981
2035
  \`responses_errored\` only appears when at least one response errored.
1982
- Use these instead of \`jq '.testers | length'\` /
2036
+ Use these instead of \`jq '.participants | length'\` /
1983
2037
  \`jq '.rounds[0].responses | length'\`.
1984
- - **\`study run --json\` exposes tester handles.** The top-level
1985
- \`tester_ids[]\` and \`tester_aliases[]\` arrays are the canonical
2038
+ - **\`study run --json\` exposes participant handles.** The top-level
2039
+ \`participant_ids[]\` and \`participant_aliases[]\` arrays are the canonical
1986
2040
  inputs to \`ish study poll/wait/cancel\`. The \`simulations[]\` array
1987
2041
  is collapsed to one batch entry per study (M13) with nested
1988
- \`tester_ids[]\`, \`tester_aliases[]\`, \`job_ids[]\`, and \`count\` —
2042
+ \`participant_ids[]\`, \`participant_aliases[]\`, \`job_ids[]\`, and \`count\` —
1989
2043
  an N-sample dispatch is a single row, not N near-duplicate rows.
1990
2044
  - **\`study\` JSON includes a \`url\` field.** \`study create\`,
1991
2045
  \`study generate\`, \`study get\`, \`study list\` (per item), and
@@ -1998,27 +2052,44 @@ The CLI guarantees these contracts so agents can chain safely:
1998
2052
  with the \`ISH_APP_URL\` env var for staging or self-hosted UIs.
1999
2053
  - **\`study results --json\` includes per-answer sentiment** (M10).
2000
2054
  Every \`interview_answers[].answers[]\` row carries \`sentiment\`
2001
- (the tester's session-level label from \`tester_summary.sentiment\`),
2002
- and every \`testers[]\` row carries \`sentiment\` + \`comment\`. No
2003
- \`study tester <id>\` round-trip required.
2055
+ (the participant's session-level label from \`participant_summary.sentiment\`),
2056
+ and every \`participants[]\` row carries \`sentiment\` + \`comment\`. No
2057
+ \`study participant <id>\` round-trip required.
2004
2058
  - **\`study results --summary\`** is a lean projection: counts +
2005
- sentiment histogram + per-tester {alias, status, sentiment, comment,
2059
+ sentiment histogram + per-participant {alias, status, sentiment, comment,
2006
2060
  error_message}. Drops \`interview_answers\` and per-interaction
2007
2061
  breakdowns. Cheapest "did this run land?" shape.
2008
- - **\`study results --transcript <tester_id>\`** is the chat-modality
2062
+ - **\`study get --json\` carries a flat top-level \`participants[]\`**
2063
+ (post backend-split). Each row carries \`iteration_id\` as a
2064
+ discriminator and the per-participant graph
2065
+ (\`person\`, \`interactions[]\`, \`participant_summary\`,
2066
+ \`interview_answers\`, \`conversation_id\`, …). The previous nesting
2067
+ under \`iterations[*].participants[*]\` is gone — read participants from
2068
+ the top level. The lite iteration list under \`iterations[]\` still
2069
+ carries each iteration's \`details\` and (for pair-mode chat) the
2070
+ conversation refs at \`iterations[*].conversations[]\`.
2071
+ - **\`study get --json\` carries assignment step completion** when an
2072
+ assignment has a checklist (see \`concepts/assignment\`). Each
2073
+ \`assignments[].step_completion[]\` row is
2074
+ \`{step_id, name, total, passed, rate, sample_failures}\`. \`step_id\`
2075
+ (a slug like \`add-to-cart\`), \`total\`, \`passed\`, and \`rate\`
2076
+ survive the lean default; \`sample_failures[].participant_id\` is a UUID and
2077
+ so is stripped unless you pass \`--verbose\`. \`rate\` is \`null\` until
2078
+ a run grades the steps.
2079
+ - **\`study results --transcript <participant_id>\`** is the chat-modality
2009
2080
  projection — **external_chatbot mode only in v1**. Returns
2010
- \`{tester_id, tester_alias, transcript: [...], unique_bot_replies,
2011
- tester_summary}\`. Each transcript entry is \`{role, text, turn_index,
2081
+ \`{participant_id, participant_alias, transcript: [...], unique_bot_replies,
2082
+ participant_summary}\`. Each transcript entry is \`{role, text, turn_index,
2012
2083
  ...}\` — bot turns add \`failure\` (set when the dispatch crashed);
2013
- tester turns add \`action_type\`, \`option_label\`, and \`sentiment\`.
2014
- \`text\` is null on tester turns whose action carries no text
2084
+ participant turns add \`action_type\`, \`option_label\`, and \`sentiment\`.
2085
+ \`text\` is null on participant turns whose action carries no text
2015
2086
  (\`select_option\`, \`ignore_offered\`); read intent from
2016
2087
  \`action_type\` + \`option_label\`. Same shape as the MCP
2017
2088
  \`get_chat_transcript\` tool. \`unique_bot_replies = 1\` on a
2018
2089
  multi-turn run is the M2 loop signature.
2019
2090
 
2020
- **For tester_pair conversations**, the bot/tester role pair doesn't
2021
- apply (both speakers are testers). Inspect pair transcripts via the
2091
+ **For participant_pair conversations**, the bot/participant role pair doesn't
2092
+ apply (both speakers are participants). Inspect pair transcripts via the
2022
2093
  iteration response instead:
2023
2094
 
2024
2095
  \`\`\`bash
@@ -2026,19 +2097,19 @@ The CLI guarantees these contracts so agents can chain safely:
2026
2097
  # → [{ id, pair_index, started_at, ended_at, end_reason, summary, ... }]
2027
2098
  \`\`\`
2028
2099
 
2029
- Per-side tester summaries still land on each tester row
2030
- (\`ish study tester <id> --json\`); the conversation-level summary
2100
+ Per-side participant summaries still land on each participant row
2101
+ (\`ish study participant <id> --json\`); the conversation-level summary
2031
2102
  (\`end_reason\`, \`dominant_dynamic\`, \`who_steered\`) lands on
2032
2103
  \`iteration.conversations[]\`.
2033
- - **\`study tester --summary\`** drops the action timeline and
2034
- returns just \`{tester, interaction_count, sentiment, comment,
2104
+ - **\`study participant --summary\`** drops the action timeline and
2105
+ returns just \`{participant, interaction_count, sentiment, comment,
2035
2106
  error_message?, error_kind?}\`.
2036
2107
  - **\`study poll\` honors the active study.** Pass no \`--study\`
2037
2108
  flag and it falls back to the active study (set by
2038
2109
  \`ish study use\`), parity with \`study results\` /
2039
2110
  \`study wait\` / \`study run\`.
2040
- - **\`iteration get --json\` testers carry \`alias\` + \`name\`** (M12).
2041
- Same identifying triple as \`study results --json\`'s tester rows.
2111
+ - **\`iteration get --json\` participants carry \`alias\` + \`name\`** (M12).
2112
+ Same identifying triple as \`study results --json\`'s participant rows.
2042
2113
  - **\`ask results --json\` keeps \`variant_pick_id\` on every response**
2043
2114
  (C5-Bug4). It's the load-bearing field for "who picked what" — no
2044
2115
  \`--verbose\` required. Same logic on \`ask get --json\`.
@@ -2051,12 +2122,12 @@ The CLI guarantees these contracts so agents can chain safely:
2051
2122
  envelope carries \`progress: {study_id, iteration_id?,
2052
2123
  timeout_seconds, done, total, pending, rows[]}\` so the agent
2053
2124
  can resume by polling rather than re-dispatching. Same shape on
2054
- \`study wait\` (single-tester rows[] has length 1).
2125
+ \`study wait\` (single-participant rows[] has length 1).
2055
2126
  - **\`study run\` accepts \`--dispatch-timeout <s>\`** (default 120)
2056
- for the per-POST testers/batch + simulation/start budget. On
2127
+ for the per-POST participants/batch + simulation/start budget. On
2057
2128
  timeout (or any dispatch failure), the error envelope includes
2058
2129
  \`seeded_but_not_dispatched_ids[]\` + \`seeded_but_not_dispatched_aliases[]\`
2059
- listing the testers that exist server-side but didn't get
2130
+ listing the participants that exist server-side but didn't get
2060
2131
  dispatched. Resume by polling those instead of re-running
2061
2132
  \`study run\` (which would create another batch on top).
2062
2133
  - **\`ask run --new\` is non-idempotent and marked \`retryable: false\`**
@@ -2083,13 +2154,13 @@ The CLI guarantees these contracts so agents can chain safely:
2083
2154
  - **Study responses carry a derived \`runtime_status\` field**
2084
2155
  (\`draft | running | completed | completed_with_errors | cancelled\`).
2085
2156
  Prefer this over the raw \`status\` field — \`runtime_status\` is
2086
- computed from the iteration testers' actual run state and never
2157
+ computed from the iteration participants' actual run state and never
2087
2158
  reports \`failed\` while completed runs exist. Available on
2088
2159
  \`study get\`, \`study results\`, and the response from
2089
2160
  \`study generate\`. The CLI also surfaces a \`status_inferred\` field
2090
2161
  alongside the raw \`status\` when it detects a partial-failure
2091
2162
  inconsistency, plus a stderr warning ("Warning: study reports
2092
- status='failed' but N/M testers completed…").
2163
+ status='failed' but N/M participants completed…").
2093
2164
  - **\`study generate --json\` includes a \`modality_rationale\`** —
2094
2165
  one short sentence explaining why the LLM picked that modality. Use
2095
2166
  it to detect mis-classifications (e.g. brief was a static concept doc
@@ -2114,11 +2185,11 @@ The CLI guarantees these contracts so agents can chain safely:
2114
2185
  agent tool result budgets. Pass
2115
2186
  \`include_accessibility_profile=true\` to opt in. Mirrors the
2116
2187
  existing \`include_bio=false\` opt-in.
2117
- - **\`ask_testers\` parameter is \`dispatch_into_round\`, not
2118
- \`round\`.** Reads verbatim — "dispatch these new testers into round
2188
+ - **\`ask_participants\` parameter is \`dispatch_into_round\`, not
2189
+ \`round\`.** Reads verbatim — "dispatch these new participants into round
2119
2190
  N". The old name (\`round\`) read as "start from round N", which
2120
2191
  was wrong: the call never restarts prior rounds, it only appends
2121
- testers to the named round. Behavior unchanged across the rename.
2192
+ participants to the named round. Behavior unchanged across the rename.
2122
2193
  - **No more auto-empty iteration A.** \`study create\` and
2123
2194
  \`study generate\` no longer produce a placeholder iteration A. The
2124
2195
  first explicit \`ish iteration create\` becomes label A.
@@ -2126,13 +2197,13 @@ The CLI guarantees these contracts so agents can chain safely:
2126
2197
  (interactive) inline so a single call yields a runnable study.
2127
2198
  Running \`study run\` on a study with zero iterations exits 2 with
2128
2199
  a suggestion to run \`ish iteration create\` first.
2129
- - **Tester responses include \`error_message\`.** When a tester is
2200
+ - **Participant responses include \`error_message\`.** When a participant is
2130
2201
  \`status: failed\`, the JSON exposes \`error_message: "<reason>"\` so
2131
2202
  agents can act without drilling into logs. \`study results\` rolls
2132
- this up: top-level \`failed_count\`, plus per-tester \`error_message\`
2133
- in the \`testers[]\` array, and a "Failed testers" subsection in
2134
- human output. Empty when the tester succeeded.
2135
- - **\`profile list\` emits a stderr pagination hint** when
2203
+ this up: top-level \`failed_count\`, plus per-participant \`error_message\`
2204
+ in the \`participants[]\` array, and a "Failed participants" subsection in
2205
+ human output. Empty when the participant succeeded.
2206
+ - **\`person list\` emits a stderr pagination hint** when
2136
2207
  \`has_more=true\` and \`--quiet\` is not set. The hint goes to **stderr
2137
2208
  in every mode** including \`--json\` and piped stdout — it never
2138
2209
  pollutes machine-readable stdout but is visible to any agent that
@@ -2174,11 +2245,11 @@ The CLI guarantees these contracts so agents can chain safely:
2174
2245
  COMPLETED rows are left untouched; only ERRORED responses are reset
2175
2246
  to PENDING and re-run from scratch. Idempotent: zero-errored is a
2176
2247
  no-op. Add \`--wait\` to block until the retry settles.
2177
- - **\`ask results --json\` deduplicates tester profile snapshots.** When
2178
- \`tester_profile\` and \`tester_profile_snapshot\` share all
2248
+ - **\`ask results --json\` deduplicates person snapshots.** When
2249
+ \`person\` and \`person_snapshot\` share all
2179
2250
  overlapping fields (the common case — they only diverge if the
2180
2251
  profile was edited after dispatch), the snapshot is collapsed to
2181
- \`{snapshotted_at, snapshot_version, _matches_tester_profile: true}\`.
2252
+ \`{snapshotted_at, snapshot_version, _matches_person: true}\`.
2182
2253
  Use \`--verbose\` to keep both copies in full.
2183
2254
 
2184
2255
  ## Exit codes
@@ -2208,14 +2279,14 @@ a structured error object on **stdout** and a human message on
2208
2279
  "retryable": false,
2209
2280
  "errors": [
2210
2281
  {
2211
- "loc": ["body", "testers", 0, "tester_profile_id"],
2282
+ "loc": ["body", "participants", 0, "person_id"],
2212
2283
  "msg": "Input should be a valid UUID",
2213
2284
  "type": "uuid_parsing",
2214
- "input": "tp-bogus",
2285
+ "input": "p-bogus",
2215
2286
  "allowed_values": ["..."]
2216
2287
  }
2217
2288
  ],
2218
- "suggestions": ["Pass a tester profile alias (tp-...) or UUID."]
2289
+ "suggestions": ["Pass a person alias (p-...) or UUID."]
2219
2290
  }
2220
2291
  \`\`\`
2221
2292
 
@@ -2245,7 +2316,7 @@ ish ask results a-6ec --human | tee /tmp/results.txt
2245
2316
  WS=$(ish workspace list --get alias | head -1)
2246
2317
 
2247
2318
  # Inspect a nested field:
2248
- ish study tester t-a17 --get tester_profile.name
2319
+ ish study participant pt-a17 --get person.name
2249
2320
 
2250
2321
  # Chain (full JSON for jq when you need multiple fields):
2251
2322
  ish study get s-b2c --fields alias,name,status,iterations --json
@@ -2260,8 +2331,8 @@ a script, then display the final result back to the user:
2260
2331
  \`\`\`
2261
2332
  # Capture — bare values, no jq needed:
2262
2333
  ITER=$(ish iteration create --url https://example.com --get alias)
2263
- TESTERS=$(ish study run --iteration "$ITER" --sample 5 --country SE --get tester_aliases)
2264
- for t in $TESTERS; do
2334
+ PARTICIPANTS=$(ish study run --iteration "$ITER" --sample 5 --country SE --get participant_aliases)
2335
+ for t in $PARTICIPANTS; do
2265
2336
  ish study wait "$t" --timeout 600
2266
2337
  done
2267
2338
 
@@ -2275,7 +2346,7 @@ reshaping output.
2275
2346
  `;
2276
2347
  const GUIDE_FIRST_STUDY = `# guide: your first study, end to end
2277
2348
 
2278
- Goal: from zero to a finished interactive study with 3 testers and one
2349
+ Goal: from zero to a finished interactive study with 3 participants and one
2279
2350
  question, produced in a single workspace.
2280
2351
 
2281
2352
  ## 1. Authenticate
@@ -2291,10 +2362,10 @@ ish workspace create --name "Demo" --base-url https://example.com
2291
2362
  ish workspace use w-… # use the alias printed above
2292
2363
  \`\`\`
2293
2364
 
2294
- ## 3. Generate a small audience
2365
+ ## 3. Generate a small group of people
2295
2366
 
2296
2367
  \`\`\`
2297
- ish profile generate \\
2368
+ ish person generate \\
2298
2369
  --description "Tech-savvy millennials in the US who use mobile banking" \\
2299
2370
  --count 3
2300
2371
  \`\`\`
@@ -2433,31 +2504,31 @@ compute cost. Agents should:
2433
2504
 
2434
2505
  - For prospective cost preview: read \`credit_estimate\` from \`study run\`'s
2435
2506
  JSON envelope (top-level for solo/media runs; under \`pair_preview\` for
2436
- tester-pair chat).
2507
+ participant-pair chat).
2437
2508
  - For hard budget checks: catch the backend's \`insufficient_credits\`
2438
2509
  rejection (HTTP 402; envelope shape below) and react to
2439
2510
  \`required\` / \`available\`.
2440
2511
 
2441
2512
  | Surface | Per-principal cost | Total formula | Example |
2442
2513
  |---------------------|---------------------------------|--------------------------------------------------|--------------------------------------|
2443
- | Interactive (URL) | \`max(1, round(steps/10))\` | \`testers × per-tester\` | 10 testers × 30 steps → 30 credits |
2444
- | Text/image/video/audio/document | same | same | 5 testers × 20 steps → 10 credits |
2445
- | Chat (external chatbot, solo) | \`max(1, round(turns/10))\` | \`testers × per-tester\` | 5 testers × 12 turns → 10 credits |
2446
- | Chat (tester pair) | \`max(1, round(turns/10))\` × 2 | \`conv × per-side × 2\` | 3 conv × 14 turns → 6 credits |
2447
- | Ask round | 1 / successful response | \`successful_testers\` | 50 responses → 50 credits |
2514
+ | Interactive (URL) | \`max(1, round(steps/10))\` | \`participants × per-participant\` | 10 participants × 30 steps → 30 credits |
2515
+ | Text/image/video/audio/document | same | same | 5 participants × 20 steps → 10 credits |
2516
+ | Chat (external chatbot, solo) | \`max(1, round(turns/10))\` | \`participants × per-participant\` | 5 participants × 12 turns → 10 credits |
2517
+ | Chat (participant pair) | \`max(1, round(turns/10))\` × 2 | \`conv × per-side × 2\` | 3 conv × 14 turns → 6 credits |
2518
+ | Ask round | 1 / successful response | \`successful_participants\` | 50 responses → 50 credits |
2448
2519
  | Study insights | first free, then **10 flat** | n/a | 2nd analysis → 10 credits |
2449
2520
 
2450
2521
  All numbers are **upper bounds**. Early termination, refusals, or
2451
- backend audience trimming can reduce actual charge.
2522
+ backend trimming can reduce actual charge.
2452
2523
 
2453
2524
  ## Capping interactive/media spend (\`--max-interactions\`)
2454
2525
 
2455
2526
  \`ish study run\` always sends \`max_interactions\` to the backend for
2456
2527
  interactive and media runs. Precedence: \`--max-interactions <n>\` flag
2457
2528
  > the iteration's stored \`details.max_interactions\` > **CLI default
2458
- of 20**. The default exists to prevent runaway spend when a tester
2529
+ of 20**. The default exists to prevent runaway spend when a participant
2459
2530
  gets stuck on a broken or non-responsive surface — without a cap, one
2460
- stuck tester can rack up 100+ steps before the SDK gives up. Pass
2531
+ stuck participant can rack up 100+ steps before the SDK gives up. Pass
2461
2532
  \`--max-interactions\` to override (e.g. \`--max-interactions 50\` for
2462
2533
  deeper exploration, \`--max-interactions 5\` for a cheap smoke test).
2463
2534
  The confirmation block shows the resolved value and where it came
@@ -2502,14 +2573,14 @@ Solo media/interactive/chat — top-level \`credit_estimate\`:
2502
2573
  "iteration_id": "…",
2503
2574
  "credit_estimate": {
2504
2575
  "upper_bound": 30,
2505
- "formula": "media_per_tester",
2506
- "breakdown": "10 tester(s) × max(1, round(30 steps / 10)) = 10 × 3 = 30",
2576
+ "formula": "media_per_participant",
2577
+ "breakdown": "10 participant(s) × max(1, round(30 steps / 10)) = 10 × 3 = 30",
2507
2578
  "unit": "credits"
2508
2579
  }
2509
2580
  }
2510
2581
  \`\`\`
2511
2582
 
2512
- The \`formula\` key is stable: agents can branch on it (\`media_per_tester\`,
2583
+ The \`formula\` key is stable: agents can branch on it (\`media_per_participant\`,
2513
2584
  \`chat_solo\`, \`chat_pair\`, \`ask_per_response\`).
2514
2585
 
2515
2586
  ## Tier allotments
@@ -2601,11 +2672,11 @@ request time, for any client, is the backend's \`TIER_LIMITS\` dict in
2601
2672
  | \`maxProducts\` | 1 | 1 | ∞ | ∞ | ∞ |
2602
2673
  | \`maxStudiesPerProduct\` | 3 | ∞ | ∞ | ∞ | ∞ |
2603
2674
  | \`maxIterationsPerStudy\` | 2 | ∞ | ∞ | ∞ | ∞ |
2604
- | \`maxCustomTesterProfiles\` | 3 | 10 | 10 | ∞ | ∞ |
2675
+ | \`maxCustomPersons\` | 3 | 10 | 10 | ∞ | ∞ |
2605
2676
 
2606
2677
  Commands that may hit a limit: \`ish workspace create\`,
2607
2678
  \`ish study create\`, \`ish study generate\`, \`ish iteration create\`,
2608
- \`ish profile create\`, \`ish profile generate\`.
2679
+ \`ish person create\`, \`ish person generate\`.
2609
2680
 
2610
2681
  ## What you see when a limit is hit
2611
2682
 
@@ -2645,7 +2716,7 @@ upgrade or delete an existing resource to free up headroom.
2645
2716
  - Use \`limit\`, \`current\`, \`max\`, \`tier\` to construct your own
2646
2717
  recovery message. The \`limit\` value matches the table above and is
2647
2718
  stable.
2648
- - The \`generate\` endpoints (\`study generate\`, \`profile generate\`)
2719
+ - The \`generate\` endpoints (\`study generate\`, \`person generate\`)
2649
2720
  refuse the entire batch when the post-generation count would exceed
2650
2721
  the cap, rather than partially fulfilling — re-issue with a smaller
2651
2722
  \`--count\` after upgrading or pruning.
@@ -2658,16 +2729,16 @@ upgrade or delete an existing resource to free up headroom.
2658
2729
  - \`concepts/workspace\` — \`maxProducts\` is per-account.
2659
2730
  - \`concepts/study\` — \`maxStudiesPerProduct\` gates study creation.
2660
2731
  - \`concepts/iteration\` — \`maxIterationsPerStudy\` gates iteration creation.
2661
- - \`concepts/profile\` — \`maxCustomTesterProfiles\` gates profile creation.
2732
+ - \`concepts/person\` — \`maxCustomPersons\` gates person creation.
2662
2733
  - \`reference/json-mode\` — full error envelope shape and exit codes.
2663
2734
  `;
2664
2735
  const GUIDE_CHAT = `# guide: chat-modality studies
2665
2736
 
2666
2737
  Chat-modality studies cover two distinct shapes:
2667
2738
 
2668
- - **external_chatbot** — testers probe a customer chatbot endpoint
2739
+ - **external_chatbot** — participants probe a customer chatbot endpoint
2669
2740
  (sections 1-3 below: configure → smoke test → run).
2670
- - **tester_pair** — two AI personas converse with each other for
2741
+ - **participant_pair** — two AI personas converse with each other for
2671
2742
  rehearsal scenarios. Pitch rehearsals, difficult-conversation
2672
2743
  prep, founder-vs-investor archetypes. See section 7a/7b and the
2673
2744
  TL;DR below.
@@ -2680,17 +2751,17 @@ scenarios — no extra files needed:
2680
2751
 
2681
2752
  \`\`\`bash
2682
2753
  # Capture aliases for the rep (1) and CTOs (3) via subshell:
2683
- REP=$(ish profile generate \\
2754
+ REP=$(ish person generate \\
2684
2755
  --description "Senior B2B SaaS account executive; concise, technical" \\
2685
2756
  --count 1 --json | jq -r '.items[0].alias')
2686
- CTOS=$(ish profile generate \\
2757
+ CTOS=$(ish person generate \\
2687
2758
  --description "Skeptical CTO at Series B SaaS; distrusts AI vendors" \\
2688
2759
  --count 3 --json | jq -r '[.items[].alias] | join(",")')
2689
2760
 
2690
2761
  # One-shot study + iteration A (1×N broadcast does the rest):
2691
- ish study create --modality chat --chat-mode tester_pair \\
2762
+ ish study create --modality chat --chat-mode participant_pair \\
2692
2763
  --name "Pitch rehearsal" \\
2693
- --audience-a "$REP" --audience-b "$CTOS" \\
2764
+ --group-a "$REP" --group-b "$CTOS" \\
2694
2765
  --scenario-a "You are pitching <your product>. Be concise, push back on vague objections. Goal: land a pilot or a clear next step." \\
2695
2766
  --scenario-b "You are a skeptical CTO. Probe for technical depth, distrust marketing-speak, refuse to commit without evidence. Goal: leave with either a concrete proof point or a graceful 'no'." \\
2696
2767
  --assignment "Pitch:Land a pilot" --max-turns 14
@@ -2704,7 +2775,7 @@ ish iteration get <iter-id> --json \\
2704
2775
  \`\`\`
2705
2776
 
2706
2777
  Section 7b below has the longer version with scenario-writing
2707
- guidance, criteria-driven audiences, and the broadcast rule.
2778
+ guidance, criteria-driven groups, and the broadcast rule.
2708
2779
 
2709
2780
  ---
2710
2781
 
@@ -2804,7 +2875,7 @@ The renderer expands these tokens at request time:
2804
2875
  - \`{{turn.role}}\` / \`{{turn.text}}\`: per-turn expansion. Place
2805
2876
  one element with these tokens inside an array literal; the
2806
2877
  renderer expands it to one entry per past turn.
2807
- - \`{{tester.name}}\` / \`{{tester.locale}}\`: persona attributes.
2878
+ - \`{{participant.name}}\` / \`{{participant.locale}}\`: persona attributes.
2808
2879
  - \`{{conversation_id}}\`: bot-supplied session id (stateful mode).
2809
2880
  - \`{{secret:KEY}}\`: workspace secret (see below).
2810
2881
 
@@ -2999,7 +3070,7 @@ cat ./bot-config.json | ish study create \\
2999
3070
  --name "Sign-up Q1" --assignment "Sign up:Try to sign up"
3000
3071
  \`\`\`
3001
3072
 
3002
- Optional \`--max-turns <n>\` (default 12) caps the chat per tester.
3073
+ Optional \`--max-turns <n>\` (default 12) caps the chat per participant.
3003
3074
 
3004
3075
  Audience size is set at run time for **external_chatbot** chat
3005
3076
  studies. Use \`--sample <N>\` to pick N random simulatable profiles,
@@ -3010,14 +3081,14 @@ ish study run stu-xyz --sample 5 --wait
3010
3081
  \`\`\`
3011
3082
 
3012
3083
  > **Pair-mode is different.** \`--sample\` / \`--profile\` / demographic
3013
- > filters on \`study run\` are **refused** for tester_pair iterations
3014
- > — pair audiences live on the iteration itself. Set them at
3015
- > iteration-create time via \`--audience-a/-b\` (with 1×N broadcast)
3016
- > or \`--role-criteria-a/-b\`. See the tester_pair section below.
3084
+ > filters on \`study run\` are **refused** for participant_pair iterations
3085
+ > — pair groups live on the iteration itself. Set them at
3086
+ > iteration-create time via \`--group-a/-b\` (with 1×N broadcast)
3087
+ > or \`--role-criteria-a/-b\`. See the participant_pair section below.
3017
3088
 
3018
3089
  Pull raw interactions:
3019
3090
  \`\`\`
3020
- ish study results stu-xyz --json | jq '.interactions'
3091
+ ish study results stu-xyz --json | jq '.participants[].interactions'
3021
3092
  \`\`\`
3022
3093
 
3023
3094
  Note: chat is currently excluded from the LLM-analysis route; the
@@ -3036,23 +3107,23 @@ ish iteration create --study stu-xyz --endpoint-config ./bot.json
3036
3107
 
3037
3108
  Same flag set as \`study create\`'s chat shortcut.
3038
3109
 
3039
- ## tester_pair: rehearse a conversation between two AI personas
3110
+ ## participant_pair: rehearse a conversation between two AI personas
3040
3111
 
3041
- \`Modality.CHAT\` also supports a **tester_pair** mode where two AI
3042
- tester profiles converse with each other — useful for rehearsing a
3112
+ \`Modality.CHAT\` also supports a **participant_pair** mode where two AI
3113
+ people converse with each other — useful for rehearsing a
3043
3114
  sales pitch, a difficult conversation, a fundraising chat, or any
3044
3115
  two-role scenario. Each side has its own scenario + goal text; the
3045
3116
  other side does NOT see it (the asymmetry contract). Audiences are
3046
- 1:1 paired by index (audience_a[i] talks to audience_b[i]).
3117
+ 1:1 paired by index (group_a[i] talks to group_b[i]).
3047
3118
 
3048
3119
  One-shot study + iteration:
3049
3120
 
3050
3121
  \`\`\`
3051
3122
  ish study create \\
3052
- --modality chat --chat-mode tester_pair \\
3123
+ --modality chat --chat-mode participant_pair \\
3053
3124
  --name "Pitch rehearsal" \\
3054
- --audience-a tp-sales-1,tp-sales-2 \\
3055
- --audience-b tp-cto-skeptic-1,tp-cto-skeptic-2 \\
3125
+ --group-a p-sales-1,p-sales-2 \\
3126
+ --group-b p-cto-skeptic-1,p-cto-skeptic-2 \\
3056
3127
  --scenario-a @./sales_rep.md \\
3057
3128
  --scenario-b @./skeptical_cto.md \\
3058
3129
  --assignment "Pitch:Try to win the meeting"
@@ -3061,8 +3132,8 @@ ish study create \\
3061
3132
  Or add a pair iteration to an existing chat study:
3062
3133
 
3063
3134
  \`\`\`
3064
- ish iteration create --study stu-xyz --chat-mode tester_pair \\
3065
- --audience-a tp-a1,tp-a2 --audience-b tp-b1,tp-b2 \\
3135
+ ish iteration create --study stu-xyz --chat-mode participant_pair \\
3136
+ --group-a p-a1,p-a2 --group-b p-b1,p-b2 \\
3066
3137
  --scenario-a "..." --scenario-b "..." \\
3067
3138
  --max-turns 14
3068
3139
  \`\`\`
@@ -3070,23 +3141,23 @@ ish iteration create --study stu-xyz --chat-mode tester_pair \\
3070
3141
  ### Rehearsing against N variations of one side (1×N)
3071
3142
 
3072
3143
  The most common rehearsal shape: fix one side (your role) and vary
3073
- the other (the audience you're rehearsing against). E.g. "pitch this
3144
+ the other (the people you're rehearsing against). E.g. "pitch this
3074
3145
  once and see how it lands against 3 different skeptical CTOs."
3075
3146
 
3076
3147
  Step 1 — produce N distinct profiles for the varying side:
3077
3148
 
3078
3149
  \`\`\`bash
3079
3150
  # Generate 3 skeptical-CTO profiles (or any archetype):
3080
- ish profile generate \\
3151
+ ish person generate \\
3081
3152
  --description "Skeptical CTO at a Series B SaaS startup; distrusts AI vendors" \\
3082
3153
  --count 3 --json | jq -r '.items[].alias'
3083
- # → tp-cto1, tp-cto2, tp-cto3
3154
+ # → p-cto1, p-cto2, p-cto3
3084
3155
  \`\`\`
3085
3156
 
3086
3157
  If you already have profiles you want to reuse, list them:
3087
3158
 
3088
3159
  \`\`\`bash
3089
- ish profile list --search "cto" --json | jq -r '.items[].alias'
3160
+ ish person list --search "cto" --json | jq -r '.items[].alias'
3090
3161
  \`\`\`
3091
3162
 
3092
3163
  Step 2 — author the two scenarios as separate files (\`sales_rep.md\`
@@ -3101,21 +3172,21 @@ template.
3101
3172
  Step 3 — create the iteration with **one profile** on the fixed
3102
3173
  side and **N profiles** on the varying side. The CLI auto-broadcasts
3103
3174
  the singleton to match length N (and prints a stderr notice like
3104
- \`Broadcasting --audience-a (1 profile) to length 3 to match --audience-b\`
3175
+ \`Broadcasting --group-a (1 profile) to length 3 to match --group-b\`
3105
3176
  when it does, so you can see it happen):
3106
3177
 
3107
3178
  \`\`\`bash
3108
3179
  ish study create \\
3109
- --modality chat --chat-mode tester_pair \\
3180
+ --modality chat --chat-mode participant_pair \\
3110
3181
  --name "Pitch rehearsal — 3 CTO variants" \\
3111
- --audience-a tp-rep \\
3112
- --audience-b tp-cto1,tp-cto2,tp-cto3 \\
3182
+ --group-a p-rep \\
3183
+ --group-b p-cto1,p-cto2,p-cto3 \\
3113
3184
  --scenario-a @./sales_rep.md \\
3114
3185
  --scenario-b @./skeptical_cto.md \\
3115
3186
  --assignment "Pitch:Land a pilot or a clear next step"
3116
3187
 
3117
- # Result: 3 conversations, all using tp-rep on side A, one each
3118
- # of tp-cto1/2/3 on side B. Same scenario for the CTOs (they share
3188
+ # Result: 3 conversations, all using p-rep on side A, one each
3189
+ # of p-cto1/2/3 on side B. Same scenario for the CTOs (they share
3119
3190
  # the role description) but different underlying personas, so the
3120
3191
  # conversations diverge in tone and pressure points.
3121
3192
  \`\`\`
@@ -3136,22 +3207,22 @@ ish iteration get <iter-id> --json \\
3136
3207
  **When to use criteria instead**: if you don't care about specific
3137
3208
  profile IDs and just want "any 3 CTOs the backend can find", pass
3138
3209
  \`--role-criteria-b '{"occupation":["cto"]}'\` (alone or with a single
3139
- \`--audience-a tp-rep\`). The backend resolves the matching pool at
3210
+ \`--group-a p-rep\`). The backend resolves the matching pool at
3140
3211
  iteration-create time. Caveat: the resolved pool may collapse onto
3141
3212
  similar personas — for guaranteed distinctness, generate explicit
3142
3213
  profiles first.
3143
3214
 
3144
- ### Criteria-driven audience (persona-first filtering)
3215
+ ### Criteria-driven group (persona-first filtering)
3145
3216
 
3146
3217
  When you don't want to hand-pick UUIDs, pass a **role-criteria
3147
3218
  filter** per side. The backend resolves it into an eligible pool of
3148
- tester profiles and pairs them 1:1. The persona itself is never
3219
+ people and pairs them 1:1. The persona itself is never
3149
3220
  altered — criteria filter the pool upstream so the persona is
3150
3221
  already plausible for the role:
3151
3222
 
3152
3223
  \`\`\`
3153
3224
  ish study create \\
3154
- --modality chat --chat-mode tester_pair \\
3225
+ --modality chat --chat-mode participant_pair \\
3155
3226
  --name "Pitch rehearsal" \\
3156
3227
  --role-criteria-a '{"occupation":["sales","account executive"],"min_age":28}' \\
3157
3228
  --role-criteria-b '{"occupation":["cto","vp engineering"],"country":["US","SE"]}' \\
@@ -3172,23 +3243,23 @@ MECE notes for the list filters:
3172
3243
  - \`household_in\`: \`couple_with_kids\` covers couples raising children;
3173
3244
  \`couple_no_kids\` is strictly child-free. \`single\` means lives alone
3174
3245
  (no partner, no roommates, no parents, no children in the household).
3175
- - \`employment_status_in\`: pick the tester's primary daytime activity.
3246
+ - \`employment_status_in\`: pick the participant's primary daytime activity.
3176
3247
  A student who works 15 hrs/week is \`student\`; a retiree who freelances
3177
3248
  is \`retired\`.
3178
3249
 
3179
3250
  If the resolved pool is too small, \`ish study run\` exits 2 with the
3180
3251
  backend's error message intact — no silent fallback. Broaden the
3181
3252
  criteria or generate more matching profiles via
3182
- \`ish profile generate --description "..."\`.
3253
+ \`ish person generate --description "..."\`.
3183
3254
 
3184
3255
  Dispatch is per-Conversation (one task per pair index). Run-time
3185
- audience overrides (\`--profile\`, \`--sample\`, \`--all\`, demographic
3186
- filters) are refused on pair iterations — the iteration's audiences
3256
+ people overrides (\`--profile\`, \`--sample\`, \`--all\`, demographic
3257
+ filters) are refused on pair iterations — the iteration's groups
3187
3258
  are authoritative. To change them, update the iteration:
3188
3259
 
3189
3260
  \`\`\`
3190
3261
  ish study run --study stu-xyz --iteration i-pair -y
3191
- ish iteration update i-pair --details-json '{...}' # change audiences
3262
+ ish iteration update i-pair --details-json '{...}' # change groups
3192
3263
  \`\`\`
3193
3264
 
3194
3265
  Inspect:
@@ -3198,8 +3269,8 @@ ish iteration get i-pair --json | jq '.details.mode_details.mode, .conversations
3198
3269
  \`\`\`
3199
3270
 
3200
3271
  Per-Conversation summaries (\`end_reason\`, \`dominant_dynamic\`,
3201
- \`who_steered\`) land on \`iteration.conversations[]\`. Per-tester
3202
- summaries land on \`tester.summary\` as before.
3272
+ \`who_steered\`) land on \`iteration.conversations[]\`. Per-participant
3273
+ summaries land on \`participant.summary\` as before.
3203
3274
 
3204
3275
  ## Active-endpoint convention
3205
3276
 
@@ -3234,7 +3305,7 @@ Mirrors \`workspace use\` / \`study use\` / \`ask use\`.
3234
3305
  - \`concepts/iteration\` — chat iteration shape
3235
3306
  (\`details.mode_details\` discriminator, \`mode_details.endpoint\` /
3236
3307
  \`mode_details.chatbot_endpoint_id\` for external_chatbot,
3237
- \`mode_details.audience_a/_b\` + \`scenario_a/_b\` for tester_pair,
3308
+ \`mode_details.group_a/_b\` + \`scenario_a/_b\` for participant_pair,
3238
3309
  \`details.max_turns\`).
3239
3310
  - \`concepts/study\` — modality + assignments + iteration nesting.
3240
3311
  - \`reference/json-mode\` — JSON output, error envelope, exit codes.
@@ -3296,13 +3367,13 @@ ish workspace list --json
3296
3367
  "id": "...", "alias": "w-6ec", "name": "Onboarding revamp",
3297
3368
  "base_url": "https://example.com",
3298
3369
  "last_activity_at": "2026-05-10T14:22:00Z",
3299
- "child_counts": { "studies": 2, "asks": 1, "tester_profiles": 4 },
3370
+ "child_counts": { "studies": 2, "asks": 1, "persons": 4 },
3300
3371
  "has_headroom": true
3301
3372
  },
3302
3373
  {
3303
3374
  "id": "...", "alias": "w-d02", "name": "Demo",
3304
3375
  "last_activity_at": "2025-11-02T09:11:00Z",
3305
- "child_counts": { "studies": 3, "asks": 0, "tester_profiles": 0 },
3376
+ "child_counts": { "studies": 3, "asks": 0, "persons": 0 },
3306
3377
  "has_headroom": false
3307
3378
  }
3308
3379
  ],
@@ -3315,14 +3386,14 @@ Read three fields per row:
3315
3386
  - **\`last_activity_at\`** — most recent run, iteration, ask, or write
3316
3387
  on this workspace. The most recently active one is usually the
3317
3388
  workspace the user is mentally already in.
3318
- - **\`child_counts\`** — \`{ studies, asks, tester_profiles }\`. Zero
3389
+ - **\`child_counts\`** — \`{ studies, asks, persons }\`. Zero
3319
3390
  across the board = quiet/empty, ideal reuse target without
3320
3391
  cluttering anyone's view. A workspace with content the user owns is
3321
3392
  also fine to reuse if there's still headroom.
3322
3393
  - **\`has_headroom\`** — \`true\` if the workspace still has room under
3323
3394
  \`maxStudiesPerProduct\`, \`maxIterationsPerStudy\`, and
3324
- \`maxCustomTesterProfiles\` for the caller's tier. If \`false\`, the
3325
- next \`study create\` / \`profile generate\` against this workspace
3395
+ \`maxCustomPersons\` for the caller's tier. If \`false\`, the
3396
+ next \`study create\` / \`person generate\` against this workspace
3326
3397
  will be \`usage_limit_reached\`. Filter these out unless the user
3327
3398
  explicitly wants to free space by deleting state.
3328
3399
 
@@ -3381,11 +3452,11 @@ ish workspace list --json --fields alias,name,last_activity_at,child_counts,has_
3381
3452
  # [
3382
3453
  # {"alias":"w-6ec","name":"Onboarding revamp",
3383
3454
  # "last_activity_at":"2026-05-10T14:22:00Z",
3384
- # "child_counts":{"studies":2,"asks":1,"tester_profiles":4},
3455
+ # "child_counts":{"studies":2,"asks":1,"persons":4},
3385
3456
  # "has_headroom":true},
3386
3457
  # {"alias":"w-d02","name":"Demo",
3387
3458
  # "last_activity_at":"2025-11-02T09:11:00Z",
3388
- # "child_counts":{"studies":3,"asks":0,"tester_profiles":0},
3459
+ # "child_counts":{"studies":3,"asks":0,"persons":0},
3389
3460
  # "has_headroom":false},
3390
3461
  # ...
3391
3462
  # ]
@@ -3394,7 +3465,7 @@ ish workspace list --json --fields alias,name,last_activity_at,child_counts,has_
3394
3465
  ish workspace use w-6ec
3395
3466
 
3396
3467
  # 3. Carry on as if the workspace_create had succeeded.
3397
- ish profile generate --description "..." --count 3
3468
+ ish person generate --description "..." --count 3
3398
3469
  ish study create --modality interactive --name "..." \\
3399
3470
  --url https://example.com \\
3400
3471
  --assignment "..." --question "..."
@@ -3437,24 +3508,24 @@ without a second round-trip.
3437
3508
  - \`reference/json-mode\` — error envelope shape and exit code mapping
3438
3509
  (\`usage_limit_reached\` is HTTP 403, exit 1, non-retryable).
3439
3510
  `;
3440
- const GUIDE_BUILD_SPECIFIC_TESTER = `# guide: build a specific simulated tester from notes
3511
+ const GUIDE_BUILD_SPECIFIC_PERSON = `# guide: build a specific simulated person from notes
3441
3512
 
3442
- \`profile generate\` is the right tool for *audiences* (many profiles
3513
+ \`person generate\` is the right tool for *groups* (many profiles
3443
3514
  from a description or interview sources). When you want **one specific
3444
- tester** — modelling a real prospect, rebuilding a persona from a
3515
+ person** — modelling a real prospect, rebuilding a persona from a
3445
3516
  single interview, or simulating a named stakeholder for a pitch
3446
3517
  rehearsal — use the iterative probe loop:
3447
3518
 
3448
- 1. \`ish profile suggest-scenarios\` — describe what you already
3519
+ 1. \`ish person suggest-scenarios\` — describe what you already
3449
3520
  know; the LLM returns 1–10 scenario probes designed to expose what
3450
3521
  you don't.
3451
3522
  2. Answer the probes locally (in chat, with the user, or from
3452
3523
  transcripts).
3453
- 3. \`ish profile create --file ...\` — save the profile shell.
3454
- 4. \`ish profile evidence add <id>\` — persist the answered probes
3524
+ 3. \`ish person create --file ...\` — save the profile shell.
3525
+ 4. \`ish person evidence add <id>\` — persist the answered probes
3455
3526
  as structured evidence on the profile so they survive into runtime
3456
3527
  persona injection.
3457
- 5. \`ish profile evidence list <id>\` — read back what's saved,
3528
+ 5. \`ish person evidence list <id>\` — read back what's saved,
3458
3529
  newest first. Useful for verifying a session or branching on prior
3459
3530
  state before the next probe round.
3460
3531
 
@@ -3467,7 +3538,7 @@ to surface a different facet of the persona:
3467
3538
  X; which option fits?" Multiple-choice, lets the persona pick
3468
3539
  behavior.
3469
3540
  - \`voice\` — \`{situation, options[2..4]}\`: same shape as situation
3470
- but framed around tone/phrasing the tester would actually use.
3541
+ but framed around tone/phrasing the participant would actually use.
3471
3542
  - \`binary\` — \`{description, option_a, option_b}\`: forced choice
3472
3543
  between two competing values or trade-offs.
3473
3544
  - \`micro-story\` — \`{prompt}\`: open-ended; the persona narrates a
@@ -3490,7 +3561,7 @@ probe, copy the scenario's \`type\` straight into the trace's
3490
3561
 
3491
3562
  \`\`\`
3492
3563
  # 1. Suggest 5 probes from a context blob
3493
- ish profile suggest-scenarios \\
3564
+ ish person suggest-scenarios \\
3494
3565
  --context "Staff platform engineer at a Stripe-using fintech. \\
3495
3566
  Owns on-call for the payments edge. Burned by a Black Friday \\
3496
3567
  outage last year." \\
@@ -3508,16 +3579,16 @@ ish profile suggest-scenarios \\
3508
3579
  # ]
3509
3580
 
3510
3581
  # 3. Create the profile shell
3511
- ish profile create --file ./persona.json
3512
- # → tp-d4e
3582
+ ish person create --file ./persona.json
3583
+ # → p-d4e
3513
3584
 
3514
3585
  # 4. Persist the answered probes as evidence
3515
- ish profile evidence add tp-d4e --traces-file ./answers.json
3586
+ ish person evidence add p-d4e --traces-file ./answers.json
3516
3587
  # → {items: [{id, text, source, scenario_prompt, created_at}, ...], total: N}
3517
3588
 
3518
3589
  # 5. Read back what got saved (also useful before the next probe round)
3519
- ish profile evidence list tp-d4e
3520
- ish profile evidence list tp-d4e --get source # one source per line
3590
+ ish person evidence list p-d4e
3591
+ ish person evidence list p-d4e --get source # one source per line
3521
3592
  \`\`\`
3522
3593
 
3523
3594
  ## Iterating the probe loop
@@ -3526,7 +3597,7 @@ To go deeper on a follow-up pass, feed the prior round back in so the
3526
3597
  LLM doesn't paraphrase what you already asked:
3527
3598
 
3528
3599
  \`\`\`
3529
- ish profile suggest-scenarios \\
3600
+ ish person suggest-scenarios \\
3530
3601
  --context-file ./notes.md \\
3531
3602
  --count 3 \\
3532
3603
  --already-surfaced '["PagerDuty fires at 02:00 on payments edge."]' \\
@@ -3542,17 +3613,123 @@ cap at 40 entries.
3542
3613
 
3543
3614
  | Need | Command |
3544
3615
  |---|---|
3545
- | Many profiles from a description or interview | \`ish profile generate\` |
3546
- | One specific persona, iterative probe loop | \`ish profile suggest-scenarios\` + \`evidence add\`/\`list\` |
3547
- | Exact profile from a JSON spec, no LLM | \`ish profile create --file\` |
3616
+ | Many profiles from a description or interview | \`ish person generate\` |
3617
+ | One specific persona, iterative probe loop | \`ish person suggest-scenarios\` + \`evidence add\`/\`list\` |
3618
+ | Exact profile from a JSON spec, no LLM | \`ish person create --file\` |
3548
3619
 
3549
3620
  ## Related
3550
3621
 
3551
- - \`concepts/profile\` — what a tester profile is; structured fields.
3622
+ - \`concepts/person\` — what a person is; structured fields.
3552
3623
  - \`concepts/source\` — interview transcripts / audio / PDF inputs
3553
- for the audience-generation flow.
3624
+ for the people-generation flow.
3554
3625
  - \`reference/aliases\` — \`tp-…\` is the profile alias prefix.
3555
3626
  `;
3627
+ const GUIDE_MCP_ADD = `# guide: wire ish into your AI clients (\`ish mcp add\`)
3628
+
3629
+ The hosted ish MCP server lets agents inside Cursor, VS Code, Claude
3630
+ Code, Claude Desktop, and Windsurf call ish operations (study
3631
+ run, ask run, person generate, …) directly. \`ish mcp add\` writes the
3632
+ per-client config block so each client knows where to find the
3633
+ server. OAuth is handled by the server itself on first connect — no
3634
+ token is written to your client config file.
3635
+
3636
+ ## When to run this
3637
+
3638
+ - You've installed the ish CLI and want your editor/desktop agent to
3639
+ drive ish without you copy-pasting JSON into a config file.
3640
+ - You're switching machines and want to re-wire every client in one
3641
+ command.
3642
+ - You changed the server URL (\`ISH_MCP_URL\`) and want every client
3643
+ re-pointed at the new endpoint.
3644
+
3645
+ ## Verbs
3646
+
3647
+ \`\`\`bash
3648
+ ish mcp list # read-only: which clients are detected + status
3649
+ ish mcp add # dry-run plan + next-step hint (default)
3650
+ ish mcp add --all --yes # wire every detected client
3651
+ ish mcp add --client cursor --yes # wire one specific client
3652
+ ish mcp remove --client cursor --yes
3653
+ \`\`\`
3654
+
3655
+ ## Supported clients
3656
+
3657
+ | Client | Config path | Server key |
3658
+ |-----------------|-----------------------------------------------------------------------------------|--------------------|
3659
+ | cursor | \`~/.cursor/mcp.json\` | \`mcpServers\` |
3660
+ | vscode | \`~/Library/Application Support/Code/User/mcp.json\` (macOS), \`~/.config/Code/User/mcp.json\` (Linux), \`%APPDATA%\\Code\\User\\mcp.json\` (Windows) | \`servers\` |
3661
+ | claude-code | \`~/.claude.json\` (user scope) | \`mcpServers\` |
3662
+ | claude-desktop | \`~/Library/Application Support/Claude/claude_desktop_config.json\` (macOS), \`%APPDATA%\\Claude\\claude_desktop_config.json\` (Windows). Not available on Linux. | \`mcpServers\` |
3663
+ | windsurf | \`~/.codeium/windsurf/mcp_config.json\` | \`mcpServers\` |
3664
+
3665
+ Detection is by per-client config-dir existence — if the dir is
3666
+ missing, the client is reported \`detected: false\`. You can still wire
3667
+ that client explicitly via \`--client <name>\` (the dir is created on
3668
+ write).
3669
+
3670
+ ## Conventions
3671
+
3672
+ - **Atomic writes.** A tmp file is renamed into place; partially-written
3673
+ configs never appear.
3674
+ - **Idempotent.** Re-running \`mcp add\` is a no-op when the ish block
3675
+ already matches the expected shape.
3676
+ - **Preserves unrelated keys.** Other MCP servers in the same config
3677
+ file, and unrelated top-level settings, are kept verbatim.
3678
+ - **No tokens written.** The hosted MCP server handles OAuth on first
3679
+ connect; only the URL goes into the client config file.
3680
+ - **Drift refusal.** If the existing ish block in a client config has
3681
+ a different URL/shape than ours, \`mcp add\` exits 2 unless
3682
+ \`--force\` is passed.
3683
+
3684
+ ## Flags
3685
+
3686
+ | Flag | Effect |
3687
+ |-------------------|-----------------------------------------------------------------------------------------|
3688
+ | \`--client a,b\` | Comma-separated and/or repeatable client keys. Mutually exclusive with \`--all\`. |
3689
+ | \`--all\` | Apply to every *detected* client on this OS. |
3690
+ | \`--dry-run\` | Print the planned mutations as JSON; write nothing. |
3691
+ | \`--force\` | Overwrite an existing ish block that has drifted. |
3692
+ | \`-y, --yes\` | Confirm writes. Required when stdout is piped or \`--json\` is set. |
3693
+
3694
+ ## JSON output
3695
+
3696
+ \`\`\`json
3697
+ {
3698
+ "ok": true,
3699
+ "server_url": "https://mcp.ishlabs.io/mcp",
3700
+ "dry_run": true,
3701
+ "plan": [
3702
+ {
3703
+ "client": "cursor",
3704
+ "display_name": "Cursor",
3705
+ "config_path": "/Users/me/.cursor/mcp.json",
3706
+ "action": "create",
3707
+ "expected": { "url": "https://mcp.ishlabs.io/mcp" }
3708
+ }
3709
+ ],
3710
+ "hint": "Re-run with \`ish mcp add --all --yes\` to commit these writes."
3711
+ }
3712
+ \`\`\`
3713
+
3714
+ \`action\` is one of: \`create\` (no client config file yet),
3715
+ \`update\` (file exists, ish block being added or overwritten),
3716
+ \`skip\` (already up to date), \`refuse-drift\` (different block exists,
3717
+ re-run with \`--force\`), \`remove\` / \`remove-noop\` (for \`mcp remove\`).
3718
+
3719
+ ## Overriding the server URL
3720
+
3721
+ \`\`\`bash
3722
+ ISH_MCP_URL=http://localhost:8000/mcp ish mcp add --client cursor --yes
3723
+ \`\`\`
3724
+
3725
+ Useful for the dev backend or a hosted preview environment. The same
3726
+ env var is read by \`mcp list\` so the reported status reflects the
3727
+ overridden URL.
3728
+
3729
+ ## Related
3730
+
3731
+ - \`reference/json-mode\` — display vs capture vs chain output rules.
3732
+ `;
3556
3733
  const PAGES = [
3557
3734
  {
3558
3735
  slug: "overview",
@@ -3575,13 +3752,13 @@ const PAGES = [
3575
3752
  {
3576
3753
  slug: "concepts/iteration",
3577
3754
  title: "concept: iteration",
3578
- description: "One configured run of a study (URL, media, or chat). Covers segments, segment labels, HTML content, and chat mode_details (external_chatbot vs tester_pair).",
3755
+ description: "One configured run of a study (URL, media, or chat). Covers segments, segment labels, HTML content, and chat mode_details (external_chatbot vs participant_pair).",
3579
3756
  body: CONCEPT_ITERATION,
3580
3757
  },
3581
3758
  {
3582
3759
  slug: "concepts/assignment",
3583
3760
  title: "concept: assignment",
3584
- description: "A single task a tester performs; CLI input formats.",
3761
+ description: "A single task a participant performs; CLI input formats.",
3585
3762
  body: CONCEPT_ASSIGNMENT,
3586
3763
  },
3587
3764
  {
@@ -3603,9 +3780,9 @@ const PAGES = [
3603
3780
  body: CONCEPT_ROUND,
3604
3781
  },
3605
3782
  {
3606
- slug: "concepts/profile",
3607
- title: "concept: tester profile",
3608
- description: "Reusable audience persona; generate vs manual create.",
3783
+ slug: "concepts/person",
3784
+ title: "concept: person",
3785
+ description: "Reusable persona; generate vs manual create.",
3609
3786
  body: CONCEPT_PROFILE,
3610
3787
  },
3611
3788
  {
@@ -3615,10 +3792,10 @@ const PAGES = [
3615
3792
  body: CONCEPT_SOURCE,
3616
3793
  },
3617
3794
  {
3618
- slug: "concepts/audience",
3619
- title: "concept: audience selection",
3620
- description: "Audience flags shared by study run and ask run --new.",
3621
- body: CONCEPT_AUDIENCE,
3795
+ slug: "concepts/people",
3796
+ title: "concept: people selection",
3797
+ description: "People-selection flags shared by study run and ask run --new.",
3798
+ body: CONCEPT_PEOPLE,
3622
3799
  },
3623
3800
  {
3624
3801
  slug: "concepts/site-access",
@@ -3641,7 +3818,7 @@ const PAGES = [
3641
3818
  {
3642
3819
  slug: "concepts/extending-a-simulation",
3643
3820
  title: "concept: extending a simulation (study extend)",
3644
- description: "Resume a terminal tester with more steps and an optional mid-run instruction. Cancel + extend as a reversible stop/start pair.",
3821
+ description: "Resume a terminal participant with more steps and an optional mid-run instruction. Cancel + extend as a reversible stop/start pair.",
3645
3822
  body: CONCEPT_EXTENDING_SIMULATION,
3646
3823
  },
3647
3824
  {
@@ -3683,13 +3860,13 @@ const PAGES = [
3683
3860
  {
3684
3861
  slug: "guides/first-study",
3685
3862
  title: "guide: your first study, end to end",
3686
- description: "Login → workspace → audience → study → iteration → run → results.",
3863
+ description: "Login → workspace → people → study → iteration → run → results.",
3687
3864
  body: GUIDE_FIRST_STUDY,
3688
3865
  },
3689
3866
  {
3690
3867
  slug: "guides/chat",
3691
3868
  title: "guide: chat-modality studies",
3692
- description: "Configure a chatbot endpoint (slots-only model), smoke test it, run a chat-modality study (external_chatbot mode). Also: tester_pair mode — two AI personas talk to each other for rehearsal scenarios.",
3869
+ description: "Configure a chatbot endpoint (slots-only model), smoke test it, run a chat-modality study (external_chatbot mode). Also: participant_pair mode — two AI personas talk to each other for rehearsal scenarios.",
3693
3870
  body: GUIDE_CHAT,
3694
3871
  },
3695
3872
  {
@@ -3699,10 +3876,16 @@ const PAGES = [
3699
3876
  body: GUIDE_COLD_START,
3700
3877
  },
3701
3878
  {
3702
- slug: "guides/build-specific-tester",
3703
- title: "guide: build a specific simulated tester from notes",
3704
- description: "Iterative probe loop for one specific persona: profile suggest-scenarios returns LLM probes; answer them locally; profile evidence add persists answers; profile evidence list reads them back.",
3705
- body: GUIDE_BUILD_SPECIFIC_TESTER,
3879
+ slug: "guides/build-specific-person",
3880
+ title: "guide: build a specific simulated person from notes",
3881
+ description: "Iterative probe loop for one specific persona: person suggest-scenarios returns LLM probes; answer them locally; person evidence add persists answers; person evidence list reads them back.",
3882
+ body: GUIDE_BUILD_SPECIFIC_PERSON,
3883
+ },
3884
+ {
3885
+ slug: "guides/mcp-add",
3886
+ title: "guide: wire ish into your AI clients (`ish mcp add`)",
3887
+ description: "One command wires the hosted ish MCP server into Cursor, VS Code, Claude Code, Claude Desktop, and Windsurf. Idempotent, atomic, preserves unrelated keys, no tokens written.",
3888
+ body: GUIDE_MCP_ADD,
3706
3889
  },
3707
3890
  ];
3708
3891
  const PAGES_BY_SLUG = new Map(PAGES.map((p) => [p.slug, p]));