Back to Home

Carrier — A Researcher's Guide

A guide for researchers — psychologists, social scientists, HCI researchers, and study designers — who want to use Carrier to run interactive experiments involving any combination of human participants, LLM chatbots, scripted chatbots, and Claude Agents. Part I introduces the four systems out of which every Carrier experiment is built; Part II covers the day-to-day mechanics of running a study.

Who this guide is for

This guide is written for researchers — psychologists, social scientists, HCI researchers, and study designers — who want to use Carrier to run interactive experiments involving any combination of human participants, LLM chatbots, scripted chatbots, and Claude Agents.

It assumes you are comfortable thinking about experiments in the usual methodological terms — conditions, manipulations, between- vs. within-subjects designs, counterbalancing, blinding, attention checks — but not that you have any prior experience with the platform or with the engineering vocabulary used internally to describe it.

Whenever Carrier uses a term that has a recognisable counterpart in research methodology, the first time you meet it we will name the counterpart explicitly.

How to read this guide

The guide has two parts.

Part I — Designing an Experiment is conceptual. It introduces the four systems out of which every Carrier experiment is built. Each chapter begins with the research problem the system solves, then names the building blocks Carrier offers, and finally describes the design decisions you make as a researcher. Short builder walkthroughs (still images and short GIFs) and worked examples accompany each chapter.

Part II — Operating the Platform is operational. It covers the day-to-day mechanics of running a study: activating an experiment, monitoring participants, exporting data, managing accounts. It is short and assumes you have read at least the relevant chapters of Part I.

Appendices at the end provide a glossary, the type-by-role compatibility matrix, and quick-reference indexes for segment types and trigger types.

The four systems

A Carrier experiment is built out of four interlocking systems. Each answers one of four design questions:

System Question What it gives you
Chamberlines, chambers, segments What is the shape of a participant's journey? A way to specify conditions, group participants, and lay out the activities they move through.
Roles Who takes part, and in what capacity? Three roles (communicator, mediator, processor) that any human, LLM chatbot, scripted chatbot, or agent can occupy.
Variables What do we know about each participant, and how should that change their journey? A way of carrying participant attributes through the experiment and using them to filter matches, gate visibility, and personalise instructions.
Triggers How should non-human participants behave? A rule-based system that defines, for every non-human participant, the conditions under which they speak or act.

Schematically, they fit together like this:

Experiment
Chamberline · condition
Chamber 1
segments
slots / roles
Chamber 2
segments
slots / roles
↑ Variables
Travel with each participant. Decide which chamberline, which slots, which chambers, and what each activity says.
↑ Triggers
Govern the behaviour of every non-human participant inside any chamber.
The four systems of a Carrier experiment. Chamberlines hold chambers; chambers hold segments and slots; variables and triggers cut across the structure.

The systems are introduced in this order — shape, then occupants, then information, then behaviour — because each layer presupposes the one before it. Once you have read all four chapters you can return to each independently.

A note on terminology

Carrier termClosest research counterpart
ChamberlineCondition / experimental arm
ChamberA timed grouping of matched participants
SegmentAn activity / phase within a chamber
SlotA position in a chamber to be filled by a participant
RunOne participant's complete pass through the experiment
ChatroomThe live instantiation of a chamber for a matched group
VariableAn attribute attached to a participant (from a survey, an assignment, or the system)
TriggerA condition–response rule that governs a non-human participant

These mappings are not strict — see the glossary in Appendix A for nuance — but they will get you most of the way.

A note on non-human participants

Carrier distinguishes three kinds of non-human participant. The distinction is important because they differ in what they can do, how reproducible they are, and which research designs they serve best.

  • LLM chatbots are language-model-driven participants (OpenAI, Anthropic, Google, or any compatible provider) that converse. On each turn the model is given the conversation so far and a system prompt, and it produces a chat message. That message is the entirety of its output. LLM chatbots are open-ended and naturalistic, but they vary from session to session. Use them when you want behaviour that reads and reacts to conversation in an unconstrained way.
  • Scripted chatbots are rule-driven participants whose responses are pre-written. A scripted chatbot is configured with a set of triggers (§5) — rules of the form “when keyword X is uttered, send one of these three sentences” — and produces nothing else. Scripted chatbots are deterministic and replayable: the same input sequence produces the same outputs across sessions. Use them when you want behaviour that is reproducible, auditable, and identical across participants.
  • Agents are autonomous participants built on Anthropic's Claude Agent API. Unlike a plain LLM chatbot, an agent does not just answer from the conversation history — it has access to a vocabulary of built-in tools it can invoke on its own initiative to gather information or take action: reading files from a configured document area, running commands, browsing the web. The agent chains these tool calls across multiple steps without being asked between each step, and produces its eventual chat message grounded in what it read or computed. Each agent is typically scoped to a particular document area — the study materials, a cited corpus, a specific dataset — and behaves as the experiment's resident expert on that material. Use an agent when you want a non-human participant that retrieves and reasons over a body of material during the conversation, not just one that talks from training-time knowledge.

All three kinds can take any of the three roles described in §3, with one exception: scripted chatbots cannot serve as processors (see §3.5). The relationship between kinds and roles is summarised in the type × role matrix in §3.1 and again in Appendix B.

When this guide says “chatbot” without qualification it means either an LLM chatbot or a scripted chatbot. “Agent” — capitalised or not — means specifically a Claude Agent. “Non-human participant” is the umbrella term that covers all three.

A note on terminology overlap: any LLM-driven participant (an LLM chatbot or an agent acting in a mediator role) can also be configured to emit Carrier intervention actions — disable a participant's input, prompt someone, highlight a message — alongside its chat message. These intervention actions are a Carrier-specific structured-output mechanism, not the same thing as a Claude Agent's built-in tools. §5.5 separates the two carefully; for now, it is enough to know that “tools the agent uses to read files and browse” and “intervention actions a mediator chooses to fire” are different channels.

Part I · §1. Chamberlines and Chambers

The outer shape of a Carrier experiment: chamberlines isolate the condition and chambers isolate the matched group. The activity is the subject of §2.

1.1 The shape of a Carrier experiment

Every Carrier experiment has the same nested shape. A participant who opens the experiment URL is assigned to a chamberline — the condition they will experience. Their chamberline is an ordered sequence of chambers, each of which is a small group of participants (human, AI, or both) who are matched once and remain together for the duration of that chamber. Inside each chamber, participants progress through an ordered list of segments — the activities that constitute the chamber, of which a real-time conversation is the most common.

This three-level structure is the backbone of Carrier:

Participant
Chamberline condition / arm
Chamber matched group, stable across segments
Segment activity
The three-level nested shape of a Carrier experiment.

The three levels exist to separate three concerns that are easily entangled when designing an interactive study:

  • Chamberlines isolate the condition. Different chamberlines represent different experimental arms; a participant sees exactly one.
  • Chambers isolate the matched group. Within a chamber, who you are with does not change.
  • Segments isolate the activity. Within a segment, what you are doing does not change.

The remainder of this chapter introduces each level in turn.

1.2 Chamberlines: the unit of condition

1.2.1 Why chamberlines

In a typical lab study you might compare two or three experimental conditions — say, high-anonymity versus low-anonymity discussions of a controversial topic. A chamberline is Carrier's representation of exactly that: a complete journey that one group of participants will take through the experiment. Multiple chamberlines in the same experiment correspond to multiple between-subjects conditions.

A participant is assigned to a single chamberline at the start of their session and does not leave it. Within-subjects comparisons (the same person experiencing two manipulations) are typically built inside a chamberline, by sequencing chambers that differ along the manipulated dimension; between-subjects comparisons (different people in different manipulations) are built across chamberlines.

1.2.2 Assigning participants to chamberlines

Carrier offers four assignment methods, configured at the experiment level:

MethodWhat it doesWhen to use
RandomEach new participant is allocated to a chamberline uniformly at random.The default for simple between-subjects studies.
CounterbalanceCarrier maintains running counts and assigns each new participant to the currently smallest chamberline.When you want equal n per condition and cannot wait for the law of large numbers.
Survey-basedA field from the global pre-survey is read at assignment time and used to choose the chamberline.When the condition depends on a participant attribute that they declare themselves (e.g. native language, political identification).
FixedAll participants are placed in the same named chamberline regardless of anything else.Pilots and demonstrations; reproducing exactly one condition.

Survey-based assignment is the most flexible. The global pre-survey runs before chamberline assignment, so any response collected there is available as a routing variable; this is the same mechanism described in §4.

1.2.3 What lives on a chamberline

A chamberline is, formally, a name plus an ordered list of chambers plus optional assignment criteria. There is no further configuration at this level — chamberlines are intentionally thin, so that a researcher can read the shape of an experiment by scanning the chamberline names and their chamber sequences in the builder.

🎞 Builder walkthrough — Creating chamberlines for two conditions

A new experiment already carries one chamberline, so the researcher clicks “Add Chamber Line” once to get a second. They click each chamberline’s name in turn and rename them “High Anonymity” and “Low Anonymity”, then click the empty canvas to return to Experiment Settings and set Chamberline Assignment to “Random”. The two chamberlines now stack on the canvas, each ready to receive chambers.

1.3 Chambers: the unit of matched group

1.3.1 Why chambers

A chamber is the basic unit of togetherness in Carrier. Once a participant enters a chamber, they are matched with the other occupants of that chamber and they stay together until the chamber ends. Within a chamber, the cast does not change.

This is a deliberate constraint. Many interactive studies depend on participants having a stable conversational partner across multiple tasks — a discussion followed by a joint ranking, for example, or a chat followed by a rating of the other person. In Carrier, those sequential activities belong in the same chamber and share its participants. Crossing a chamber boundary, by contrast, dissolves the group: the next chamber re-matches its occupants from the pool of participants who have reached that point.

1.3.2 Matching at the chamber boundary

Matching happens once, at the start of each chamber. Participants who finish the previous chamber (or, for the first chamber in a chamberline, who have completed the global pre-survey) enter a waiting pool. As soon as enough participants are present to fill the chamber's required slots, the chamber begins.

A slot is a description of the kind of participant the chamber needs. Each slot has a type (human, LLM chatbot, scripted chatbot, or agent) and a role (communicator, mediator, or processor; see §3). For human slots, matching can additionally require certain variable values — for example, that the chamber contain one self-identified novice and one self-identified expert. Variable-based matching is the topic of §4.

When a matching attempt does not assemble enough participants within a configured interval, Carrier applies a chamber-level fallback policy. The fallback is part of the chamber's configuration; common choices are to keep the participant waiting, to fill the missing slot with a default agent, or to end the run gracefully with a completion code. The choice is the researcher's, not the participant's.

1.3.3 What lives on a chamber

A chamber carries several pieces of configuration:

  • A name and identifier, used in the dashboard and in exported data.
  • A communication channel: text, audio, or video. The channel determines what the chat segment looks like and what data is recorded (transcripts, audio files, recorded video, or any combination).
  • A slot definition, listing the roles to be filled and the type each slot expects.
  • An ordered list of segments, described in §2.
  • An optional pre-survey shown to each participant before they enter the chamber, and an optional post-survey shown after they leave.
  • A maximum participant count — the total number of slots.

Chamber pre- and post-surveys are distinct from the experiment's global pre- and post-surveys. The global surveys run once per participant, at the very beginning and end of the run; the chamber surveys run once per chamber. Researchers typically use the global surveys for demographics and consent, and the chamber surveys for state measures that need to be taken before and after each manipulation.

🎞 Builder walkthrough — Configuring a chamber

The researcher selects a chamber, renames it to “Deliberation” in the configuration panel, and drags Human Participant from the Library into the chamber’s Communicators lane twice. The chamber card’s occupancy badge updates to two. (Slots are added by dragging an entity into a role lane, not through an “add slot” button; the communication channel is a setting on the chat segment, not on the chamber — see §1.4.)

Segments — the activities a matched group moves through inside a chamber, and the parameters of each — are the subject of §2. Roles are the subject of §3.

Part I · §2. Segments

The unit of activity: what a matched group actually does, one activity at a time, and how each of the fourteen kinds is configured.

2.1 The segment system

A chamber's segments are its inner timeline: the participants are already matched, and now they move together through an ordered series of activities. Each segment is a single, self-contained activity — showing a slide, holding a conversation, voting on options, ranking items, watching a video, or completing a short embedded survey — with its own timing and transition rules.

Segments are deliberately fine-grained. A “thirty-minute deliberation” study in Carrier is usually not a single thirty-minute chat segment but a sequence: a slide introducing the topic, a timer giving participants a moment to think, a chat segment for the deliberation itself, a ranking segment to record the group's collective answer, and a short survey at the end. Each piece is configured separately, recorded separately, and can be skipped, repeated, or replaced without touching the others.

2.2 The catalogue of segment types

Carrier ships fourteen segment types. The Compatible with AI column marks the types a non-human participant can contribute to — LLM chatbots, scripted chatbots, or agents. The conversational and choice-based types can; the read, listen, wait and integrity types cannot, because there is nothing for a non-human participant to do. The Embeddable column marks the types that can run as an overlay on a chat rather than taking the screen to themselves, which is the subject of §2.3.

TypeWhat participants doCompatible with AIEmbeddable
instructionRead formatted instructions and click Continue.
slideStep through a deck of researcher-designed slides, one at a time.
mediaWatch or listen to a clip.
pdfRead a document, optionally beside a live chat.
timerWait, with or without a visible countdown.
surveyAnswer an embedded Survey.js questionnaire.
inputWrite a free-text answer.
selectionChoose from a set of options.
rankingDrag a set of items into an order.
chatHold a real-time conversation with the other occupants.
socialBrowse a mock social feed — comment, reply, like or vote, follow.
taskComplete a custom interactive task.
processRun an LLM processing step between activities.
attention-checkPass a survey- or camera-based attention check.

Each type has its own reference entry below, from §2.6 onwards: what the participant sees, what it is for, and every parameter its configuration panel exposes.

2.3 Standalone and embedded segments

Most segment types occupy the participant's entire screen for the duration of the segment. We call these standalone segments: they take their turn in the chamber's timeline, run to completion (or timeout), and then yield to the next segment.

Most non-chat segment types can additionally be configured to run embedded inside a chat segment. An embedded segment is rendered as an overlay on top of an ongoing chat, so that participants can act on it — vote, rank, write, watch a clip, read an instruction — without leaving the conversation. The chat continues to record messages in the background, and the embedded child appears only when its start trigger fires.

Standalone
The vote

The activity replaces the screen. The conversation is not visible while it runs, and resumes when it ends.

Embedded
The vote

The activity is laid over the chat. Messages keep arriving behind it, and the group answers without leaving the discussion.

The same selection segment in both display modes. Only the second one keeps the conversation on screen — which is the whole reason the embedded mode exists.

Embedded display is supported for the selection, ranking, pdf, social, input, task, slide, instruction, timer, media, and survey segment types. Two segment types are excluded outright: chat (it is the parent container; you cannot embed a chat inside a chat) and attention-check (no embedded renderer — attention checks always run standalone). A third, process, is offered the setting by the builder but has no embedded renderer either, so an embedded process segment shows the participant an error rather than a step — leave it standalone. On screens narrower than 900 px the runtime additionally falls back to standalone rendering for every segment, so the layout never breaks on mobile.

Embedded segments are useful when the activity is part of the conversation rather than an interruption to it. Two examples:

  • Periodic polling during a deliberation. Ask participants to vote at three points (after one minute, after three minutes, after five minutes) while they continue talking. Each vote is a separate embedded selection segment with a different embedded-start trigger.
  • Pacing a ranking activity to chat progress. Show a ranking overlay only once the conversation has produced enough material to rank — for example after a fixed number of chat messages, or after a fixed time offset from the start of the chat.

An embedded segment adds three configuration parameters beyond its standalone equivalent:

ParameterWhat it does
Embedded startWhen the overlay first appears, relative to the parent chat: immediately, after N seconds, after N chat messages, or after the previous embedded sibling ends (chained, with an optional delay).
Embedded stopWhen the overlay closes: as soon as the participant submits / clicks Next (the default), or after a hard N-second timeout.
Embedded completion behaviourWhat happens when the embedded child finishes: dismiss it (continue chatting), end the parent chat, lock the overlay so it cannot be reopened, or minimise it as a badge.

A chat segment with one or more embedded children may use an additional transition mode, embedded-complete, which ends the chat when every embedded child has completed. This is the canonical way to build a chat segment whose end is gated on the group having voted (or ranked, or read the instruction), rather than on a fixed duration. An optional fallback timeout on the chat itself is a safety net for chains whose start triggers might never fire — for instance an after N messages trigger if the participants never reach that message count.

For an attention-check segment (or any activity that needs the participant's complete attention), keep the display mode at standalone — the chamber timeline yields to it the same way it would for a survey or instruction.

🎞 Builder walkthrough — Embedding a vote in a chat

The researcher selects a chat segment in a chamber, adds a selection segment immediately after it in the segment timeline, and changes the selection segment's display mode to “Embedded (overlay on chat)”. They set the embedded start to “after 60 seconds” and the completion behaviour to “minimise”. The selection segment now appears in the timeline as an indented child of the chat, with a small “embedded” badge.

2.4 Inline and split view

An embedded segment can occupy the chat in one of two ways, chosen with the Embedded Layout setting.

Inline renders the child as a card appended into the message stream itself. The card scrolls with the conversation, in the position the conversation had reached when it appeared. This suits short interruptions — a vote, a one-line question — where the activity is a moment in the discussion rather than a task beside it.

Split docks the chat into a column and gives the child its own pane alongside. Nothing scrolls away: participants read, watch or work in one pane while talking in the other. This is the layout for material that has to stay on screen — a document being discussed, a task being performed collaboratively. The pdf segment is the only type that defaults to it, because reading a document while discussing it is the case it exists for.

Inline
Card

The card sits in the message stream and scrolls away with it. Suits a moment in the conversation — a quick vote, a one-line question.

Split
Chat
Document

The chat docks into a column and the activity gets a pane of its own. Nothing scrolls away — for material that must stay on screen.

Both are embedded — the difference is whether the activity travels with the conversation or sits beside it. A split layout with no chat parent falls back to inline.

Card width and card height size the child as a percentage of the available area, defaulting to 75 % and 35 %.

⚠ Three silent fallbacks

An embedded segment degrades rather than failing, which means a misconfiguration shows up as a participant seeing the wrong thing rather than as an error at save time:

  • Split without a chat parent falls back to inline. The split pane mounts against an actual chat segment; if the embedded child does not follow one, the layout quietly reverts.
  • Narrow viewports fall back to standalone. Below roughly 900 px the child takes the whole screen instead of overlaying the chat, so a participant on a phone sees a different sequence from one on a laptop.
  • Mediators and observers see the standalone form. The overlay is rendered for the participants doing the activity, not for those watching it.

2.5 Timing, transitions, and participant pacing

Every segment has its own timing and transition rules, which together determine how long participants spend on it and how they advance. These are the four parameters that matter most:

  • Duration — the maximum time the segment may run. If left unset, the segment has no automatic deadline.
  • Minimum duration — the earliest moment at which a participant may advance. This is the standard way to enforce a floor on engagement (a “read for at least 30 seconds before continuing” instruction slide, for example).
  • Warning time — how long before an auto-advance the participant is warned. Useful to prevent surprise transitions in long segments.
  • Transition mode — the rule for moving on:
ModeDescription
AutoThe segment advances on its own when the duration elapses.
ManualEach participant advances when they click Continue.
SyncThe segment advances only when every participant in the chamber is ready. Keeps the group in lock-step.
HostThe experimenter advances the segment from the dashboard.
Embedded-complete(Chat segments only) Advances when every embedded child has completed.

The pacing choice has substantive consequences. A sync transition gives participants the experience of a shared rhythm, but it also means that the slowest participant determines the group's pace, which can be frustrating in long studies. A manual transition lets each participant move at their own speed, but it can break the group character of a chamber if used for the chat segment itself. A host transition is most useful during pilot testing — the experimenter can step the group through the timeline by hand to debug pacing.

2.6 The slide segment (deck)

A slide segment is a deck: an ordered set of slides that the participant steps through one at a time. That is the whole distinction from instruction, and it is worth stating before anything else — an instruction is one thing to read and acknowledge; a slide segment is a sequence to present. A study brief with a Continue button underneath belongs in instruction (§2.7), which has the better authoring surface for prose. A stimulus that unfolds across four screens — a scenario, then a chart, then the manipulation, then the question framing — belongs here, as one segment rather than four.

The participant sees one slide filling the activity area, with the usual chamber chrome around it — segment progress indicator, countdown clock and participant list in the sidebar, subject to the chamber's own settings. Beneath the slide sits a Next button on every slide but the last, and a Back button beside it on every slide but the first, and then only if the segment allows going back, which it does not by default. On the last slide the deck's Next disappears and the segment's own Continue takes its place, so leaving the deck and leaving the segment are never two separate clicks. That chamber progress indicator counts segments: there is deliberately no counter for the deck itself, so the participant is not told how many slides remain, because knowing changes how carefully the early ones are read. If a slide sets a minimum dwell, the button it holds renders disabled with “Available in 12s” counting down beside it. There is nothing to submit inside a slide: the participant reads or watches, then moves on.

Slides are built in the Deck Builder, a modal opened from the segment's configuration panel. The left column is the slide list — add, duplicate, reorder, delete — and the right pane holds the selected slide's layout settings and its blocks. A block is one piece of content of a declared type: a heading, a text block written as Markdown in the same full-screen editor the instruction segment uses, an image or video chosen from the experiment image library or given by URL, or an html block injected into the page exactly as authored. Blocks stack in the order listed, and nothing is written back to the experiment until Save deck.

What you put in an HTML block
<h2>Round 1 — the proposal</h2>
<p>Your group has <strong>£10,000</strong> to allocate between three bids.</p>
<blockquote>{{var.first_name}}, you have been asked to speak first.</blockquote>
What the participant sees
A participant's screen showing the slide: the segment name and an unlimited-duration clock in the sidebar, a participant list containing Amara, and the rendered slide with a Continue button.
A real participant session, not a mock-up. The tags are rendered rather than escaped and {{var.first_name}} has resolved to this participant's value. Note what an Align: centered slide does to the <blockquote>: it is centred with everything else rather than set as a pull quote, because the slide's alignment governs the text inside it too. This is a one-slide deck, so there is no Next — the Continue button comes from the segment's transition settings. The sidebar — segment name, duration, participant list — is chamber chrome, not part of the slide.

Heading, text and HTML blocks support {{var.X}} tokens, resolved per participant when the segment begins (see §4.5) and applied block by block, which is how one deck serves as a personalised briefing across an entire sample. Every slide's tokens are resolved at that same moment, not when the slide is reached, so a token on slide five carries the value it had when the participant entered the segment.

Two settings live on the segment itself; everything else that shapes the deck is per slide, edited in the Deck Builder.

ParameterWhat it controls
Open Deck BuilderOpens the authoring modal (config.slides). The panel beneath the button reports the deck's size — “4 slide(s) · 11 block(s)” — so the shape of a deck is readable without opening it. Nothing typed in the modal reaches the experiment until Save deck; Cancel and the backdrop both discard it.
Allow participants to go backRenders a Back button beside Next on every slide but the first (config.allowBack). Off by default, on the reasoning that revisiting a stimulus contaminates a controlled sequence. Turning it on does not hide the revisits: the view log records every entry, so a re-read is visible in the data rather than silently averaged into one dwell.
Per slide · LayoutStacked (blocks in one column), Media left or Media right (the slide's first image or video block in a pane beside the rest, stacking below 900 px). A slide with no image or video block renders the split layouts as stacked rather than drawing an empty pane.
Per slide · WidthColumn (a capped reading column, the default) or Full width (edge to edge).
Per slide · AlignCentered (the default) or Left. This positions the block column and sets the text alignment inside it to match — unlike the instruction segment's Content Alignment, which moves the column without touching the prose.
Per slide · BackgroundA CSS colour or an image URL, applied to the whole slide area — a colour as the background colour, a URL as a cover image. Values that are neither a colour nor an http(s) URL are ignored rather than passed through to the stylesheet.
Per slide · Minimum dwell (seconds)Holds this slide's button disabled, with “Available in…” counting down beside it, until the time has elapsed. It gates the deck's Next on any slide but the last, and the segment's Continue on the last. It is a floor on this slide alone, counted from the moment the slide appears, and separate from the segment's own Minimum duration, which is counted from the moment the segment starts and gates leaving it. On the last slide both hold the same Continue, and the participant waits for whichever expires later — they do not add up.
Per slide · Advance when the clip endsOffered only on a slide that has a video block. When the clip finishes, the deck moves to the next slide — or, on the last slide, ends the segment. It waits out this slide's minimum dwell first in either case, and on the last slide the segment's minimum duration as well, matching what the buttons beside it would allow. Unticked, the clip simply stops and the participant clicks on.
Block · HeadingA line of text at level H1, H2 or H3. Escaped, so it is text rather than markup.
Block · TextMarkdown, written in the same full-screen editor the instruction segment uses — formatting toolbar, live preview, and an Insert variable picker that lists every experiment variable as {{var.key}}, split into Available here and a greyed-out Not yet defined group. Availability is computed for this segment's exact position.
Block · Image / VideoA URL and an alt text, which becomes the image's alt. Beside the URL field sit a dropdown of the experiment's image library and an Upload… button that adds files to it — the same library the social feed segment uses (§2.16); anything uploaded from either place is available in both. Only http and https URLs are rendered; anything else shows Invalid media URL. in place of the stimulus rather than being embedded, and a block left without a URL shows No media URL configured.
Block · HTMLInjected into the participant's page as written: tags are not escaped, so an <img>, a table, a styled vignette or an <iframe> pointing at an external stimulus all work exactly as authored. It is the escape hatch for a screen that has to look like something specific, and it is where a slide authored before decks lands when it is converted.
Display ModeStandalone (replaces the screen) or Embedded (a card inside a running chat), stored as config.displayMode. Choosing Embedded reveals the start / stop / completion trigger fields and hides the Timing & Transition card entirely, because an embedded slide is paced by its triggers instead.
Timing · Duration and Transition · ModeShared fields, but load-bearing here: reaching the end of the deck does not end the segment by itself, so duration and transition mode are still what ends it — the one exception being a last slide whose video is set to advance when the clip ends. See §2.5.

There is no Data Sharing control on a slide, and no results display. Those appear only on segments that collect a response from the participant, which a deck does not — though it does record how the participant moved through it.

Every slide change writes a view log to the segment's submission: one entry per visit, carrying the slide's id and position, the millisecond offsets at which it was entered and left, and the resulting dwell. A slide visited twice appears twice, so revisits under Allow participants to go back are countable rather than merged. Alongside it sit slidesViewed, deckSize and deckCompleted, the last recording whether the participant actually reached and left the final slide. All of it arrives in the Segment Submissions section of the data export (§6.5), with the log itself as a JSON string in the submission's data.

A slide segment authored before decks existed keeps working untouched: it is read at run time as a one-slide deck, its old content becoming a single block and its old layout mapping onto the new width and alignment. There is no migration and nothing to re-save — the old fields stay on disk exactly as they are, and the segment's configuration panel says so. They stop being read the moment the segment is saved from the Deck Builder, which is what gives such a segment a slides array.

2.6.1 Standalone and embedded display

A slide runs standalone by default, and can be embedded inside a chat segment — it is one of the types the builder offers the Display Mode dropdown for. Embedded, it defaults to the inline layout, appearing as a card in the message stream with its own Continue button and, optionally, pinned to the top of the chat while it is active; the alternative split layout mounts the content in a pane beside a docked live chat. Two conditions gate this. The embedded segment must sit immediately after the chat segment it belongs to in the chamber timeline, which is how the builder decides whose child it is. And on viewports narrower than 900 px the runtime ignores the embedded setting and renders the slide standalone, so a participant on a phone sees a full-screen slide where a desktop participant sees a card in the conversation — worth knowing before treating an embedded slide as a controlled stimulus.

A deck works inside the card as it does full-screen: the slides replace one another in place, and the card's Continue stays hidden until the last slide is reached, so one click cannot skip the rest of the deck. The card's timing is the exception noted above — the Timing & Transition controls are replaced by the embedded triggers — but per-slide minimum dwell is set in the Deck Builder and keeps working either way.

⚠ Before trusting a deck

Four things behave in ways a deck's authoring surface does not warn about.

  • Finishing the deck does not finish the segment. The last slide hands over to the segment's own Continue, and under transition mode auto with early advance off and no duration set, that button is never drawn — leaving the participant on the final slide with nothing to click, however many slides they got through. A last slide whose video has Advance when the clip ends ticked is the one way out of that combination, and it only works once the clip has actually played to the end. Otherwise: give an auto segment a duration, or allow early advance, or use a manual transition. The configuration panel does warn when an auto segment has no duration, which is the common way into this.
  • Saving from the Deck Builder is what converts a legacy slide. Until then the old content / contentType / mediaUrl fields are what runs, adapted into a single slide. Once the segment has been saved from the modal it has a real deck, and those fields are dead weight — they stay on disk, but editing them through the API or a config import changes nothing on screen.
  • Interpolation is frozen at entry, for the whole deck. {{var.X}} tokens in every slide are resolved once, when the participant reaches the segment — not when each slide is reached. A variable that changes while the participant is working through the deck does not update the later slides, so a long deck showing a live value will quietly show a stale one.
  • Changing the segment's Type discards the deck. Switching a slide segment to another type replaces its configuration with that type's defaults, without a confirmation, and every slide and block is gone.
🎞 Builder walkthrough — Authoring a slide with a personalised variable

The researcher drags Slide onto the chamber timeline, writes the slide's content, drops a {{var.condition}} token into it from the variable picker, and sets the transition so the segment ends on a participant click. Recorded before the Deck Builder existed, so the content and the variable picker are shown in the configuration panel rather than inside a text block's editor; the timeline and transition steps are unchanged.

2.7 The instruction segment

An instruction segment gives the participant one block of formatted prose and one button. The text is authored as Markdown and rendered on screen — headings, lists, emphasis, links, inline code, tables and rules all survive — in a column 800 px wide by default, centred horizontally, and centred vertically when the text is short enough to fit without scrolling. Beneath it sits a single primary button, labelled “Continue” unless renamed. That button is drawn disabled and stays disabled until the segment’s pacing conditions are satisfied; when a button delay is set, the remaining seconds count down inside the label itself — “Continue (5s)”, “Continue (4s)” — so the wait reads as deliberate rather than as a broken page.

This is the segment to reach for whenever the participant must read something and acknowledge it: the study brief, a consent-style preamble, the task instructions before an interactive segment, or the framing text that differs between conditions. What separates it from slide is not the content but the shape: an instruction is one screen, and the segment is over when it is acknowledged, whereas a slide segment is a deck the participant walks through (§2.6). One prose screen with a button is an instruction; a sequence is a slide segment. The instruction also owns the acknowledgement itself — a renameable button, a delay before it becomes clickable — so the click can be made to cost the participant a measured number of seconds rather than being a reflex. Because the body is interpolated per participant before it is shown, one instruction segment can carry condition-specific text through {{var.X}} tokens instead of being duplicated once per chamberline (see §4.5). Interpolation is resolved when the participant enters the segment and is not refreshed afterwards, so a variable that changes mid-segment does not alter text already on screen.

What the participant sees
The instruction segment as a participant sees it.
Markdown rendered into a reading column, with the Continue button the segment supplies. {{var.first_name}} has resolved for this participant.
ParameterWhat it controls
Instruction Content (Markdown)The body text. The configuration panel shows a rendered preview of the current content (or “No content yet”) plus an Edit content button that opens a full-screen editor: a formatting toolbar (bold, italic, heading, link, bullet list, numbered list, quote, inline code, image, table, horizontal rule), an Insert variable picker, and side-by-side editing and preview panes that collapse to Edit / Preview tabs on a narrow screen. Changes are committed only on Save; Cancel discards them.
Content AlignmentCenter (the default) or Left. This positions the whole text column within the screen; it does not change the alignment of text inside the paragraphs.
Content WidthNarrow (800 px, the default), Medium (1000 px) or Wide (full width). The same width governs the row holding the button, so the button stays aligned with the prose above it.
Show continue button · Button TextWhether the acknowledgement button is rendered at all, and what it says. Shown by default, labelled “Continue”. The label is also the base of the countdown text while a button delay runs.
Button Delay (seconds)A fixed wait, counted from the moment the segment appears, before the button becomes clickable. Entered in seconds. This is the control that turns an instruction into enforced reading time, and it is the only pacing control that remains visible when the segment is embedded.
Require scroll to bottom before proceedingIntended to hold the button until the participant has scrolled the text to its end. It does not currently do so — see the caveat below.
Minimum duration (Timing & Transition)Shared with every segment, but treated specially here: the button is enabled only once the button delay has elapsed and the scroll requirement is satisfied and the minimum duration has passed. While the floor runs, a hint reading “Available in 0:42” appears beside the button.

The instruction segment runs standalone by default, filling the screen, and can also be set to embedded so that it appears over a live chat. Embedded, it defaults to the inline layout — a card in the message stream — and can instead use split, where the prose fills the main area and the chat docks to a side panel; the latter suits a briefing that participants are meant to consult while they talk. It is one of the segment types whose interactive behaviour is rewired for the embedded path rather than simply drawn there, so the button delay and the disabled-button logic keep working inside the card. The usual embedding rules apply: the segment must sit immediately after a chat segment in the timeline to become its child, and on viewports narrower than 900 px the runtime falls back to standalone rendering.

⚠ What actually holds the button back

Three parameters claim to pace an instruction segment. Only two of them do, and one combination can leave a participant with no way forward at all.

  • Require scroll to bottom before proceeding does not gate. The check is attached to the text element, but that element is not the one that scrolls — the surrounding container carries the overflow — so the initial check reports “already at the bottom” and releases the button on the first paint, however long the instruction is. Nothing warns you in the builder. Use Button Delay (seconds) or a minimum duration to buy reading time, and do not treat the checkbox as evidence that participants reached the end of the text.
  • Turning off Show continue button removes the only exit. The instruction segment renders no navigation of its own beyond that button, and the surrounding segment shell adds none. With the button hidden, the segment must be able to end by itself: give it a Duration with transition mode Auto, or hand control to the experimenter with Host. A hidden button plus a Manual or Sync transition and no duration strands every participant on the page.
  • Delay and minimum duration do not stack. Both clocks start when the participant enters the segment, so a 30-second minimum duration beside a 30-second button delay produces a 30-second wait, not sixty. Neither is redundant, though: the minimum duration also governs the automatic and synchronised advance paths, while the delay governs only the button.
  • Embedding hides the timing controls. Choosing Embedded replaces the Timing & Transition card with the embedded start, stop and completion controls. A minimum duration set before the switch is still enforced but is no longer visible or editable, and the embedded card draws no “Available in…” hint, so the participant sees a disabled button with no explanation. Set the pacing with the button delay instead once a segment is embedded.
🎞 Builder walkthrough — Writing an instruction with enforced reading time

The researcher drags Instruction from the Library onto the chamber timeline, opens the Markdown editor with Edit content, writes a short brief using the toolbar and drops a {{var.condition_label}} token in from Insert variable while the preview pane keeps pace, saves, and then sets Button Text to “I have read this” and Button Delay to 5 seconds so the acknowledgement cannot be a reflex.

2.8 The media segment

A media segment gives the participant one thing: a full-width player filling the content area — a video, an audio bar, or an embedded YouTube or Vimeo frame — with the shared Continue button beneath it. As with every segment, that button disappears when the transition mode is auto and early advance is off, in which case the clip is the whole screen and the segment ends on its own terms.

It exists so that a stimulus clip can be a step in the timeline in its own right, rather than something bolted onto a slide: a news vignette shown before a deliberation, an audio prompt each participant hears alone, a recorded briefing that must precede a task, or a debrief video at the end of a chamber. Because it is a distinct segment, it gets its own timing, its own transition rule, and its own row in the timeline — so “how long were they exposed to the stimulus” is a property of the design rather than something inferred from slide dwell time. The segment records no response of its own; it is exposure, not measurement, and the measurement belongs in whatever segment follows it.

What the participant sees
The media segment as a participant sees it.
A video stimulus with the browser’s own transport controls. The sidebar keeps the segment rail and participant list visible throughout.
ParameterWhat it controls
Media TypeVideo (the default), Audio, YouTube or Vimeo. Video and Audio build a native player around the URL; YouTube and Vimeo build a bare iframe at least 400 px tall. The two embed types are a different code path, not a skin — see the boxes below.
Media URLThe address the player loads. Unlike the pdf segment there is no upload control and no Carrier-side hosting: the file must already be reachable over http(s), from a lab web server, an institutional store, or a CDN. The value is used as given, so a mistyped or expired URL shows an empty player rather than an error.
Poster Image URLA still frame shown before playback begins. Video only in practice — an audio element ignores a poster, and the YouTube/Vimeo path drops it.
AutoplayOn by default. Asks the browser to start the clip as soon as the segment renders. Browsers routinely refuse; see the first box.
Loop playbackOff by default. Restarts the clip when it reaches the end — useful for a short ambient stimulus, and incompatible with ending the segment when the clip ends.
Show player controlsOn by default. Renders the browser's own transport bar: play, pause, seek, volume. This is the participant's only means of controlling playback.
Sync playback across participantsNothing. The checkbox is checked by default and is read nowhere in the runtime; playback is per-participant and unsynchronised.
Allow participants to pauseNothing. Pausing is governed entirely by Show player controls.
Captions URLA WebVTT file attached as a default captions track. Standalone video only: the audio path, the YouTube/Vimeo path and the embedded renderer all omit the track. A cross-origin VTT additionally needs CORS headers on its host.
Display ModeStandalone (replaces chat) or Embedded (overlay on chat), with the usual Inline / Split choice when embedded — see below.
Transition → Mode (shared field, special meaning)For a self-hosted video or audio clip, setting the mode to auto is what arms “advance when the clip finishes”: the runtime listens for the player's end event and moves to the next segment, waiting out the minimum duration first if one is set. There is no separate checkbox for this behaviour, and the slide segment's “auto-advance when media ends” option has no effect on a media segment.

The segment can run standalone or embedded. Standalone is the default; embedded defaults to the inline card, and the split layout — clip in the content pane, live chat docked alongside — is the natural shape for “watch this while you discuss it”. The embedded form obeys the same gating as every other embeddable type: the segment must directly follow the chat segment it overlays, it takes the shared embedded start, stop and completion parameters, and on viewports narrower than 900 px it falls back to standalone rendering. Because media collects no response, the Data Sharing, results-display and anonymity controls that appear on the interactive types are absent here.

⚠ Controls that promise more than they deliver

Four of the checkboxes on this panel read like guarantees and are not. Two of them are checked by default, which makes them worth knowing before a pilot rather than after:

  • Sync playback across participants is inert. It is checked by default, but nothing in the runtime reads it: there is no synchronisation event and no shared clock. Every participant's clip starts when their own segment renders and runs at their own pace. If your design needs the group to see the same frame at the same moment, you must construct that yourself — a fixed segment duration, a preceding timer, and a clip short enough that drift does not matter.
  • Allow participants to pause is inert. Whether a participant can pause is decided by Show player controls alone. Leaving this box unticked does not prevent pausing, and ticking it does not enable it.
  • Autoplay is a request, not an instruction. Browsers block autoplay of audible media without a prior user gesture, and Carrier does not add a muted attribute to work around it — so the clip will usually sit still until the participant presses play. Precede the media segment with an instruction segment whose Continue click supplies the gesture, and leave Show player controls on so there is a play button to press. Turning controls off while autoplay is blocked leaves the participant with no way to start the clip at all.
  • Poster Image URL and Captions URL apply to the standalone video path only. Audio, the YouTube/Vimeo embeds, and any embedded (inline or split) rendering silently drop them. An accessibility commitment made through the captions field therefore does not survive a switch to an embedded display mode.
⚠ Make sure the segment can actually end

End-of-clip auto-advance is the one genuinely media-specific behaviour in this segment, and it is invisible in the config panel. It requires a self-hosted video or audio clip and transition mode auto. Three configurations quietly lose it, and each needs a duration or a Continue button as a fallback:

  • YouTube and Vimeo. The embed is a plain iframe; Carrier receives no end event from it. An auto-transition YouTube segment with no duration set therefore never advances, and with no Continue button rendered the participant is stranded. Give every embed segment an explicit duration.
  • Looping clips. A looping clip never reaches its end, so it never fires the event. Loop and end-of-clip advance are mutually exclusive by construction.
  • Embedded display. The embedded renderer does not attach the end listener — an embedded clip closes only on its embedded stop rule, its duration, or the card's Continue button, however it is configured.

One further caution on the embed types: because YouTube and Vimeo URLs are passed to the frame untransformed, only an already-embeddable address (https://www.youtube.com/embed/<id>) works. The ordinary watch?v= link copied from the address bar is refused by the frame and renders blank — a failure that looks exactly like a broken network on the participant's screen, so it is worth checking in a pilot run rather than in the builder preview.

🎞 Builder walkthrough — Adding a video stimulus that ends itself

The researcher drags Media onto the chamber timeline, leaves Media Type on Video, pastes the clip's URL, then sets the transition mode to Auto and clears the duration — arming the segment to end the moment playback finishes — while the inert Sync playback across participants checkbox sits ticked above it.

2.9 The PDF segment

A pdf segment puts a document on the participant's screen inside a plain frame drawn by the browser's own PDF viewer — so scrolling, zooming, page navigation, search and print are the browser's controls rather than Carrier's, and behave exactly as the participant already expects them to. Run standalone, the frame fills the segment area beneath the usual Carrier chrome (progress bar, countdown, participant list) with a Continue button under it. Run in its default split layout, the document takes the main pane and the live chat is relocated into a docked column beside it, so participants can argue about a text that stays in front of them. Run inline, it arrives as a card in the message stream with a document header and a minimise chevron.

Its purpose is to carry the stimulus — a vignette, a news article, a policy brief, a scientific paper, an information sheet — when the material is longer or more typographically demanding than a slide can carry honestly. It is a reading surface, not a response surface: a pdf segment records no response of its own. That is why the Data Sharing controls other segment types offer are absent from its panel — there is nothing to share — and why the only trace it leaves in the export is how long the participant had it open. Evidence that the document was actually read has to come from its neighbours: a minimum-duration floor on the segment itself, or a comprehension question in the segment that follows it.

What the participant sees
The PDF segment as a participant sees it.
The document in the browser’s PDF viewer — page thumbnails, zoom and print all come from the viewer, not from Carrier.
ParameterWhat it controls
PDF URLThe document source, used verbatim as the frame's address. Only http: and https: addresses are accepted; anything else renders the words “Invalid PDF URL.” to the participant, and an empty field renders “No PDF URL configured.”
Upload PDFSends a local file to Carrier and, on success, overwrites PDF URL with the returned same-origin path /uploads/pdfs/<uuid>.pdf. The experiment must be saved before uploading; the cap is 25 MB; the file's first bytes are checked, so a renamed non-PDF is refused. The stored name is a fresh random identifier, so the hosted address cannot be guessed from the original filename.
Display ModeStandalone (replaces chat) or Embedded (overlay on chat). Every control below this row appears only while the mode is Embedded.
Embedded LayoutInline (card in chat) or Split (content + side chat). The choice re-draws the rest of the panel, because the sizing controls differ between the two.
Start TriggerWhen the document appears during the parent chat: immediate, after N seconds, after N chat messages, or after the previous embedded sibling ends (with an optional delay in seconds; 0 fires at once).
Stop TriggerWhen it closes: on participant action — which, for a document with nothing to submit, means the Continue button — or a hard auto-dismiss after N seconds.
Completion BehaviorWhat happens once it finishes: dismiss, end chat segment (the way a read-then-stop flow is built), lock (stays on screen but inert), or minimize to a pill.
Show Continue button on the cardWhether the participant may dismiss the document themselves. Clearing it is what produces a persistent reference panel that stays beside the chat until the stop trigger fires or the parent chat ends. This checkbox exists because the Timing and Transition cards are hidden for embedded segments; it is the only way to reach that behaviour.
Content pane widthSplit layout only. The document pane's share of the row, 30–90% in steps of 5 (default 75); the docked chat column takes the remainder.
Card width · Card heightInline layout only. Width as a percentage of the chat area (40–100%, default 75) and height as a percentage of the visible chat viewport (20–90%, default 35). The 35% default is a very short window for a document — raise it substantially, or use the split layout instead.
Pin to top while activeInline layout only. Keeps the card fixed at the top of the chat while the segment runs, so it stays in view as messages scroll underneath; it unpins on completion.
Timing · TransitionTreated specially. A new standalone PDF segment is created user-paced — no duration, no warning time, transition mode manual — so participants read at their own speed and click Continue. When the segment is embedded, both cards disappear from the inspector entirely and are replaced by the Start / Stop trigger controls above.

The pdf type is embeddable, and is the only segment type whose factory defaults are embedded rather than standalone, and split rather than inline — the split-screen reader is treated as the normal way to use it, and the standalone document as the special case. Two gates apply. The split layout needs a real chat parent: the runtime looks for a live chat segment to dock, and if the PDF is not running under one it logs a warning and quietly falls back to the inline card. And embedding of any kind is abandoned below a 900 px viewport, where every embedded segment reverts to standalone rendering, so a participant on a phone reads the document full-width and never sees the chat beside it.

⚠ A PDF segment can fail without saying so

Nothing in this segment reports its own failure to the researcher. Three of its four common problems are invisible in the builder and only surface in a pilot run — or, worse, in the data:

  • A linked document may simply not appear. Most publishers, journals and news sites forbid being framed by another site, and a forbidden document renders as an empty frame with no error message — the participant sees blank space where the stimulus should be. The builder shows no preview of the participant frame, so this is undetectable until someone runs the segment. Upload the file instead of linking to it wherever the licence allows: a file Carrier hosts is served from Carrier's own origin and always displays.
  • The embedded/split defaults only arrive by one route. They are applied when the type is set from the segment inspector's Type dropdown. Dragging the PDF tile in from the Library seeds a generic configuration with no display mode at all, so a dragged PDF segment is standalone — and if it is then switched to Embedded, the layout it is given is inline, not split. Whichever route you take, confirm Display Mode and Embedded Layout read what you intended before saving.
  • Uploads fail on an unsaved experiment. The upload is attached to an experiment record, so a brand-new experiment must be saved once before the file picker will work; until then it answers “Please save the experiment before uploading a PDF.”
  • The segment stores nothing. There is no submission, no per-page record, no scroll or dwell trace inside the document — only the segment's start and end times. A PDF segment on its own cannot tell you whether a participant read the stimulus or clicked straight past it, so pair it with a minimum duration, a following comprehension item, or both.
🎞 Builder walkthrough — Building a split-screen reader beside a live chat

The researcher adds a pdf segment after a chat segment, uploads a local document so Carrier hosts it and the PDF URL field fills with the hosted path, sets Display Mode to “Embedded (overlay on chat)” and Embedded Layout to “Split (content + side chat)”, drags the content pane to 70%, and clears Show Continue button on the card so the document stays open beside the conversation until the chat itself ends.

2.10 The timer segment

A standalone timer segment clears the activity area and leaves one centred card on screen: a line of text — “Please wait...” unless the researcher changes it — a large minutes-and-seconds readout that ticks down once a second, and a horizontal bar that fills as the wait elapses. The usual segment shell remains around it: the progress indicator, the participant list and the chamber clock, subject to the chamber's own visibility settings. What is not there is a Continue button. The timer renderer emits no navigation controls at all, so there is nothing for a participant to press; they wait, and the segment ends itself. If they reload the page or drop and reconnect, the remaining time is recomputed from the server's record of when the segment started, so a refresh does not restart the wait.

A timer is a pacing instrument rather than a measurement one. It records nothing, accepts no input, and gives non-human participants nothing to do. Its uses are all variants of buying time on purpose: a short settle-in pause after matching, before a discussion begins; an enforced deliberation delay between reading a case and deciding on it; a synchronising beat while slower members of the chamber finish an earlier activity; an inter-trial interval between repeated measures. Two nearby instruments are often the better choice. If the requirement is “spend at least thirty seconds on this page”, a Min Duration on the page's own segment enforces that floor without also imposing a ceiling (§2.5). If the wait exists because the server is computing something, a process segment ends when the work finishes rather than when a clock runs out.

The type configuration panel offers six controls. Two of them reach the participant; the other four are documented here because they are visible in the builder, not because they do anything.

What the participant sees
The timer segment as a participant sees it.
A circular countdown with the waiting message above it. Nothing else is on screen; the segment ends itself when the clock runs out.
ParameterWhat it controls
Display MessageThe text above the countdown; defaults to “Please wait...”. It is inserted as trusted author markup, so HTML tags written here are rendered rather than shown literally. Unlike slide and instruction content, it is not variable-interpolated — a {{var.name}} token would appear on screen as typed.
Show countdownOn by default. Draws the ticking readout and the progress bar; unchecked, the card shows the message alone. It is a display switch only: the segment still expires at the same moment either way, and the chamber clock in the shell is a separate control.
Countdown FormatOffers Digital (00:00), Circular progress and Progress bar. Inert — config.countdownFormat is never read, and every participant sees the digital readout with a bar beneath it whichever option is stored.
End MessageIntended as the text shown once the countdown reaches zero. Inert — nothing reads it; the Display Message stays on screen until the segment advances.
Show “waiting for others” indicatorsInert. Whether the participant list appears is decided by the chamber's own setting together with the per-segment participant-list toggle, not by this box.
Allow participants to end earlyInert, and the most misleading of the four: it reads as permission to skip the wait. Early advance is governed by Allow early advance in the Transition panel — and on a standalone timer even that renders no button, because the timer emits no navigation.
Max Duration (Timing panel)
— shared field, load-bearing here
The wait itself, and the only thing that ends a standalone timer. Expiry advances the segment regardless of transition mode. Left empty the clock reads ∞ and the segment never ends.

2.10.1 Standalone and embedded

The timer is one of the embeddable types: its Display & Sharing card offers Standalone and Embedded (overlay on chat), and the default embedded layout is inline — a card in the message stream — with split available. It carries no gating beyond the general rules for embedded children: the segment must sit immediately after the chat it belongs to, and below 900 px of viewport width the runtime renders it standalone instead. The card is cosmetic, though. The embedded path draws the message and a number, but never starts the per-second countdown, so the figure on the card is a static one that does not move; and because choosing Embedded hides the whole Timing panel, the duration it is drawn from can no longer be edited in the inspector. An embedded pause should therefore take its timing from the embedded stop trigger (“auto-dismiss after N seconds”) and be read as a notice rather than a clock.

⚠ Two of the six controls work, and Max Duration is the only exit

The timer's configuration panel promises more than the segment delivers. Four of its controls — Countdown Format, End Message, Show “waiting for others” indicators and Allow participants to end early — are stored in the experiment but read by nothing at run time. Three consequences are worth planning around:

  • A standalone timer has no advance control of any kind. It renders neither a Continue button nor a sync-readiness button, so neither Allow participants to end early nor Allow early advance can give participants a way out. The segment ends when Max Duration expires and at no other moment.
  • A timer with no Max Duration strands the participant. The clock shows ∞, nothing expires and there is no button; the only remaining remedy is to end that participant's session from the dashboard (§6.3). Always set a duration before piloting.
  • A timer dragged in from the segment library arrives at five minutes with its transition mode set to Sync, inherited from the generic segment defaults rather than chosen for this type. Sync's readiness gate is unreachable here for the reason above, so set Max Duration deliberately and set the transition mode to Auto, which is what actually describes the behaviour.
🎞 Builder walkthrough — Adding a settle-in pause

The researcher drags a Timer segment into a chamber's timeline, sets its Display Message to “Take a moment to think before the discussion begins.”, replaces the inherited five-minute Max Duration with 20 seconds, and changes the transition mode from Sync to Auto — and the timeline card updates to show a 20-second auto-advancing wait.

2.11 The survey segment

A survey segment hands the main area over to a Survey.js form. The questionnaire arrives with its own chrome — pagination, Next and Previous buttons, required-field markers, inline validation messages and a Complete button — rendered inside the usual segment frame, with the progress indicator, countdown clock and participant list showing or hidden according to the chamber’s settings. Standalone, Carrier adds no advance button of its own: the Survey.js Complete button is the only way forward, and pressing it saves the answers and moves the timeline on in one motion.

It is the same survey engine that renders the experiment-level and chamber-level surveys, so the segment is not the place for a long battery that could simply be attached to the chamber. What the segment adds is position and consequence. Position, because it occupies an exact slot in the chamber’s inner timeline — a manipulation check wedged between two chat segments, a mood measure taken immediately before a deliberation rather than fifteen minutes earlier, a confidence rating collected the moment a task ends. Consequence, because it is the only survey surface that can end or divert the run on the strength of an answer, which makes it the natural home for screeners and exclusion criteria as well as for measures. Each answer is stored under a researcher-chosen key, so a survey segment is also the usual way of manufacturing a variable mid-run for later matching (§4.3) or prompt interpolation (§4.5).

What the participant sees
The survey segment as a participant sees it.
A Survey.js form rendered inside the segment, with its own Complete button. Question types and validation are Survey.js’s, not Carrier’s.
ParameterWhat it controls
Survey Definition The questionnaire itself. A visual question builder is mounted over the stored Survey.js JSON, with a two-button toggle in its header — Visual builder and Raw JSON — a draggable question list, an Add Question button and a link out to the SurveyJS Creator. Questions composed visually are written back as a single-page definition.
Type (per question) One of seven answer formats: Short Text, Long Text, Single Choice, Multiple Choice, Dropdown, Rating, Yes / No. This is a deliberate subset of Survey.js — matrices, panels, file upload and conditional question visibility exist in the underlying library but have no control in the visual builder, and are reachable only through Raw JSON or the SurveyJS Creator.
Question Title The wording the participant reads. The form treats it as mandatory: Add / Update refuses to save while it is empty.
Variable Name The key the answer is stored and referenced under — in the export, in chamberline assignment (§1.2), in matching constraints, in prompt interpolation, and in the Completion Conditions below. Left blank it is generated as q<timestamp>, which is unreadable in the data and unusable in every rule that would want to name it. The chosen key is echoed on the question card as a var: badge.
Required Marks the question isRequired, so Survey.js refuses to complete until it is answered. Once the question form is closed this is visible only as a Required badge on the question card.
Choices (one per line) · Min / Max Type-conditional answer options, shown and hidden as the Type dropdown changes: a one-option-per-line textarea for Single Choice, Multiple Choice and Dropdown; a numeric range for Rating, defaulting to 1–5 and bounded at 0–10, so a 0–100 scale is not reachable from this form.
Anonymous responses A checkbox stored on the segment. Nothing in the runtime, the export or the dashboard consults it — see the caution below.
Completion Conditions A collapsed accordion beneath the type card, offered for survey and attention-check segments only. Each rule pairs an If field — a question’s Variable Name, matched exactly — with an operator (=, , in, >, <) and a value, then a Message and an optional Redirect URL. The in operator reads its value as a comma-separated list. Rules are evaluated on completion, before the normal advance; the first match saves the submission, then ends the run with that message and redirect. This is how a screener excludes someone mid-study.
Data Sharing (and Results Display, Anonymous Results) Offered because the survey counts as an interactive type, but no results view is implemented for it — see the caution below.
Display Mode and its embedded controls Standalone or embedded. Choosing embedded reveals Start Trigger and Stop Trigger, Completion Behavior, Embedded Layout (inline or split), Show Continue button on the card, and the sizing controls for the chosen layout — Card width, Card height and Pin to top while active for inline, Content pane width for split.
Min Duration (Timing) Implemented differently here than anywhere else, because the segment has no advance button of its own to disable. Carrier instead takes over the Survey.js Complete button: it is disabled and its label rewritten to a live countdown — “Continue in 0:42” — reverting to the survey’s own completion text when the time expires. The button is re-found and re-disabled on every tick, since Survey.js rebuilds its navigation on each page change, and completion is refused outright until the minimum has elapsed in case a re-render lets a click through.
Max Duration and Transition Mode A new survey segment is created user-paced — no duration, no warning time, transition mode manual — but because the standalone renderer emits no navigation, that manual mode produces no visible Next button and advancement rests entirely on Survey.js. If a maximum duration is set and expires mid-form, the runtime blurs the focused field first (Survey.js text inputs only commit on blur), flushes the partial answers, and tags the saved record timedOut so abandoned forms are distinguishable from real submissions in the export.

The survey is embeddable, but defaults to standalone. Embedded, it mounts the same Survey.js model into a card inside the chat stream — inline by default, at 75% of the chat width and 35% of its height, which is cramped for anything longer than a question or two. The split layout gives the form the main area with the live chat docked beside it, but it needs a chat segment to dock into: mounted anywhere else it silently falls back to the inline card. Embedding as a whole is gated on viewport width — below 900 px the segment renders standalone regardless of the configured mode, so a mid-conversation manipulation check becomes a full-screen one on a phone. General embedding behaviour, start and stop triggers and completion behaviour are covered in §2.

⚠ Controls that do less than they appear to

The survey segment carries two inert controls, one behaviour that does not survive the move into a chat, and one lossy editor. None of them fails loudly, so each is worth checking before a study goes out:

  • Anonymous responses anonymises nothing. The checkbox writes a flag that no runtime, export or dashboard code reads; responses stay attributed to the participant exactly as they would be with it unticked. Anonymity has to come from the study design, not this box.
  • Data Sharing is inert for this type. The dropdown renders because the survey is classed as interactive alongside selection, ranking, input, task and social, but the code path that shows other participants’ submissions has no case for a survey and there is no survey results view to show. Setting it to Real-time or After all submit changes nothing a participant sees.
  • Completion Conditions are evaluated only on the standalone path. The embedded renderer builds its own completion handler, which advances on the minimum-duration gate alone and never consults the rules. A screener written into an embedded survey card will therefore never fire — keep exclusion rules on a standalone survey (or on an attention-check, which shares the same panel).
  • One further edge, on the definition rather than the run: the visual builder flattens every page’s elements into a single list, and rewrites the definition as one page when a question is added or edited. A multi-page questionnaire pasted through Raw JSON keeps its page breaks only until the first visual edit, and anything the parser cannot read — matrices, panels, conditional visibility — comes back as an empty question list rather than an error.
🎞 Builder walkthrough — Building a screener that ends the run

The researcher drags a Survey segment onto the chamber, adds a Single Choice question in the visual builder titled “Have you taken part in a study like this before?” with the variable name prior_exposure and the choices Yes and No, then opens the collapsed Completion Conditions accordion and writes one rule — if prior_exposure = Yes, end the session with a thank-you message and a redirect — and the panel settles with the question card and its exclusion criterion side by side.

2.12 The input segment

A participant meets an input segment as one question and one field to answer it in: a four-row textarea, a single-line text box, or a number field, carrying whatever placeholder text the researcher supplied. A hint line may sit underneath — “Value range: min: 0, max: 100” for a numeric field — and below that sits the ordinary Continue button. That button is live-gated: it enables and disables on every keystroke as the answer starts and stops satisfying the segment’s validation rules. It does so silently. There is no error message, no highlight on the field, and no explanation of what is wrong — only a button that will not respond.

The segment exists for the case where there is exactly one thing to ask: an open-ended justification for a choice just made, a one-line explanation, a numeric estimate, a willingness-to-pay figure. It is the lightweight alternative to a survey segment — one prompt, one answer, no form schema to author — and its answer is a first-class segment submission, readable by later segments and by trigger conditions as a variable (§4.2) and carried into the export as the segment’s recorded data (§6.5). Note that non-human participants can submit into an input segment as well as humans (§5.5), which the catalogue table in §2 and Appendix C both fail to record.

2.12.1 Parameters

What the participant sees
The input segment as a participant sees it.
A single prompt above a free-text box, with the character counter the length limit implies.
ParameterWhat it controls
Prompt The question shown above the field. A plain single-line text box: the text is injected into the participant’s page unescaped, so raw HTML passes through, but Markdown is not rendered and {{var.X}} tokens are not interpolated. Unlike the slide and instruction editors, this field has no variable picker.
Input Type Single line, Multi-line (the default), or Number. Chooses the field that is rendered and rebuilds the panel below to show either the length fields or the value fields. One asymmetry to know about: the builder writes textarea by default, but the runtime falls back to a single line when the key is absent altogether, so a hand-authored or imported configuration that omits it renders as a one-line box.
Placeholder Grey hint text inside the empty field. Injected unescaped into the HTML attribute, so a quotation mark inside it breaks the markup.
Min Length / Max Length Nominally a character floor and a character cap for text answers; hidden when Input Type is Number. Both are inert — see the warning below.
Min Value / Max Value Lower and upper bounds for a numeric answer, each blank meaning no bound. Shown only when Input Type is Number, and genuinely enforced: they set the field’s min and max attributes, gate the Continue button, and produce the “Value range” hint beneath the field.
Validation Pattern (regex) A regular expression the answer must match before Continue enables. Offered for all three input types and genuinely enforced, recompiled and tested on every keystroke. It is currently the only working way to require a minimum length: .{50,} in place of a Min Length of 50.
Validation Error Message The message intended to appear when validation fails. Inert — nothing in the participant runtime or the server reads it.
Anonymous input A legacy per-segment anonymity flag, retained only as a fallback for Anonymous Results when resolving the name beside a shared answer. It does nothing on its own; new configurations should use Anonymous Results.
Data Sharing Whether the other occupants of the chamber see this answer: Never (the default), after all have submitted, or in real time. Real time deserves a moment’s thought here, because the field autosaves: typing emits a submission after every 500 ms of quiet, so real-time sharing broadcasts half-finished sentences as they are composed rather than a considered final answer.
Results Display / Anonymous Results Revealed once Data Sharing is not Never. Anonymous Results works, replacing names with “Participant 1”, “Participant 2” and so on. Results Display does not: whichever of Aggregated or Individual is chosen, free text is always listed one entry per participant, because there is no meaningful aggregate of open-ended prose.
Timing & Transition Treated specially for this type. input is one of the user-paced segment types, so a new one is created with no duration, no warning time and a manual transition — it waits for the participant rather than the clock. Minimum duration is the one timing field that reaches the validation logic: it is ANDed into every enable/disable decision, so Continue stays disabled until the floor has elapsed even for an answer that is already valid.

2.12.2 Standalone and embedded

input runs either standalone, replacing the chat view for the duration of the segment, or embedded as a card laid over a live chat. The embedded renderer implements it properly — the validation, the button gating and the debounced autosave all keep working inside the card — and an embedded input is treated as a child of the nearest preceding chat segment. The default is standalone; when Embedded is chosen, the Timing and Transition cards disappear in favour of a start trigger and a stop trigger, and the default layout is inline (a card in the message stream) rather than split (a content pane beside a docked chat column). Two cautions are specific to this type: the inline card’s default height of 35 per cent of the chat viewport is tight once a prompt, a four-row textarea and a hint line are stacked inside it, and a time-based stop trigger will dismiss the card mid-sentence, discarding whatever had not yet autosaved. Below 900 px of viewport width, and for mediator and observer views, an embedded input falls back to standalone rendering.

⚠ Validation is silent, and half of it is inert

A participant whose answer fails validation sees no message and no highlight — only a Continue button that stays greyed out. That makes the three gaps below more consequential than they look in the panel, because none of them announces itself at run time:

  • Min Length and Max Length do nothing. The panel writes them to config.minLength and config.maxLength, while the runtime’s validator reads validation.minLength and validation.maxLength — keys the builder never writes. A Min Length of 50 therefore neither gates the button nor draws the “Minimum 50 characters” hint that the runtime is capable of showing. Express a length floor as a Validation Pattern instead (.{50,}). The task segment’s identically-labelled fields are wired up correctly, which makes the difference easy to miss.
  • Validation Error Message is never displayed. Any rule enforced by a Validation Pattern must therefore be stated in the Prompt text as well — “at least two sentences”, “digits only” — or the participant is left guessing why the button will not move.
  • A pattern that matches the empty string will not stop a blank answer. The expression is tested against the field’s current contents, empty included, so an unanchored or *-quantified pattern enables Continue on an untouched field. Anchor it and require at least one character (+, or {n,}). Because the expression is recompiled on every keystroke, a malformed pattern also throws inside the typing handler rather than being reported anywhere in the builder.
🎞 Builder walkthrough — Configuring a validated free-text answer

The researcher adds an input segment, types the question into Prompt, sets a Placeholder and a Validation Pattern of .{50,} in place of the inert Min Length field, then switches Input Type to Number and the two length fields give way to Min Value and Max Value.

2.13 The selection segment

A selection segment puts a single question on the screen and asks every participant in the chamber to answer it. The question is rendered from markdown at the top of the segment area; beneath it sits either a list of options — radio buttons in single mode, tick boxes in multiple mode, with a “Select 1-3 options” hint under the list when more than one answer is allowed — or, in slider mode, a track with an anchor label at each end and a large live readout of the current value. The Continue button below starts disabled and only becomes clickable once the number of ticked options falls inside the Min/Max range (and any minimum duration has elapsed); clicking it before then flashes the option list red rather than advancing. A slider is the exception: it always reports a value, so its Continue is live from the first paint.

It is the segment to reach for whenever a design needs a discrete answer on the record: a vote at the end of a deliberation, a forced choice between two framings, a manipulation check, or — in slider mode — a Likert or visual-analogue rating. Its answers are not merely logged. Every submission is readable downstream as segment-submission:<segId>:selections, segment-submission:<segId>:optionValues.0 or segment-submission:<segId>:sliderValue, so a choice made here can steer a later chamber, feed an aggregate, or interpolate into an instruction (§4). It is also one of only two types — with ranking — for which a scripted or LLM participant can be configured to submit a confederate answer alongside the humans (§5).

2.13.1 Parameters

What the participant sees
The selection segment as a participant sees it.
The question and its options as radio buttons. Continue stays disabled until a choice is made.
ParameterWhat it controls
Question / PromptThe question shown above the options. Rendered as markdown at run time, so **bold**, links and ![image](url) all work; the Preview button beside the box shows the rendered result without leaving the builder.
Selection ModeSingle choice (radio buttons), Multiple choices (tick boxes) or Slider (continuous scale). The mode decides which block of fields below is shown, and which of the two answer shapes the segment records.
Options (one per line)The choice list, one option per line. Appending | <value> to a line attaches a numeric or string value to that option, which is recorded as optionValues alongside the chosen indices — the way to make a five-point scale sum or average without a lookup table elsewhere.
Min Selections · Max SelectionsThe floor and ceiling on how many options may be ticked before Continue enables. Both default to 1. Hidden in slider mode.
Min Value · Max Value · Step Size · Default ValueSlider bounds, increment and starting position (0, 100, 1 and 50 by default). Step accepts fractions, so a 0–1 continuous scale is expressible.
Left Label · Right LabelAnchor text at each end of the track, e.g. “Strongly Disagree” and “Strongly Agree”. Left blank, the numeric bound is shown instead.
Show current valueWhether the live numeric readout appears above the slider. On by default; turning it off gives a true visual-analogue scale, where participants cannot anchor on the number.
Anonymous votingA legacy anonymity flag, superseded by Anonymous Results. The runtime consults it only as a fallback when Anonymous Results is unset.
Allow changing voteNominally whether an answer may be revised. Nothing reads it: a participant can always change a selection until Continue is pressed.
Data SharingNever (the default), After all submit (results revealed once everyone in the chamber has answered) or Real-time (results update live as answers arrive). Option modes then draw a horizontal bar chart of the distribution; slider mode draws a responses/average/min/max row with an average marker on the track.
Results DisplayAggregated (distribution or averages) versus Individual (each participant's answer listed separately). Shown only when Data Sharing is not Never.
Anonymous ResultsReplaces names with “Participant 1”, “Participant 2” in shared results. Shown only when Data Sharing is not Never, and the field the runtime actually reads.
Display ModeStandalone (replaces the chat) or Embedded (a card overlaid on a running chat). Choosing Embedded removes the Timing and Transition cards from the panel altogether and adds a Visibility Conditions accordion, which shows the card only to participants matching the listed property conditions.
Start Trigger · Stop Trigger · Completion BehaviorEmbedded only: when the card appears (immediately, after N seconds, after N chat messages, or a delay after the previous child ends), when it closes (on submission, or a hard auto-dismiss timeout), and what happens afterwards (dismiss, end the parent chat, lock, or minimise to a pill).
Embedded Layout · Card width · Card height · Pin to top while active · Content pane widthEmbedded only: Inline renders a card in the message stream, sized as a percentage of the chat area and optionally pinned to the top so it stays in view as messages scroll; Split puts the poll in a content pane beside a docked chat column, sized by pane width alone.
Show Continue button on the cardEmbedded only. It is a proxy for the transition settings the hidden Transition card would otherwise hold, and it is the only control that lets a participant close the card deliberately once they have answered — sensible to disable on a read-only reference panel, never on a poll.

Timing and transition are shared with every other segment, but a new selection is created user-paced rather than timed: no duration, no warning time, and transition mode manual, so it waits for the participant instead of running a clock. Setting a duration turns it into a timed poll; setting mode sync holds the whole chamber until everyone has answered.

2.13.2 Standalone and embedded

Selection renders in both display modes, and embedding it is the canonical use of the embedded mechanism: a poll that overlays a live conversation lets a group vote without leaving the discussion, and a chat whose transition mode is embedded-complete ends exactly when the vote is in. The default is Standalone, and the default embedded layout is Inline. The gating is positional rather than a setting: an embedded segment becomes the child of the nearest preceding chat segment in the same chamber, so a segment that is not preceded by a chat has nothing to overlay. Two run-time conditions force a fallback to standalone rendering regardless of the configuration — a viewport narrower than 900 px, and any mediator or dashboard observer, who sees the mediator view rather than the interactive widget.

⚠ Five traps worth checking before piloting

Most of these are silent: the segment saves, renders, and collects data, but not the data the configuration appears to promise.

  • Switching to Multiple choices does not raise Max Selections. It stays at its default of 1, so participants get tick boxes but Continue disables again the moment a second box is ticked — a multi-select poll that can only ever record one answer. Raise it explicitly whenever the mode changes.
  • A submission is emitted on every click, not on Continue. Under Real-time sharing the rest of the chamber sees provisional answers as they are being weighed; under After all submit the reveal fires once each participant has clicked once, which may be well before anyone has settled. Downstream variables track the same provisional values and settle on the last click before the segment ends.
  • A slider has no untouched state. The handle starts at Default Value and reports it immediately, so a participant who never touches the slider still records that number as an answer, indistinguishable from a deliberate one.
  • The prompt does not interpolate variables. {{var.X}} is substituted only in instruction and slide content; a token written into a selection prompt reaches the participant as literal text. Put the variable in a preceding instruction segment instead.
  • Two settings are inert. Allow changing vote is read by nothing, and Anonymous voting is only a fallback for Anonymous Results. Anonymity should be set with Anonymous Results, which requires Data Sharing to be enabled to appear at all.
🎞 Builder walkthrough — Configuring a multi-select poll

The researcher adds a selection segment to a chamber, writes the question, switches Selection Mode from “Single choice” to “Multiple choices”, types three options with per-option values, and raises Max Selections from 1 to 3 so that more than one box can actually be ticked.

2.14 The ranking segment

A ranking segment shows a prompt line and, beneath it, a vertical list of the items to be ordered. Each row carries a grip handle on the left, its current position number, and the item's label. Rows are reordered by dragging them past one another; the position numbers renumber the moment a row is dropped, and every drop records the new order. Below the list sits the standard Continue button. There is no keyboard or button-based alternative to dragging at runtime — the up/down arrows that appear in the builder's playtest preview are a preview affordance only — so a ranking segment assumes a pointer.

Researchers reach for it in three situations: eliciting a preference order over a fixed set of alternatives, asking a group to set priorities, and recording a collective ordering at the end of a deliberation. The third is the common case, and it is usually configured as an embedded card over a chat so the group can rank while still talking. Like selection, a ranking's output is readable downstream as a segment-submission variable (§4.2), and a scripted or LLM participant can be configured to submit an ordering alongside the humans (§5.5).

2.14.1 Parameters

What the participant sees
The ranking segment as a participant sees it.
The items as a draggable, numbered list. Participants reorder them directly; the numbers update as they drag.
ParameterWhat it controls
InstructionThe prompt line above the list. A plain single-line text field — no markdown, no variable interpolation, and no Preview button. It is honoured only in embedded display; see the caveat below.
Items (one per line)The list to be ranked, one item per line. Appending | <value> attaches a numeric or string value to an item, and submissions then expose rankedValues in rank order — so segment-submission:<segId>:rankedValues.0 reads the value of whatever the participant put first. The typed line order is the order every participant is shown; nothing is shuffled.
Ranking ModeNominally Individual versus Collaborative (a shared ranking). Inert — see the caveat below.
Lock ranking after submitNominally freezes the order once submitted. On by default, and inert — see the caveat below.
Data Sharing, Results Display, Anonymous ResultsWhether participants see one another's orderings — Never (the default), After all submit, or Real-time — and, once sharing is on, whether the reveal is Aggregated (an “Average Rankings” list giving each item's mean rank, best first) or Individual (each participant's ordering listed in full), with names optionally replaced by “Participant 1”, “Participant 2” and so on.
Display ModeStandalone (replaces the chat view) or Embedded (an overlay on an active chat). Standalone is the default. Choosing Embedded hides the Timing and Transition cards and reveals the embedded controls below, plus the Visibility Conditions accordion.
Start Trigger, Stop Trigger, Completion BehaviorEmbedded only. When the card appears (immediately, after N seconds, after N chat messages, or chained after the previous embedded sibling), when it closes (on the participant's submission, or a hard timeout), and what happens afterwards — dismiss, end the parent chat, lock, or minimise to a pill.
Embedded LayoutEmbedded only. Inline (a card in the chat stream, the default) or Split (the list fills the main pane with the live chat docked beside it). Split is worth preferring for ranking specifically: a list of more than a handful of items is awkward to drag inside a short inline card.
Card width, Card height, Pin to top while activeInline layout only. Card width as a percentage of the chat area (40–100%, default 75) and content height as a percentage of the chat viewport (20–90%, default 35), plus whether the card stays fixed at the top as messages scroll. The card's content area scrolls, so a long list stays reachable — but the drag target shrinks with it, which is the practical argument for raising the height beyond about five items.
Content pane widthSplit layout only. The content pane's share of the row, 30–90%; the docked chat column takes the remainder.
Show Continue button on the cardEmbedded only. Whether the card carries its own Continue button. It must stay on for a ranking card, because there is no other way for the participant to commit the order.
Timing and TransitionRanking is a user-paced type: a new ranking segment is created with unlimited duration, no warning time, and a manual transition, rather than the timed defaults most segment types receive. A minDuration, if set, disables the Continue button until it elapses. Both cards are hidden when Display Mode is Embedded, where the start and stop triggers take over.

2.14.2 Display modes

Ranking is one of the embeddable types. It defaults to standalone with an inline embedded layout, and the builder places no restriction on where an embedded ranking may sit — at runtime it is treated as a child of the nearest preceding chat segment in the chamber, so an embedded ranking with no chat before it has no parent to overlay. Two conditions force it back to standalone regardless of configuration: a viewport narrower than 900 px, and a viewer watching as a mediator or observer. Shared results appear beneath the list in either mode.

⚠ What the ranking panel does not do

The ranking panel is older than most of its neighbours. Three of its fields never reach the runtime, and two absences in the runtime itself change what the recorded ordering means. All five are cheap to work around at design time and expensive to discover after data collection.

  • Instruction is dropped in standalone display. The standalone renderer reads only a prompt field and falls back to the generic “Drag to rank items:”; only the embedded renderer falls back to Instruction. A standalone ranking therefore shows the generic line no matter what is typed, and the wording that defines the task — “rank by importance” versus “rank by likelihood” — never reaches the participant. Until this is fixed, put the framing in an instruction or slide segment immediately before, or run the ranking embedded.
  • Ranking Mode and Lock ranking after submit are inert. Neither is read by the runtime or the server; both survive only in the builder's own prompt text. Selecting Collaborative does not produce a jointly edited list — every participant still ranks alone. The closest available approximation to a group ordering is Individual with Data Sharing set to Real-time, so each participant ranks for themselves while watching the running average.
  • Nothing gates the Continue button. A ranking never blocks advance, so a participant who drags nothing still submits — and what is submitted is the item order exactly as it was typed into Items. In the export an untouched list and a deliberate endorsement of the starting order are identical. Two consequences follow: the typed order is an anchor, not a neutral starting point, so vary it across conditions if the ordering itself is the measure; and to distinguish real responses from inaction, count only participants who produced at least one reorder event.
  • Aggregated results are strict about length. The “Average Rankings” view discards any submission whose ordering does not have exactly one entry per item, and renders nothing at all if no submission qualifies — so an empty results panel after a reveal means no usable submissions, not zero participants.
🎞 Builder walkthrough — Ranking items over a live chat

The researcher drags a Ranking segment onto the chamber timeline beneath a chat, types an instruction and three items carrying per-item values, then switches the display mode to “Embedded (overlay on chat)” and the embedded layout to “Split” — the Timing and Transition cards give way to the embedded start, stop and pane-width controls, and the timeline re-draws the ranking as an indented child of the chat above it.

2.15 The chat segment

A chat segment fills the participant's screen with one scrolling message stream and, beneath it, a text box that grows as it is filled and a paper-plane send button. Around that sits the usual segment shell — segment progress, the countdown clock and the participant list in the left sidebar, unless the chamber or this segment turns them off. Every other occupant of the chamber writes into the same stream, and nothing in the interface distinguishes them by kind: another human, an LLM chatbot, a scripted chatbot and an agent all produce ordinary message bubbles, complete with typing indicators that appear and clear on their own. There is one room, everyone is in it, and there is no channel to switch to. Participants holding a non-communicator role see a different screen entirely — a processor gets a read-only observation column beside its draft panel, a mediator gets the broadcast console — described in §3.1.

This is the segment used for deliberation, negotiation, dyadic advice-giving, interviewing, and any design where the dependent measure is what people say to one another. It is also the surface most of the trigger system watches: keyword, message-count, sequence and after-bot-message triggers (§5.1) are all evaluated against a chat segment's message stream, so a chamber's scripted behaviour usually lives or dies here. Finally, it is the host container for embedded child segments — the way a vote or a ranking can be put to a group mid-conversation without taking the conversation off screen.

2.15.1 Type configuration

What the participant sees
The chat segment as a participant sees it.
The conversation view: message stream, composer, and the participant list in the sidebar. Here there is one occupant and no messages yet.
ParameterWhat it controls
Communication Channel Text, Audio, Video, or None (bots/agents only). In practice only None changes anything: it disables the composer, replaces its placeholder with “Chat is disabled in this segment”, and is enforced again on the server, which rejects human messages for that segment. Choosing Audio or Video here has no effect — media capture is initialised once per chatroom from the chamber's channel, not the segment's.
Max Messages Intended as a per-segment message cap; help text reads “0 or empty = unlimited”. Recorded in the configuration and shown in the segment inspector, but never enforced at runtime (see the caveat below). The chamber-level message cap is a separate setting.
Show typing indicator
Agent Think Delay Min / Max (ms)
The checkbox is a disclosure control: unticking it hides the two indented delay fields, which are described in the builder as a legacy fallback for per-chatbot thinking delays. None of the three is read at runtime — typing indicators are always shown, and a non-human participant's pause before replying comes from its own thinking-delay setting.
Show message history from previous segments Nominally carries the conversation forward from earlier segments of the same chamber. Not consulted by the participant client; what the stream shows is unaffected either way.
Show participant list Live, and one of the few segment-level overrides of the shared shell. The sidebar list appears only when the chamber's own participant-list setting and this checkbox are both on; turning it off here can change the shell from a sidebar to a floating countdown pill. Useful for hiding group composition during a manipulation while leaving it visible elsewhere in the chamber.
Enable emoji reactions
Enable message reporting
Add, respectively, a reaction picker and a report flag to the hover action row on message bubbles. Reporting is deliberately not offered on a participant's own messages. The action row is drawn only if at least one of the two is on, so leaving both off keeps bubbles completely inert.
Enable behavior tracking Records browser-level behaviour for the duration of the segment — tab switches, scrolling, mouse movement, typing patterns and focus changes — and writes it to the experiment export. This is one of the few per-event process-tracing streams Carrier produces, so it is worth turning on wherever attention or hesitation is of interest; it costs nothing visible to the participant.
Control Events A chat-only list of rules that disable or re-enable the composer, or force the segment to end, when a condition is met in the conversation. Added through the + button; detailed in 2.15.2 below.
Transition Mode → Embedded complete The shared Transition card gains a fifth mode for chat: the chat ends when all of its embedded children have finished. The option is offered only while the segment immediately after this chat is an embedded child — any non-embedded segment in between removes it. General transition behaviour is in §2.5.
Fallback timeout (seconds) Appears under the transition mode only when that mode is Embedded complete. A safety net: if the embedded chain stalls — a message-count start trigger that never fires, a child hidden from every participant by a visibility condition — the chat advances anyway after this many seconds. Left empty, the chat waits indefinitely.

2.15.2 Control events

Control events are the one part of the chat panel with no equivalent elsewhere in the builder, and the reason a chat can be made to close itself on the state of the conversation rather than on the clock. Each event pairs an action with a condition; the modal opened by the + button shows only the sections the chosen action needs.

ParameterWhat it controls
Action Disable Input, Enable Input, or End Section (force advance). This choice governs the rest of the modal: the two input actions reveal Target and the optional agent-script block; End Section reveals Activation and the timeout pair.
Target Type
Participant Name
Input actions only. Either all human participants, or one named participant — the name list is drawn from the chamber's own participant slots, so an empty chamber offers nothing to select.
Activate On End Section only: when the rule starts watching — at segment start, after a delay in milliseconds, when a named agent trigger fires, or on a matching message. Each choice reveals its own field group; Message Match adds a Message Source of Any, Human, Agent or a specific participant.
Condition Type Always shown: Message Count (with a From filter of anyone, human, agent, or a named participant), Participant Message (a chosen participant sends anything), Agent Trigger (a trigger id fires), Any Response, or All Humans Responded. This is what actually fires the action.
Trigger ID
Select from chamber triggers
Wherever a rule refers to an agent trigger, the identifier is free text and the dropdown beside it is a convenience that writes the selected id into that box. The picker appears in up to three sections of the same modal, so it is worth noting which one is being filled; typed identifiers are not checked against the chamber.
Placeholder text when input is disabled Disable Input only. The message shown in the composer while it is locked; defaults to “Please wait...”.
Trigger Agent Script (Optional) Input actions only. Fires a named agent trigger alongside the enable/disable, after its own delay in milliseconds — the usual way to have a chatbot explain why the box has just locked. Leaving the trigger id empty skips it.
Timeout (ms)
On Timeout
End Section only. How long to wait for the condition before giving up, with 0 meaning wait indefinitely, and what to do when that time passes: force the section to end, or let the rule expire quietly.

2.15.3 Display

The chat segment is the only type with no Display & Sharing card at all — it has neither a display-mode dropdown nor a data-sharing dropdown, because it is never the thing being displayed on top of something else. A chat is always standalone, and the participant client refuses to render it in embedded mode even if a configuration asks for one. The relationship runs the other way: a chat is the background that embedded children are drawn over, either as an inline card appended to the message stream or, with the child's embedded layout set to split, in a pane beside a docked chat column (§2). Below a viewport width of 900 pixels every embedded child falls back to standalone, which means that on a narrow screen the child replaces the chat instead of overlaying it — worth knowing before recruiting participants on mobile devices. One further chat-only detail has no control in the builder: a prompt value set on the segment through an imported configuration renders as a standing banner above the message stream.

⚠ Four controls that record but do not act

The chat panel is older than most of the builder, and four of its controls still save a value that nothing at runtime reads. They are worth knowing by name, because each one looks like a manipulation and none of them is:

  • Max Messages — no cap is applied. A study that needs a fixed number of turns has to reach the same end by other means: a control event with condition Message Count and action End Section, which does work.
  • Show typing indicator — indicators are always on. The checkbox's only real effect is showing and hiding the two fields below it, so it cannot be used as a presence manipulation.
  • Agent Think Delay Min / Max — not consulted, despite the “legacy fallback” help text. Response latency is set per chatbot in that participant's own configuration, and a delay typed here will not change it.
  • Show message history from previous segments — does not gate what the stream shows.

Two adjacent settings do behave as labelled and are easy to confuse with the above: Show participant list genuinely hides the sidebar roster, and a chat left on Embedded complete whose children have since been deleted or moved will never advance — the builder prints an amber warning under the transition dropdown when that happens, and a Fallback timeout is the insurance against it.

🎞 Builder walkthrough — Ending a chat when everyone has spoken

The researcher opens a chat segment's configuration, turns on behaviour tracking, adds a control event whose action is “End Section” with the condition “All Humans Responded” and a sixty-second timeout, and the new rule appears in the segment's Control Events list.

2.16 The social feed segment

The social segment renders a mock social-media platform — a feed of researcher-authored posts with photos, captions, like counts, and seeded “crowd” comments. The platform takes over the entire screen for the duration of the segment: participants see only the mock platform's own chrome (its top bar and feed), not the Carrier segment UI, for a fully immersive simulation. Participants browse the feed and can comment, reply, like or vote on posts and comments, follow authors, and click decorative share/save buttons.

Three layouts are available per segment:

  • Instagram-like — media-dominant cards, double-tap to like, caption below the image, collapsed comments. Replies nest one level.
  • Facebook-like — caption-first cards, a labeled Like · Comment · Share bar, bubble comments. Replies nest one level.
  • Reddit-like — a forum layout: an up/down vote gutter with a running score on every post and comment, title-led posts (a post can be an image post, a text post, or a link post), optional community and flair labels, and comment threads that nest to any depth. Indentation re-bases every five levels behind a “Continue this thread →” link, so a deep argument stays readable.
⚠ Layout is not presentation-only

It is tempting to use layout as a between-subjects condition. Be aware that the three layouts do not record the same data and do not display the same stimulus, so a layout factor is confounded with both the response set and the content participants see:

  • The reddit layout records a dependent variable — vote scores, in the _votes variable and as vote_post / vote_comment entries in _actionLog — that is structurally empty in the other two layouts, which have no vote control at all. An empty _votes there means “never offered”, not “chose not to vote”.
  • The reddit layout can carry stimulus text the other layouts never display: title, community, flair, link posts (kind, linkUrl) and a seeded score. The same feed therefore presents different content in different arms. The Feed Builder warns you when a feed uses these fields on a non-reddit layout — it does not block the save, so layout stays swappable. Comment depth is the one exception: if your seeded comments nest more than one level deep, the Instagram and Facebook layouts cannot render them and the save is refused until the threads are flattened. There is now exactly one place to change layout — the control above the preview in the Feed Builder — so every layout change goes through that validation. (Before the Feed Builder was rebuilt, the segment card carried a second Layout dropdown that wrote straight through without re-validating; feeds authored that way may still hold deep threads on a shallow layout, and opening the Feed Builder and saving will surface it.)
  • Instagram and Facebook were never equivalent either, long before the Reddit layout existed. A like made by double-tap (like_post with via: "double_tap") is Instagram only, and save_post / unsave_post are missing from Facebook (they exist on Instagram and Reddit). So even an Instagram-vs-Facebook study is confounded: two logged actions are simply unavailable in the Facebook arm. Do not treat double-taps or saves as matched measures across those two.
  • To use layout as a clean condition, leave the reddit-only post fields empty and restrict your analysed measures to what every arm affords — comments, replies, likes, follows and dwell.
What the participant sees
The social feed segment as a participant sees it.
The feed takes the whole screen — the platform’s own chrome replaces Carrier’s, and the remaining time shows as a pill in its top bar.
ParameterWhat it controls
Feed Builder → LayoutChosen above the live preview, so the change is visible as you make it. One of Instagram-like (media-first, double-tap like), Facebook-like (caption-first, labeled action bar) or Reddit-like (forum threads, vote scores). It sets the platform’s chrome and card design, whether a vote gutter exists at all, and how deep comment threads may nest — one level on instagram and facebook, unbounded on reddit, where indentation re-bases every five levels behind a “Continue this thread →” link. It is therefore a capability switch, not a skin: see the caveat below.
Feed Builder → Feed settingsParticipant handleThe username the participant appears to comment under; defaults to you. Accepts {{variable}} interpolation against the participant’s properties (§4.5), which is how a feed shows a per-condition or per-participant identity. A token that resolves to nothing is left on screen literally, so a mistyped name is visible rather than silent.
Segment inspectorRead-only, and deliberately so: layout, handle and the post and account editors all live in the Feed Builder, and repeating them here was two places to change one setting. It shows a summary card (layout, counts of posts, accounts, seed comments and live agents), an arrival strip plotting each post on the segment’s own clock with an amber marker at the end, the readiness checklist, and the managed variables the segment will record. Readiness is a ✓/⚠ list: agent accounts exist and are bound, each bound agent has a trigger that actually responds, an API key is stored for each LLM provider in use, and — the check most easily missed — the segment has a duration and no post is scheduled at or after it. A post arriving after the end is authored, exported and never seen. The unbound-account row carries an inline Set up an agent account button that opens the Feed Builder with the agent wizard already running.
Open Feed BuilderOpens the editor where the feed is authored, in three tabs. General is the workspace: the feed outline on the left (posts on an arrival timeline, then the cast of accounts beneath, each pane scrolling on its own so a long feed never buries the cast), a contextual editor in the middle for whatever is selected, and a live participant preview on the right rendered in the participant’s own stylesheet. Agents and Feed are detail tabs for the work a column beside a preview is the wrong shape for — see their rows below. A worked walkthrough of all three is in Appendix E.
Post → MediaImagesA collapsible library of uploaded images (PNG, JPEG, WebP, GIF) shared across the experiment, with multi-select upload, drag-and-drop and per-image delete. The server sanitises and de-duplicates filenames and the confirmation reports the stored name — that name, not the local one, is what a CSV must reference. Deleting an image a post still uses asks for confirmation.
Add postAppends an empty post with a generated postId (p1, p2, …). The id is the identity used by agent scopes and by CSV rows and is not editable in the interface; renumbering means exporting, editing and re-importing.
DataImport JSON / CSV…, Export JSON, Export CSV, CSV help, Starter kit, Template JSONThe file actions, gathered under Data in the Feed Builder header. CSV is a flat, spreadsheet-friendly form of the same feed: accounts are derived from author handles and existing agent bindings survive a re-import, so content can be rewritten in a spreadsheet without losing the agent wiring. Import blocks on a malformed file or a structural validation error, but not on binding problems or layout warnings, which are left to be fixed in the interface. The starter kit bundles a worked CSV, a plain-language guide and sample images; Template JSON downloads a worked configuration to bulk-edit from.
Agents tabOne agent account at a time, in full: its persona prompt, provider and model, temperature, max tokens, context window, whether it answers every comment or waits for a pause, its reply delay and its post scope. These fields belong to the chamber agent, not to the feed — this is the same agent card that sits on the builder canvas, so the edits apply immediately rather than waiting for the feed’s Save, and there is one copy of the persona rather than two to reconcile. An account bound to nothing shows the bind control instead of a form full of dead fields; an account bound to a scripted-rules agent says it has no persona to tune and points at the trigger editor rather than inventing an empty prompt for it.
Feed tabEvery post at once: a sortable table (id, author, caption, arrival, likes, replies) with row selection and bulk Retime, Reassign author and Delete — deleting also drops the post from any agent’s scope, so no agent is left scoped to a post that no longer exists. Above it, arrival on a real axis in three bands: a backlog post at −2h and a post at +45s cannot share one linear scale without collapsing the segment itself to a sliver, so the backlog and the overflow each get a compressed band and the live window keeps the width. The amber band is posts scheduled at or after the segment ends. A right-hand rail lists the managed variables a run will produce, with _votes struck through whenever the layout cannot collect it.
Outline → filterMatches post id, caption and author, and reports “N of M” while active. It never renumbers anything, so it is safe to filter and then act on what is shown. Past eight distinct arrival times the timeline stops printing a header per time — a hundred posts at a hundred times would be a hundred single-post headers — and groups into Before entry · On entry · After entry, moving each post’s own offset onto its row.
Post → Author · CaptionThe authoring account (chosen from the feed’s accounts — a post cannot be attributed to an ad-hoc handle) and the post body, which is required. Both accept {{variable}}.
Post → Title, Community, Flair, Score, Kind, Link URLThe forum fields: a headline above the body, a community label such as r/science, a flair chip, and a starting vote score that may be negative (left blank it falls back to Base likes rather than to zero). Kind selects image, text or link; text and link suppress the image in every layout, and link without a Link URL fails validation. The instagram and facebook layouts display none of these fields, which is what makes a feed that uses them layout-specific.
Post → ImageA source (preset, url, dataUri, and library once images have been uploaded) paired with a value control that changes shape to match — a preset picker, a library picker, or a free-text field. A library choice is stored as an ordinary URL, so it reads back as library only while the file is still in the library. An unknown preset name is a validation error.
Post → Base likes · Time labelDisplay only. Base likes is the starting count under the post; a participant’s own like adds one on their screen alone and is never shared with anyone else in the chamber. Time label is the cosmetic age (“3h”) and has nothing to do with when the post appears.
Post → Appears · OffsetWhen the post enters the feed, authored as a direction and a value in ms, seconds, minutes or hours rather than as raw milliseconds. Before entry places it in the backlog paged in by scrolling, At entry means already present, and After entry makes it arrive later behind the “new posts” pill. What is stored is unchanged — a signed whole-millisecond showAt, which is also the sort key for Feed order — and the field prints the stored value beneath it.
Post → ThreadThe pre-written comments, edited as the thread they describe: each carries an author account, text, a like count and its own offset on the same clock, and Reply inserts a child beneath one. A seed comment never appears before its post, whatever its offset. Nesting depth is the one hard constraint the layouts impose, and the interface enforces it rather than letting the validator catch it: on instagram and facebook the Reply button is absent on a comment that is itself a reply. An empty comment is a validation error, so a comment added and left blank blocks the save.
Account → handle · kindThe visible username — duplicates are a validation error, and the avatar colour is derived from the string, so renaming an account recolours it — and whether the account is crowd (decorative; it authors posts and seed comments and does nothing at run time) or agent (live, and able to reply). Choosing crowd hides everything below.
Account → Replies as and New agentBinds the account to one of this chamber’s agents by display name; only chamber agents are listed, and one agent may hold only one account — an agent already bound elsewhere is offered disabled, naming the account holding it, rather than being accepted and refused at save. An agent account left unbound is a validation error, so the feed will not save and the experiment will not deploy until it is bound or set back to crowd. The New agent button beside it runs a guided wizard that creates the agent — LLM persona or scripted keyword rules, with provider, model and personality prompt — adds it to the chamber and binds it in one pass. Participants never see the agent’s name, only the account handle.
Account → Responds onAll posts (whole feed) or Chosen posts…, which turns the feed timeline itself into a checklist — the posts are ticked where they are authored, so post ids never have to be matched by hand. All and None act on whatever the filter is showing, and Chosen only narrows the list to what has been ticked. A scoped agent never receives comments made elsewhere in the feed, and an LLM agent additionally gets a scope-aware identity block in its system prompt naming its handle and quoting its posts, so several agents can run as distinct personas owning different parts of the feed. Choosing Specific posts and ticking nothing is a validation error.
Account → Reply delay (ms) min / maxThe reply is generated as soon as the comment arrives but held back for a random interval in this range, so it reads as human rather than instantaneous. Both blank means reply as soon as ready; the range must be whole milliseconds with min no greater than max, and a delay on a crowd account is rejected.
Feed settingsFeed orderNewest first, Oldest first (both sorting on Show at) or Manual, which preserves the order of the Posts list. Exposed only inside the Feed Builder.
Feed settingsAllow likes, Allow replies, Show share/save, Collapse seed commentsFour affordance switches, all on unless turned off. Turning off Allow replies makes the feed a pure exposure stimulus and leaves _comments and _commentCount empty by construction. Collapse seed comments decides whether seeded comments start folded behind a “View comments” toggle, as a real feed would, or fully expanded — a real manipulation of what participants read without an extra click, not a cosmetic one. Note that facebook has no save affordance at all, whatever Show share/save says.
Data SharingOffered, because social counts as an interactive type, but a poor fit: the segment submits seven evolving JSON collections rather than one comparable response, and nothing in the feed renders another participant’s state. Leave it at Never.
🎞 Builder walkthrough — Adding a social feed to a chamber

The researcher opens the chamber, finds Social Feed in the Library, drags it onto the chamber timeline, and opens the Feed Builder to author posts and accounts.

Note: this recording predates the Feed Builder rebuild and shows the older four-tab editor and a Layout dropdown on the segment card. The path into the segment is unchanged; for the current editor see Appendix E.

Progression is time-based: new social segments default to a 3-minute auto-advance timer (adjust the duration in the segment's Timing panel), and the remaining time appears as a small pill in the platform's top bar that turns amber as the end approaches. If you configure a manual transition instead, a subtle “Continue →” button appears in the platform chrome.

The feed connects to the agent system through account binding: each account in the feed is either crowd (decorative — it authors posts and seeded comments only) or agent — bound to one of the chamber's non-human participants by display name. When the participant comments, the comment travels through the same pipeline as chat messages, so every trigger type and LLM configuration works unchanged; the bound agent's reply appears in the feed as its account, threaded under the comment that triggered it. At segment start the platform writes one hidden context message per post into the conversation history, so LLM agents know what the posts say without any extra configuration.

Each agent account also has a scope: by default it responds across the whole feed, or you can scope it to specific posts by ticking them on the Feed Builder’s own timeline. Scoped agents only react to comments on their posts — a comment elsewhere never reaches them — and unbound agents stay silent in social segments. LLM agents additionally receive a scope-aware identity block in their system prompt (“you are commenting as sona.fields; you only take part in these posts: …” with the posts' captions and seeded comments), so their replies stay in character and grounded in the right content. This lets you run several agents as distinct personas, each owning different posts.

The fastest way to set a persona up is the New agent button beside an account's bind dropdown: give it a name, a persona prompt, and a provider/model, and Carrier creates the agent (with an LLM reply-to-comments trigger), adds it to the chamber, and binds it — one step, no script builder needed. Afterwards, the Agents tab is where that persona is tuned: prompt, model, temperature, context window and timing, without leaving the Feed Builder to hunt for the agent on the canvas. A readiness checklist (agent accounts bound · responding triggers present · API key stored · every post arriving before the segment ends) appears both in the segment inspector and behind the chip in the Feed Builder’s header, and each agent's own config panel still shows where it is bound (“replies as sona.fields on posts p2”) or warns when it isn't.

Feed content is edited in the Feed Builder (posts, seed comments, accounts, bindings, scopes, layout, settings). Its header’s Data menu bundles the file actions — import as JSON (the full configuration) or CSV (a flat, spreadsheet-friendly content format — accounts are derived automatically from the author handles and existing agent bindings survive re-import), export in either format — with a Template JSON download of a worked example to start a bulk edit from, plus a downloadable starter kit (worked CSV, plain-language guide, and sample images) for spreadsheet-based authoring. Seven managed variables are created automatically per social segment — social_<segmentId>_comments, _agentReplies, _likes, _follows, _actionLog, _commentCount, and _votes — and can be used anywhere other variables can. _votes is only ever filled by the reddit layout; on the other two it stays empty because there is nothing to vote with.

If you built a social segment before the reddit layout shipped, it still has only the original six variables. Carrier does not add _votes to a saved experiment on its own — open that segment's Feed Builder and save (or re-import its CSV) and the variable appears. Votes are recorded in _actionLog either way; it is only the dedicated _votes variable that has to be created this once.

2.17 The task segment

In its plain form a task segment shows a block of instructions, then a labelled text box with a live character counter reading “0 / 500 characters” and a Submit button that stays greyed out until the response is long enough. Submitting locks the box, removes the button and leaves a “Response submitted” confirmation in its place. Give the segment an iFrame URL and an external page — an existing web experiment, a game, a reaction-time tool — is loaded in a sandboxed frame between the instructions and the text box. Turn on the custom application and the segment is handed over entirely: the instructions shrink to a short Markdown header, the researcher's own HTML fills the rest of the screen, and Carrier's text box, counter and results panel are not rendered at all.

The task segment is the escape hatch for activities Carrier has no purpose-built segment for, and it is used at three escalating levels: a prompted free-text response with length limits; an externally hosted tool that posts a result back when the participant finishes; or a full custom application whose internal state is bound to experiment variables in both directions. That last case is what distinguishes the segment from a dressed-up input. A belief slider, a network diagram, an economic game or a drawing canvas can write its state into variables that surveys, matching rules, visibility gates and bot prompts all read (§4.2), and can be handed live variables back as they change — so a bespoke instrument becomes part of the experiment rather than an external detour whose data has to be reconciled afterwards. The trade-off is that whatever runs inside the frame is the researcher's responsibility: Carrier renders it, sandboxes it and records what it posts back, but validates none of it.

What the participant sees
The task segment as a participant sees it.
The instructions banner above the response area, with a character counter and an explicit Submit before the segment will advance.
ParameterWhat it controls
Instructions (shown to participants)The text above the task; empty falls back to “Complete the task below:”. Injected as raw HTML in the plain task — markup works — but rendered through the restricted Markdown renderer when the custom application is enabled. One field, two rendering rules.
Require text submissionOn by default. Renders the prompt, text box and Submit button, and holds the segment's Continue button disabled until Submit is pressed. It is the gate, not just the visibility switch — see the caution below.
Text PromptThe label shown above the text box (default “Enter your response below:”). The box's own placeholder, “Type your response here…”, is fixed and cannot be changed.
Min Length · Max LengthMinimum characters before Submit becomes clickable, and a hard maxlength ceiling on the box (empty = no limit). Both surface only in the counter, as “N / max characters” with a small “(min N)” hint; there is no message explaining a greyed-out Submit, and typing simply stops at the maximum.
iFrame URLAn external tool loaded in a sandboxed frame. It returns its result by posting { type: 'carrier-task-result', payload } to the parent window; the payload is stored alongside the text response as iframeData.
iFrame Height (px)Fixed pixel height of that frame (default 400, minimum 100). Pixels here — the custom application's height is in vh.
Enable custom HTML applicationReplaces the whole plain-task rendering with the researcher's HTML and reveals the fields below. Once it is on, iFrame URL, Text Prompt and the length limits no longer reach the participant.
Source · URL · Inline HTML (N chars)Where the app comes from. External URL exposes a URL field — a different field from iFrame URL above, with different plumbing. Inline HTML exposes a character count plus Edit HTML… (a full-screen editor) and Insert starter template.
Height (vh)Height of the application frame as a percentage of the viewport, 10–100, default 80. Around 80–100 fills the segment.
Hide advance buttonSuppresses the Continue button outright, ahead of every transition rule, so an immersive app can fill the segment and let the parent chat drive progression.
Variable bindingsThe table of OUT and IN rows that connects the app's state to experiment variables. Each row is a direction, an html key (the field name inside the app's own state object) and a variable; a live cheat-sheet below prints the exact messages the app must send and listen for.
Data SharingOffered (Never / After all submit / Real-time) because task counts as an interactive type, but for a task the shared results panel only ever reads “Submissions — N response(s) submitted”. The Results Display and Anonymous Results sub-options that appear beneath it change nothing on a task, because no response content is shown.
Display ModeStandalone or embedded, with the usual embedded start / stop / completion and layout controls. Choosing embedded removes the Timing and Transition cards from the inspector.
Timing · Transition (defaults)A task is not treated as user-paced, so a new one is created with a 5-minute duration, a 60-second warning and transition mode sync — a timed segment that waits for the whole group. Worth revisiting deliberately, because it interacts with the submission gate below.

The bindings deserve a closer look, because they are what the custom application is for. An OUT row names a new variable and Carrier creates it: a managed alias that appears in the Variables Manager locked — referenceable everywhere, editable nowhere — and is dropped again if the binding row is removed. A name already in use is refused with an inline warning rather than silently overwritten. An IN row picks from a dropdown of the experiment's variables, deliberately listing all of them rather than only those already resolved at this point in the timeline, because the interesting IN values are often ones that change during the segment — an LLM-processed variable, a running peer aggregate.

The contract on the other side of the frame is two messages. The app posts { type: 'carrier-task-data', state: {…} } whenever its state changes; Carrier picks out the OUT-bound keys, debounces them over 400 ms, discards no-op repeats and flushes whatever is pending when the segment ends. In the other direction Carrier posts carrier-variables with the changed IN keys. Variables that were already set before the segment began never fire a change event, so an app that wants them must announce itself with a carrier-app-ready message and receive the full snapshot in reply. Insert starter template writes a working skeleton that already does all of this, wired to the bindings exactly as they stand at that moment — so add the bindings first, generate the template second, and note that it overwrites any existing HTML without asking.

A task can run standalone or embedded, and both embedded layouts are fully supported: the inline card rewires the submit, iframe and custom-application listeners exactly as the standalone path does. The defaults are standalone and inline. The split layout — the app in the main pane with a live chat docked beside it — is the intended shape for an instrument used during a discussion, but it needs a chat segment as its parent; without one it falls back to the inline card silently. Below 900 px of viewport width every embedded segment reverts to standalone, which for a task means the app replaces the conversation rather than sitting next to it.

⚠ The submission gate, and what crosses the frame

Most task segments that go wrong in a pilot go wrong in one of these five ways. All five are silent — the participant sees no error.

  • Submit is rendered disabled and is only re-evaluated when the participant types. Min Length 0 does not mean an empty response is acceptable: a participant who types nothing never enables Submit, and the Continue button stays disabled behind it.
  • With the default transition mode sync, that participant does not merely strand themselves — the whole matched group waits until the 5-minute duration expires.
  • Require text submission is inert once the custom application is enabled, because no text box is rendered to gate on. The app's own state, delivered through OUT bindings, is then the only record of what the participant did.
  • A carrier-task-result payload larger than 1 MB, or one that cannot be serialised, is replaced by a marker recording that it was oversized. Sending it would exceed the server's message limit and disconnect the participant mid-run, so the run is protected at the cost of the payload. Post ids, scores and timings; upload large artefacts over HTTP separately.
  • Sandboxing differs by origin, and not in the direction most people expect. A cross-origin tool keeps allow-same-origin; a tool served from Carrier's own origin deliberately loses it, and inline custom HTML runs in an opaque origin with no cookies, no localStorage and event.origin reported as the string “null”. A tool that depends on its own storage will not work in the same-origin or inline cases.
🎞 Builder walkthrough — Wiring a custom application to variables

The researcher switches a segment to task, enables the custom HTML application, adds an OUT binding and an IN binding to the bindings table, clicks “Insert starter template”, and opens the HTML editor to show the generated skeleton already posting and listening on exactly those keys.

2.18 The process segment

A process segment shows a spinning icon and a single line of text — by default “Analyzing your responses…” — centred on an otherwise empty screen. There is no input, no Continue button, and no navigation of any kind: the participant waits, and the platform moves them on when it is ready. It is the only segment type that asks nothing at all of the person in front of it.

Its purpose is the work happening out of sight. While the spinner turns, the server runs the llm-processed variables attached to the segment: it sends the sources those variables declare — a free-text answer, an earlier submission, a chat transcript — to a language model with the researcher's prompt, and stores the result against each participant. A process segment is therefore the join between something a participant has already done and something a later segment needs to know: a summary of an open-ended answer to read back to them, a classified stance used to gate a chamber, a piece of advice written into an instruction. It is plumbing rather than an activity, and it exists for one reason — the timeline must not advance until that derived value exists.

What the participant sees
The process segment as a participant sees it.
What a participant sees while an LLM step runs: the configured waiting message and a spinner, with no control of their own.
ParameterWhat it controls
LLM-processed VariablesOne tick-box per variable in the experiment whose kind is llm-processed. The ticked variables are the ones this segment runs and waits for. Every such variable is offered regardless of its own Update trigger, and ticking it runs it here whatever that trigger says. If the experiment defines none, the control collapses to the placeholder “No llm-processed variables defined. Create one in the Variables manager first.” — the segment cannot be configured at all until a variable exists (§4.2).
Processing MessageThe line of text under the spinner. Left empty it reads “Analyzing your responses…”. The server sends its own copy again when the segment starts and overwrites what the browser rendered, so a message containing variable references resolves server-side and may visibly change a beat after the spinner appears.
Timeout (seconds)How long the server waits for the language model before applying On Timeout. Default 30, minimum 1. It also sets the participant's release: the browser advances on its own five seconds after this figure if the server's completion signal never arrives. Emptying the box restores 30 s rather than removing the limit — there is no “wait forever” setting.
On TimeoutWhat becomes of variables that have not resolved when the timeout expires. Use default values (fallback) writes each unresolved variable's default value for every participant who has none; Block (keep waiting) writes nothing and leaves them unset. Neither option keeps the participant on the spinner — see below.
Auto-advance when processing completesTicked by default. When ticked, the completion signal tells the browser to move to the next segment as soon as the variables resolve. Unticking it changes the signal to “wait”, which the browser honours — but the segment offers no control with which to continue, so the participant is released by the timeout instead. See below.

Everything about the language-model call itself — provider, model, system and user prompts, declared sources, output format, and whether the call is made once per participant or once per chamber — belongs to the variable definition in the Variables manager (§4.2), not to the segment. The segment contributes only three things: when the call happens, how long the study will wait for it, and what the participant sees meanwhile. One consequence is worth knowing before debugging an empty result: if a ticked variable carries a capture scope pointing at some other chamber or segment, the process segment skips it silently and its stored value stays frozen.

The builder's validation panel flags a process segment with nothing ticked (“nothing will be processed”), and flags a tick that no longer corresponds to an existing llm-processed variable — renaming or deleting a variable in the Variables manager leaves the segment holding a dangling reference, which is reported but not repaired.

2.18.1 Display and timing

Treat the process segment as standalone only. The builder's Display & Sharing card nonetheless offers it a Standalone / Embedded choice, because embedding is withheld only from chat and attention-check; the participant-side renderer, however, has no case for this type. An embedded process segment renders the literal text “Unknown segment type: process” inside the chat flow and the variables never run at all. (Below a 900 px viewport the runtime falls back to standalone and the segment works by accident, which makes the fault easy to miss on a laptop.) Leave Display Mode alone. There is no Data Sharing control, since nothing is submitted here.

Timing is collective. The server starts the run only once every participant in the chamber has reached the segment, and broadcasts completion to the whole chatroom, so a matched group waits together and leaves together. In a single-participant chamber the wait is simply the length of the call.

⚠ The gate is softer than it looks

The panel reads as though the researcher controls how long a participant is held. In practice the participant is always released, and the settings decide only what has been written by the time they are:

  • Block (keep waiting) does not keep anyone waiting. The server races the model call against the timeout and announces completion when the timeout wins, exactly as it does under fallback. The sole difference is that block writes no default values, so an unresolved variable is left empty — and a later instruction or prompt interpolating it renders nothing. Of the two options, this is usually the more damaging failure.
  • Unticking Auto-advance does not produce a pause. It changes the completion signal to “wait”, and the browser obeys, but the segment draws no continue control of any kind, so the participant sits on the spinner until the client's own release fires at Timeout + 5 s. The net effect is to replace a prompt advance with a silent, later one. There is no supported way to hold a participant on a process segment, and no way for them to advance by hand.
  • The Timing and Transition panels are effectively bypassed. Progression is driven by the completion signal and that release timer, whichever comes first. A newly dropped segment carries the generic 5-minute Max Duration, which only ever takes effect if it is shorter than Timeout + 5 s; Manual mode and Allow early advance have no button to expose. Lengthening the wait means raising Timeout, not Max Duration.
  • The two clocks do not start together. A participant's release timer is armed the moment they reach the segment, while the server begins the run only when the last participant arrives. In a chamber with staggered arrivals an early arriver's clock can therefore run down before the call has even started. Set Timeout generously in multi-participant chambers.
  • Changing the segment's Type discards this configuration. A process segment stores its settings apart from every other segment type's configuration, and that store is deleted the moment the type changes. Switching a segment to another type and back returns the ticks, message, timeout and timeout behaviour to their defaults, without warning.
🎞 Builder walkthrough — Pairing a process segment with an llm-processed variable

The researcher drags Process (LLM) onto the chamber timeline and finds its variable list empty — “No llm-processed variables defined” — so they open the Variables manager, add an llm-processed variable with a source and a prompt, and return to the segment, where the new key now appears as a tick-box beside the processing message and timeout.

2.19 The attention-check segment

An attention-check segment takes over the screen with a panel headed “Attention Verification” and asks the participant to prove they are present and paying attention before the timeline will let them continue. What that proof looks like depends on the mode. In survey mode the panel is a short Survey.js form of researcher-written items — instructional-manipulation checks, catch trials, comprehension questions — graded the moment the participant presses Survey.js’s own Complete button. In liveness mode the participant first sees a consent-style pre-screen (“This step requires camera access to verify your presence” / “No video will be recorded or transmitted”) with a Begin Verification button, then a live camera preview that calibrates, waits for a face to hold steady in frame behind a filling progress bar, and finally requests one physical action at a time — blink, turn left, raise eyebrows, and so on. In CAPTCHA mode the participant meets an animated canvas and a countdown: either counting circles in a phase-coded animation, clicking a red dot before it vanishes, or watching a masked ball trace a hidden path and then picking every matching trajectory out of a thumbnail grid.

The purpose is screening, and the segment is built around what happens to a participant who fails rather than around the check itself. Every attempt emits a result record — mode, pass/fail, retry count, and mode-specific details — which lands both on the run and as a system message interleaved into the transcript, so a failure is visible in the export at the moment it happened (see §6.5). The failure itself routes through a configurable policy: end the session with a message and a redirect to a panel screen-out URL, grant a fixed number of retries, or log the failure and carry on so that attentiveness can be used as a covariate rather than an exclusion. The three CAPTCHA variants exist for a narrower reason: their answers are generated and graded server-side, so unlike a survey item they cannot be passed by a script that has read the experiment configuration.

2.19.1 Mode

Mode is the first control on the panel and selects the whole check family — Survey (question-based) (the default), Liveness (camera-based), or CAPTCHA (perception / reaction). Changing it rebuilds the rest of the panel, but the configuration for all three families is kept side by side in the segment, so switching mode to look at the other options does not discard the settings already entered.

2.19.2 Survey mode

What the participant sees
The attention-check segment as a participant sees it.
A survey-mode check rendered in its own framing, visually distinct from an ordinary survey segment so the two are not confused in a transcript.
ParameterWhat it controls
Questions / Add QuestionThe list of graded items. Each is a collapsible card in the builder, headed “Q1: …”, with a trash button to remove it. An empty list is a configuration error the runtime does not report — see the caveat below.
Question TypePer item: Radio Group, Checkbox, Dropdown, Text, or Rating. The choice list is hidden for Text and Rating, which have none.
Question TitleThe prompt shown to the participant, and the card’s header in the builder.
Question Name (unique ID)The key this item is recorded under in the graded response data and in the per-attempt result record. Auto-seeded as attn_q0, attn_q1, … Two items sharing a name collide in the same key, and this is also the field a completion condition matches on.
Choices (one per line)The answer options, newline-separated. Shown for Radio Group, Checkbox and Dropdown only.
Correct Answer and Case sensitiveThe expected value for this item. Grading is a string comparison, lower-cased on both sides unless Case sensitive is ticked — that checkbox appears for Text items only. Because the comparison is on the stringified answer, a Checkbox item’s multi-select answer must be written here as a comma-joined list in the order the options are stored.
Pass Criteria and Pass PercentageAll correct (the default) or Percentage. Choosing Percentage reveals a 0–100 threshold, default 100, compared with “at least” — so a threshold of 100 is exactly equivalent to All correct.
Randomize question orderOn by default. Shuffles the presentation order per participant. Grading is keyed on Question Name, so shuffling never misaligns an answer from its expected value.
Show feedback on wrong answersIntended to append a per-item Correct / Incorrect list under the form when the participant fails. In practice it is never seen — see the caveat below.

2.19.3 Liveness mode

Liveness mode has two phases in two accordions, each independently switchable. The presence phase establishes that a face is in front of the camera at all; the liveness phase establishes that it is a live one by requesting actions a static photograph cannot perform.

ParameterWhat it controls
Enable presence checkOn by default. Requires a sustained face in frame before the action phase begins.
Sustained Duration (seconds)How long a face must be continuously detected to clear the presence phase. Default 3 s. The phase also carries an internal 60 s ceiling that is not exposed in the builder.
Enable liveness checkOn by default. Runs the requested-action challenge after presence. Turning both phases off leaves a check with nothing to verify.
Action PoolSeven checkboxes — blink, turn-left, turn-right, raise-eyebrows, open-mouth, nod-up, nod-down — selecting which actions may be requested. All on by default. The labels are the raw identifiers, not prose.
Number of ActionsHow many actions are drawn at random from the pool, 1–8, default 3. The builder does not clamp this to the number of actions actually ticked.
Time Limit (seconds)The budget for the whole action phase. Default 30 s, minimum 5 s.
Instruction TextThe status line shown during a phase. There are two identically-labelled fields, one per accordion: the presence phase defaults to “Please position your face in the camera frame”, the action phase to “Please perform the following actions”.

A camera permission the participant declines is treated as a retryable problem, not a failure: the panel shows a warning and offers another attempt rather than terminating the run. A passing liveness check is also the only thing that can start a chamber’s Background Face Monitoring — that setting’s Start After Segment dropdown lists attention-check segments and nothing else (see §1.3).

2.19.4 CAPTCHA mode

CAPTCHA Variant selects one of three challenges, each with its own parameter set and its own server-side grader. Changing the variant rebuilds the sub-panel.

ParameterWhat it controls
CAPTCHA VariantSpooky Circle (perception) — count the circles in a phase-coded animation; Red Dot (reaction) — click a red target before it disappears; Trajectory Recovery (motion) — identify a masked ball’s path in a thumbnail grid.
Spooky Circle · Minimum Circle Count and Maximum Circle CountThe range a generated challenge may draw from, 1–9 each, default 2 and 7. They also become the bounds on the participant’s number input. The builder does not check that the minimum is below the maximum.
Spooky Circle · Time Limit (seconds)How long the participant has to answer. Default 35 s. This is the only variant with its own time-limit control: Red Dot uses Overall Timeout instead, and Trajectory Recovery has no control at all and falls back to a fixed 40 s.
Spooky Circle · Difficulty1–10, default 4. Higher values make the rings harder to perceive.
Spooky Circle · Log answer telemetry (latency / input method)Off by default. Records soft behavioural signals alongside the answer. It never contributes to the pass/fail decision.
Red Dot · Required HitsHow many dots must be clicked successfully, 1–20, default 5. Drives the on-screen “0 / N” counter.
Red Dot · Target Lifetime (ms)How long each dot stays visible before vanishing, 200–5000 ms, default 800. Note the unit: this is the one duration field on the segment expressed in milliseconds rather than seconds.
Red Dot · Overall Timeout (seconds)The total budget for reaching the required hits. Default 60 s.
Red Dot · Distractor CountDecoy dots shown alongside the target, 0–10, default 0 (off). Every hit’s position and timing is verified server-side; the browser only reports the click.
Trajectory Recovery · Grid Columns and Grid RowsThe size of the thumbnail grid, 2–6 each, default 4 × 4.
Trajectory Recovery · Correct TrajectoriesHow many thumbnails match the ball’s path, default 3. The participant must select all of them. Its upper bound is derived from the current grid size, so it is re-clamped only when the panel rebuilds — check it after shrinking the grid.
Trajectory Recovery · Enabled Trajectory TypesWhich path families the target and the distractors are drawn from: Zigzag, Circular, Wave, Cubic, Quadratic. All on by default.
Trajectory Recovery · Difficulty1–10, default 5. Higher values make the masked motion harder to follow. This is a separate control from the Spooky Circle difficulty of the same name and means something different.

2.19.5 What happens on failure

These controls sit below the mode-specific panel and apply to all three modes identically.

ParameterWhat it controls
On FailTerminate session (the default) ends the run with attention_check_failed as the recorded reason; Allow retry re-renders the check from scratch, drawing a genuinely new CAPTCHA challenge where applicable; Continue (log only) advances as if the participant had passed, leaving the failure in the data as a covariate.
Max Retries1–5, default 1. Rendered only when On Fail is Allow retry. Retries are counted per participant and reported in the result record.
Fail MessageThe text shown on the end-of-study screen, which is titled “Study Ended” rather than “Study Complete”. Default: “You did not pass the attention check.” Hidden when On Fail is Continue (log only).
Redirect URL (optional)Where a failed participant is sent, typically a recruitment panel’s screen-out URL. The redirect fires after the participant acknowledges the debrief screen, not the instant they fail, so a participant who closes the tab first is never redirected. Hidden when On Fail is Continue (log only).
Completion Conditions / Add ConditionA second, independent screen-out path, offered on this type and on survey only. Each condition is an If field (matched against a Question Name, exactly), an operator (=, ≠, in, >, <), a value, and its own message and redirect URL. It runs alongside the graded pass/fail rather than replacing it, which makes it the right place for exclusions that are not about attention — a demographic screen-out, say, on the same form.

2.19.6 Display mode and timing

The attention check is standalone only, and this is enforced on both sides. The builder never offers a Display Mode dropdown for this type — its Display & Sharing card is empty, with no embedded-start triggers and no data-sharing control — and the runtime has no embedded renderer for it, so it can never be layered over a chat. That is a deliberate constraint rather than an omission: an attention check that could be dismissed like an overlay, or answered while a conversation continued underneath it, would not be measuring attention. The two common timing fields, however, behave differently here than anywhere else, and both are worth setting explicitly.

⚠ Timing is not neutral on an attention check

On every other segment type, the timing fields govern pacing. On this one they govern outcomes:

  • Max Duration expiry is a failure, not an advance. A participant who runs out of time is recorded as failing with reason timeout and routed through On Fail — which, at its default, ends their session. This matters because of what a new segment inherits: attention checks are not one of the user-paced types, so a freshly added segment arrives with a five-minute duration already set, and switching the Type dropdown resets only the type-specific configuration, never the timing. An attention check left at its inherited default silently terminates any participant who takes longer than five minutes — a real risk for the camera and CAPTCHA modes, where a permission prompt or a retry can eat the budget. Set the duration deliberately or clear it.
  • Min Duration holds participants who have already passed. A pass does not advance until the minimum has elapsed, so the participant sits on a completed check with nothing to do and no explanation. Leave it at zero unless the delay is the point.
⚠ Three ways a survey-mode check grades everyone the same way

Survey mode is the default and the easiest to leave half-configured. Each of these produces a check that is uniform across participants — and therefore measures nothing — without reporting a problem in the builder:

  • No questions means everyone passes. A survey-mode check with an empty Questions list is skipped at runtime with only a browser-console warning; every participant is waved through and no result record is written. A check added late in the build and never populated is indistinguishable, in the export, from one that was never added.
  • A blank Correct Answer means everyone fails that item. Leaving the field empty does not make an item ungraded — it makes the expected answer the empty string, which nothing the participant can select will match. With the default All correct criterion and the default Terminate session policy, one forgotten answer key ends every run in the study. To include an ungraded question on the same screen, put it in a separate survey segment instead.
  • Show feedback on wrong answers does nothing observable. The feedback list is built into the page and the failure is handled in the same instant, so the panel is torn down — re-rendered for a retry, or replaced by the end-of-study screen — before the feedback is ever painted. Do not rely on it to tell participants why they failed; put that explanation in Fail Message.
🎞 Builder walkthrough — Building a screening attention check

The researcher adds a segment, switches its type to Attention Check, adds two graded questions with their correct answers, clears the inherited five-minute Max Duration, and sets On Fail to “Terminate session” with a fail message and a screen-out redirect — and the Display & Sharing card stays empty throughout, because the check can only run standalone.

Roles — who occupies the slots a chamber defines, and what each kind of occupant may do inside these activities — are the subject of §3.

Part I · §3. Roles: Communicator, Mediator, Processor

Three orthogonal roles — communicate, facilitate, assist composition — each occupying a distinct zone of the participant's screen. Any type (human, LLM chatbot, scripted chatbot, agent) can fill any role, with one exception.

3.1 Why roles exist

Chapter 1 defined the shape of an experiment but not its cast. A chamber declares a list of slots, each of which awaits a participant; the question this chapter answers is what a participant who fills a slot can actually do.

Carrier separates that question into two orthogonal axes:

  • The type of a participant — what they are. Four types: a real human, an LLM chatbot (language-model-driven, chat only), a scripted chatbot (rule-driven, chat only), or an agent (an autonomous Claude Agent with built-in tools for reading documents, running code, and browsing the web — see the note on non-human participants).
  • The role of a participant — how they take part. Three roles: a communicator, a mediator, or a processor.

The four kinds of participant differ in two practical respects — how they produce what they say, and how reproducible they are across sessions:

KindHow it produces outputReproducibility
HumanThe person typesWhatever the person does
LLM chatbotAn LLM is called once per turn with the conversation history; output is a chat message or silenceVariable across sessions
Scripted chatbotPre-written rules fire when their trigger conditions matchIdentical across sessions
AgentThe Claude Agent loops between LLM calls and built-in tool calls (read files, run code, browse) until it decides it is ready to speak, then produces a chat message grounded in what it foundVariable across sessions

The type × role matrix is the most important table in this chapter, because almost every design decision below either depends on it or is constrained by it:

Type \ RoleCommunicatorMediatorProcessor
Human
LLM chatbot
Scripted chatbot
Agent

The only forbidden combination is scripted chatbot as processor. The reason is technical but worth knowing: processors operate by reading drafts, generating suggestions, or interrupting composition — activities that demand the kind of open-ended language understanding only a human or a language model can provide. A pre-scripted rule set has no business being a processor.

Everything else is supported. That makes it possible to write one experimental design and instantiate the same role with a human in one condition and a language model in another — which is the single most important affordance Carrier offers for studies that compare human and machine behaviour. It also means that the choice between an LLM chatbot, a scripted chatbot, and an agent can itself be the manipulation: same role, same instructions, three different kinds of non-human partner — one talking from training-time knowledge, one talking from a written rulebook, one talking from documents it has just read.

3.2 The spatial model: three zones in the interface

A useful way to keep the roles separate in your mind is to remember that each occupies a distinct zone of the participant's screen during a chat segment:

Mediator zone
Top of chat / overlay banners. Styled announcements, broadcasts, facilitation cues.
Communicator zone
The main message area. Messages exchanged among the chamber's communicators — human and/or AI/scripted — appear here side by side.
Processor zone
Input area / composition sidebar. Draft review, generation, in-progress suggestions.
The three role zones in the chat-segment interface.

This is more than visual hygiene. Mediators broadcast; communicators converse; processors assist composition before words enter the conversation. The separation of channel is what makes it possible to study facilitation, conversation, and composition assistance independently of one another — or to combine them deliberately, knowing that the layers do not bleed into each other.

3.3 Communicator

3.3.1 Framing

The communicator is the primary interactive participant. Whatever the experiment ultimately studies, communicators are the ones doing the studied behaviour. All communicators — whether a human, an LLM chatbot, a scripted chatbot, or an agent — operate in the same message space: their messages appear in the main chat area alongside each other in the order they were sent.

The animating design principle is interaction parity. A human communicator and any non-human communicator send messages through the same mechanism, appear in the same UI, and are indistinguishable to other participants unless the researcher explicitly marks them otherwise. This is what makes it possible to run human–human, human–machine, and machine–machine conditions of the same design without rebuilding the experiment.

3.3.2 The communicator design surface

Three dimensions of configuration matter for any communicator. They are independent: settings on one dimension do not constrain settings on the others.

Identity. Who appears in the chat, by what name, and with what disclosure?

AspectChoicesWhat you decide
Source of identityUser-provided · Configured · Auto-generatedWhether the participant chooses their own display name, you pre-set it, or the platform invents one.
VisibilityVisible · HiddenWhether the communicator appears in the participant list at all.
Type disclosureDisclosed · BlindedWhether other participants are told that this communicator is human or AI.

Human communicators typically go through an identity-setup flow (choose a display name, pick an avatar) before entering the experiment. Non-human communicators — LLM chatbots, scripted chatbots, and agents alike — carry pre-configured identities. The separation makes blinding possible: a participant cannot tell from the interface alone whether a fellow communicator is human or a machine.

Input control. When can the communicator speak, and who decides?

AspectChoicesWhat you decide
Initial stateEnabled · Delayed · ConditionalWhether the communicator can send messages from the moment the chat begins.
Enable conditionsTime-based · Message-count · Bot-trigger · Participant-messageWhat event lifts a delay or unlocks input.
External controlNone · Mediator-controlledWhether a mediator can disable or enable this communicator's input dynamically during the chat.

Carrier's chat input is not simply on or off. A communicator might begin with input disabled, wait for three messages from other participants, and then become enabled. Or a mediator (see §3.4) might disable and re-enable input on the fly to enforce turn-taking. Building experimental conditions out of these primitives is how you produce interventions like simultaneous discussion vs. sequential discussion.

Message capabilities. What kinds of messages can be sent and acted on?

AspectChoicesWhat you decide
Content typesText · Media (audio/video)What the communicator can attach to a message.
ReactionsEnabled · DisabledWhether emoji reactions are available to the communicators.
ReportingEnabled · DisabledWhether a participant can flag a message for the experimenter.

3.3.3 Communicator subtypes at a glance

The four communicator subtypes correspond to the four types in the matrix:

  • Human communicator. A real participant joining via browser. They enter the matching queue, are matched into a chamber, and join its chatroom. The platform tracks their socket connection with a heartbeat; on disconnect they can be reconnected within the session. All messages, survey responses, timestamps, and activity events are recorded.
  • LLM-chatbot communicator. Configured by provider (OpenAI / Anthropic / Google / compatible), model, system prompt, temperature, and response logic (when to speak, when to stay silent — see §5.3). The model is given the conversation so far on each turn and produces a chat message (or stays silent). LLM chatbots never enter the matching queue: once the human slots in a chamber are filled, they are spawned into the chatroom automatically. The platform supports multi-step LLM chains for advanced configurations (a generation step, then a critique step, then a rewrite step).
  • Scripted-chatbot communicator. Configured by a set of triggers (§5). Like LLM chatbots, scripted chatbots do not enter the matching queue — they are spawned into the chatroom after human slots are filled. Their behaviour is deterministic and replayable: the same input sequence produces the same outputs across sessions, which makes them the right choice for confederate roles and any design in which conversational reproducibility matters.
  • Agent communicator. A Claude Agent (Anthropic) scoped to a particular document area — typically the study materials, a reference corpus, or a configured dataset. Before producing each message, the agent's underlying model loops over its built-in tools (file reading, code execution, web browsing) to look things up, run small computations, or check a citation. The message it eventually sends is grounded in what it has retrieved. Agent communicators are the natural choice when the experiment wants a conversational partner that can answer with evidence — a study-materials expert that quotes the brief verbatim, a fact-checker that can browse during the discussion, a domain assistant that can re-read the dataset before stating a number.

3.3.4 Research uses of the communicator role

A short, indicative list of designs that map cleanly onto different communicator configurations:

DesignCommunicator configurationWhat it studies
Group discussion2+ human communicatorsOpinion formation, group dynamics, polarisation
Human–AI dyad1 human + 1 LLM communicatorTrust, persuasion, perception of machine partners
Confederated AI1 human + N LLM communicators, blindedConformity, majority influence
Turn-taking studyHumans with delayed input controlSequential vs. simultaneous discussion
Agent-to-agent comparison2 LLM communicators with different system promptsModel behaviour under controlled prompting

3.4 Mediator

3.4.1 Framing

The mediator is a facilitator. Unlike a communicator, a mediator does not converse on equal footing with the others — they orchestrate the conversation. The qualitative differences are four:

  1. Universal visibility. A mediator sees every message in the chat, regardless of who it was addressed to.
  2. Distinct delivery. A mediator's output appears as styled announcements at the top of the chat, not as chat bubbles.
  3. Control capabilities. A mediator can act on the chat — disable a communicator's input, prompt a specific participant, highlight a message — not only speak into it.
  4. Event awareness. A mediator reacts to aggregate patterns (number of messages, time elapsed, idle participants) at least as readily as to individual messages.

These four capabilities together describe the moderator / facilitator / researcher dynamic that has no analogue in plain group chat.

3.4.2 The broadcast system

Mediator messages are called broadcasts. A broadcast is styled along three independent axes:

AxisChoicesEffect
StyleFacilitator · Announcement · SystemIcon and tone of voice in the rendered banner.
PriorityNormal · Important · UrgentVisual emphasis and how long the banner stays before auto-dismissing.
PersistenceDismissible · PersistentWhether the participant can dismiss the banner.

Broadcasts can also be targeted: at every participant in the chamber, at communicators only, at a specific named participant, or at all participants in a specific role. This is the mechanism by which a mediator can deliver a private prompt to one communicator without the rest of the group seeing it.

3.4.3 Facilitation actions (LLM-driven mediators)

When the mediator is driven by a language model — either an LLM chatbot or a Claude Agent — it has access to an action vocabulary that goes beyond plain broadcasting. These five intervention actions are produced as part of the model's structured response on each turn; the most consequential is the first.

A mediator implemented as a scripted chatbot can fire the same actions, but their parameters must be baked into the trigger configuration ahead of time (§5.5); the scripted bot cannot choose the action contextually based on what was just said.

Note that this intervention-action vocabularydisable_chat, enable_chat, prompt_participant, highlight_message, request_attention — is distinct from the built-in tools of a Claude Agent (file reading, code execution, web browsing). They live on different channels:

  • The intervention actions are how a mediator acts on the chat — they affect what participants see and what they are allowed to do.
  • An agent's built-in tools are how it gathers information for itself — they affect what the agent knows when it speaks, but the participants see only the eventual message.

A Claude Agent acting as mediator has access to both: it can read its configured document area before responding, and fire intervention actions alongside its broadcast. An LLM chatbot acting as mediator has only the intervention actions; a scripted chatbot acting as mediator has only the pre-baked variants.

ActionTargetDescription
disable_chatA specific communicator, or allTemporarily prevents the target from sending messages. The release is governed by a set of conditions described below.
enable_chatA specific communicator, or allExplicitly lifts a disable. Immediate.
prompt_participantA specific communicatorSends a private encouragement or prompt visible only to that participant.
highlight_messageA specific messageMarks a past message as highlighted in the chat for a configurable duration.
request_attentionA specific communicatorTriggers a visual or audio cue to draw the participant's attention.

The disable_chat action supports composite release conditions — a list of conditions combined with an any / all connector that determines when the disable lifts. The available conditions are:

  • Timeout — after a fixed duration.
  • All others responded — when every other communicator has sent a message of at least a configured minimum length.
  • Message count — after a fixed total number of messages have been sent in the chat.
  • Keyword mentioned — when a designated keyword (or any from a list) is uttered by any, or a specific, participant.
  • Participant message — when a designated participant has sent a configured number of messages.
  • Mediator release — released only by an explicit subsequent enable_chat.
  • Segment change — released when the chamber transitions to its next segment.

Composite release conditions are the building blocks for richly specified turn-taking protocols. “Wait until everyone else has responded, or sixty seconds, whichever comes first” is a single disable_chat action with two conditions and an any connector.

3.4.4 Mediator-specific triggers

Mediators inherit the standard trigger system (§5), but five extra trigger types are particularly suited to facilitation:

TriggerWhen it firesTypical use
PeriodicAt regular intervals after the chat beginsRecurring summaries, scheduled check-ins.
AggregateAfter N messages have accumulated within a time windowBatched synthesis or pattern detection.
Topic-detectedWhen a designated keyword pattern appearsTopic steering, off-topic detection.
Activity-timeoutWhen no messages have been sent for N millisecondsIdle prompts, participation encouragement.
Participant-countWhen the number of active participants crosses a thresholdReacting to departures, waiting for arrivals.

These trigger types, combined with the action vocabulary above, are what make automated AI facilitation in Carrier expressive: a mediator can be configured to periodically summarise the discussion every two minutes, prompt any participant who has been silent for ninety seconds, or steer the conversation back on topic when a specified keyword has not appeared in the last thirty messages.

3.4.5 Activity monitoring

A mediator can optionally maintain an activity model of each communicator. The activity-monitor settings are:

  • An idle threshold (milliseconds of inactivity before a participant is considered idle), and a list of idle prompts to send when it is reached.
  • An active threshold (messages-per-minute rate at which a participant is considered to be dominating), and a list of active prompts to send when it is reached.

This is the building block for participation-equity interventions: a mediator that automatically prompts quiet members and gently invites dominant members to “make space for others”.

3.4.6 Research uses of the mediator role

DesignMediator configurationWhat it studies
Automated facilitatorLLM mediator with periodic + topic triggersEffectiveness of automated facilitation.
Turn-taking enforcementLLM mediator using disable_chat with all_others_respondedEffects of structured discussion on quality.
Participation equityLLM mediator with activity monitoring and idle promptsInterventions on balanced participation.
Discussion steeringLLM mediator with topic-detected triggersTopic-management strategies.
Human facilitatorHuman in mediator role, broadcast capabilityExpert facilitation patterns.
Timed interventionsScripted mediator with periodic broadcastsInformation-injection effects.
Real-time summarisationLLM mediator with aggregate triggers and a synthesis promptImpact of real-time summaries on deliberation.

3.5 Processor

3.5.1 Framing

The processor is the most novel of the three roles. It operates in the input composition space rather than the message exchange space — that is, it acts before a communicator's text becomes a message in the chat. Where a mediator sits over the conversation and a communicator sits inside it, a processor sits alongside the input box, reviewing what the communicator is about to send, generating drafts on request, or offering live suggestions as the communicator types.

The role exists because the act of composing a message is a distinct site of intervention — distinct from facilitating the conversation, and distinct from participating in it. A study that wants to ask “what happens when an AI helps people write what they say?” needs a place to put that AI, and that place is not the chat.

3.5.2 The design space, in three dimensions

It is tempting to think of processors in terms of “review” and “generate” alone, but the design space is richer. Three independent dimensions structure it.

Initiation. Who starts an interaction?

  • Communicator-initiated — the communicator explicitly asks (submits a draft for review, clicks Generate).
  • Processor-initiated — the processor offers help without being asked (sends a suggestion).
  • System-initiated — the platform triggers an interaction based on an event (a pause is detected, a timer elapses).

Control. Who controls the final output?

  • Communicator retains control — the communicator always decides what is actually sent (accept / reject / edit the processor's output).
  • Processor retains control — the processor decides what the communicator sees (filtering, rewriting).
  • Shared control — both can edit; the final version is negotiated.

Timing. When does the interaction happen?

  • Pre-send — before the message enters the chat (review, approval).
  • During composition — while the communicator is typing (real-time suggestions).
  • On-demand — when explicitly requested (a Generate button).

Not every combination of these dimensions is feasible. LLM API latency makes true simultaneous co-editing impractical for LLM processors; processor-controlled rewriting risks undermining the validity of self-report studies by replacing the communicator's voice with the processor's. Carrier's design therefore commits to one principle and offers three concrete modes.

The committed principle: communicator agency. Whatever the processor does, the communicator retains final control over what enters the chat. The processor never bypasses the communicator's agency.

3.5.3 The three modes

Review

Communicator writes draft  →  submits for review  →  processor gives feedback
                                                     ↓
Communicator accepts / edits / rejects  →  message sent to chat
DimensionSetting
InitiationCommunicator-initiated (on-submit) or system-initiated (pause-triggered)
ControlCommunicator
TimingPre-send

Configuration parameters:

  • Triggeron-submit (the communicator clicks Send and the processor steps in) or pause-triggered (the processor steps in automatically after the communicator stops typing for a configured period).
  • Pause timeout — the inactivity window, in milliseconds, used only when the trigger is pause-triggered.
  • Feedback formatfreeform (the processor writes a free-text response), inline-edit (the processor proposes an edited version of the draft), or approve-reject (the processor returns a binary verdict).
  • Mandatory — whether the communicator must address the feedback before they can send the message.
  • Max rounds — a cap on the number of review iterations on a single draft.

Research applications include writing-quality improvement, self-reflection, metacognitive scaffolding, and peer-review dynamics.

Generate

Communicator clicks Generate  →  processor creates a draft
                                  ↓
Communicator edits  →  message sent to chat
DimensionSetting
InitiationCommunicator-initiated (explicit request)
ControlCommunicator (edits before sending)
TimingOn-demand

Research applications include AI-ghostwriting perception, co-authoring dynamics, and the trade-off between generation quality and editing effort.

Real-time assist (human processors only)

Communicator types  →  the draft streams to a human processor
                       ↓
Processor sends ephemeral suggestions  →  communicator accepts or dismisses
DimensionSetting
InitiationSystem-initiated (continuous streaming)
ControlCommunicator (suggestions are ephemeral)
TimingDuring composition

This mode is restricted to human processors because LLM round-trip latency makes truly live suggestions impractical. The use cases — peer coaching, expertise-based assistance, live mentoring during composition — all assume a human partner at the other end.

3.5.4 Phase scripts: dynamic processor behaviour

A processor does not have to behave the same way throughout a chamber. Carrier supports phase scripts — an ordered list of phases, each with a mode and a transition trigger that advances to the next phase.

phases: [
    { id: "warmup",     mode: "disabled", transition: { on-start } }
    { id: "review",     mode: "review",   transition: { message-count: 5 } }
    { id: "generate",   mode: "generate", transition: { time-elapsed: 180000 } }
]

The processor in this example starts disabled, switches into review mode once five messages have been exchanged, and switches into generate mode three minutes after the segment starts. The supported transition trigger types are:

TriggerWhen the next phase activates
On-startImmediately when the segment begins.
Message-countAfter N messages have been sent in the chatroom.
Time-elapsedAfter N milliseconds from segment start.
KeywordWhen a designated keyword appears in the chat.
Participant-eventWhen a participant joins or leaves.
ManualWhen the experimenter advances from the dashboard.
On-endWhen the segment ends.

Phase scripts are how Carrier supports designs like progressive scaffolding (start with heavy review, then withdraw assistance over time) or adaptive intervention (switch modes once a quality threshold has been crossed).

3.5.5 Context configuration

The amount of conversation a processor can see is configurable:

LevelWhat the processor sees
NoneOnly the current draft; no chat history.
PartialThe last N messages (configurable).
FullThe complete chat history of the chamber so far.

For LLM processors, the context level controls what is sent to the model with each call. For human processors, it controls the contents of a read-only chat panel beside the suggestion interface. Context level interacts with privacy considerations: a research design that does not want the processor influenced by — or able to see — earlier conversation should set context to none.

3.5.6 Research uses of the processor role

DesignProcessor configurationWhat it studies
AI writing coachLLM processor, review mode, freeform feedbackImprovement of writing quality through AI feedback.
Mandatory reviewLLM processor, review mode, mandatory: trueEffects of forced reflection on output.
AI ghostwriterLLM processor, generate modeAuthorship perception, AI-assisted communication.
Peer reviewHuman processor, review modePeer-feedback dynamics.
Live coachingHuman processor, real-time assist modeExpert scaffolding during composition.
Progressive scaffoldingPhase script: disabled → review → generateEffects of withdrawing assistance over time.
Mode comparisonTwo chamberlines: one review, one generateTrade-off between reviewing and generating.

3.6 How the roles interact

The three roles are designed to be orthogonal — to operate in different spatial zones, on different inputs, with different output channels — but they can be present in the same chamber and combined in principled ways.

Communicator ↔ mediator. Asymmetric. The mediator sees everything; the communicator does not see the mediator's view. The mediator can broadcast to communicators, disable or enable their input, prompt them privately, and highlight their messages. This asymmetry is intentional: it models the moderator dynamic.

Communicator ↔ processor. Collaborative but communicator-controlled. The processor sees drafts; it sends feedback, suggestions, or generated text; the communicator decides what actually gets sent. The processor never bypasses the communicator.

Mediator ↔ processor. Orthogonal. Mediators act in the broadcast/control layer; processors act in the composition layer. They do not directly interact, but their effects can be coordinated — a mediator might broadcast that “the next message will be reviewed” at the same trigger point at which a processor switches from disabled to review mode.

Four representative configurations to keep in mind:

ConfigurationRoles presentResearch scenario
Communicators only2+ communicatorsStandard group discussion.
Communicators + mediatorN communicators + 1 mediatorFacilitated discussion.
Communicators + processorN communicators + 1 processorAssisted composition.
Full setupN communicators + mediator + processorFacilitated discussion with composition assistance.

3.7 Design principles, distilled

Six principles run through the role system. They are summarised here for reference.

  1. Separation of concerns. Each role occupies a distinct spatial zone (chat, broadcast, input) and serves a distinct function (communicate, facilitate, assist). The separation is what enables clean experimental contrasts.
  2. Communicator agency. The communicator always retains final control over what they say. No mediator or processor can speak in their name.
  3. Type orthogonality. Any participant type that is compatible with a role can fill it. This is what makes human–AI comparisons trivial to set up.
  4. Phase-based dynamism. Both mediators (via triggers) and processors (via phase scripts) can change behaviour during a session, enabling within-session manipulations and progressive designs.
  5. Comprehensive logging. All messages, broadcasts, mediator actions, and processor interactions are recorded with metadata. This is what makes the resulting data analysable.
  6. Configuration hierarchy. Global → chamber → segment overrides → event-based control. Defaults at the top, specificity at the bottom.

3.8 Builder walkthroughs

🎞 Builder walkthrough — Adding an LLM mediator to a chamber

The researcher clicks “Create New Agent”, names it Facilitator, expands the LLM Response section and picks a provider, a model and a system prompt, then confirms with “Add Agent”. The agent joins the Library. They open the chamber and drag it into the Mediators lane — a role is not a field on the agent, it is the lane you drop it into. (Triggers are per-chamber and are authored afterwards in the Script Builder; see §5.7.)

🎞 Builder walkthrough — Configuring a processor with a phase script

The researcher creates an agent called Draft Reviewer, selects it in the Library and switches its Role to Processor — which replaces the trigger-script half of its configuration with the phase editor. They click “Add Phase” three times; each phase carries its own mode (review, generate, real-time-assist or disabled), its own transition trigger, context level, feedback format and AI settings.

Variables — the attributes that travel with each participant and decide which slots they can fill, which chambers they see, and what each activity says to them — are the subject of §4.

Part I · §4. Variables

Variables are how a Carrier experiment becomes personal. They constrain matching, gate visibility, inject context into prompts, and drive triggers.

4.1 Why variables exist

By the end of Chapter 2 we have a design that can place a researcher's chosen mixture of humans, LLM chatbots, scripted chatbots, and agents inside a sequence of chambers, with each role doing what the design expects. That design is, however, still impersonal: it treats every participant identically. The same chamberline, the same slots, the same instructions, the same prompts — applied to every person who walks into the experiment.

Variables are how a Carrier experiment becomes personal. A variable is an attribute attached to a participant — their self-identified expertise, the political party they support, the score they obtained on the pre-survey, the option they ranked first in the last chamber — that is then available to the rest of the experiment to consult. Variables let you:

  • Constrain matching. Require that the matched group contain (say) one self-identified novice and one self-identified expert.
  • Gate visibility. Show a remedial chamber only to participants whose pre-test score was below threshold; skip the chamber for everyone else.
  • Inject context into instructions and prompts. Address each participant by their chosen name; brief an LLM mediator on the political identification of each person in the room.
  • Drive triggers (§5). Fire a bot's response only when the variable matches a condition.

In conventional methodological language, variables are how Carrier represents the experimentally-relevant measured and manipulated characteristics of each participant. Once captured, they travel with the participant for the rest of the run.

4.2 The anatomy of a variable

A variable is defined once, at the experiment level, and then referenced from anywhere in the experiment. Each definition has the following parts:

PartWhat it specifies
KeyThe short identifier used to reference the variable (e.g. expertise, party_id, pretest_score).
TypeThe kind of value the variable can take: string, number, boolean, single-choice, multi-choice.
SourceWhere the value comes from — a survey response, the result of a segment, a system-assigned value, or an aggregate over other participants.
Options (for choice types)The list of allowable values, optionally each with a display label and a numeric value (see 3.2.2).
DefaultThe value used when the source produces nothing.

Variables are addressed in the rest of the experiment by their key, prefixed with var. — for example, var.expertise or var.party_id. The prefix exists so that the system can tell, when reading a configuration, that a string is referring to a variable rather than to a literal.

4.2.1 Where variable values come from

A variable's value is computed from a source specification. The five common sources are:

  • Survey response. The most common source. Read the value of a named question from one of the participant's completed surveys — the global pre-survey, a chamber pre- or post-survey, or any embedded survey segment. Survey-based variables are evaluated as soon as the survey is submitted, which means they are available for chamberline assignment, slot matching, and chamber visibility from that point forward.
  • Segment submission. Read what the participant submitted during a particular segment — the option they chose in a selection segment, the order they produced in a ranking segment, the text they entered in an input segment. A submission from a segment embedded in a chat is available during that chat (the embedded card runs alongside it): triggers and prompts in the parent chat can read it, but the value is empty until the participant actually submits, and the builder's pickers flag such variables as “available during segment”.
  • Segment data. Read data captured about a segment — how long the participant spent, how many messages they sent, whether they passed an attention check.
  • System-assigned. Read a value the platform sets automatically — the chamberline the participant was assigned to, their participant ID, the run identifier.
  • Aggregate. Compute a value across multiple participants — the mean of all communicators' pre-test scores in the same chamber, for example, or the modal political identification of the group. Aggregates are how a configuration can talk about the group, not just the individual.

A source can be qualified by a participant reference: by default a variable is read from the participant who is being configured (referred to internally as self), but it can also be read from another slot in the chamber (slot:1, slot:2, or slot:mediator). This is what makes it possible to address a fellow participant in an instruction or in a system prompt — for example: “you are about to chat with {slot:1:var.first_name}, who identifies as a {slot:1:var.party_id} voter.”

4.2.2 Per-option numeric values

A choice-type variable is, by default, a categorical label — “novice” or “expert”, “Democrat” or “Republican”. When a numerical contrast is also useful, each option in the variable's definition can carry an associated numeric value.

A typical case is a five-point self-rating scale. The variable's options might be labelled Very low / Low / Moderate / High / Very high, with numeric values 1 / 2 / 3 / 4 / 5. The label is what the participant sees and what is logged in raw form; the numeric value is what the configuration arithmetic uses when computing aggregates or threshold conditions. A condition like “this chamber only appears if the participant's expertise is ≥ 3” is expressed against the numeric value, not the label.

Numeric values are optional. A variable without them behaves as a pure categorical attribute.

4.2.3 Aggregate variables across non-human participants

When an aggregate variable is computed across the participants of a chamber, the researcher can choose whether non-human participants (LLM chatbots, scripted chatbots, and agents) are included or excluded from the aggregate. This setting matters in two common situations:

  • Confederated designs (one human, several non-human communicators) — usually the researcher wants aggregates to reflect humans only, since the non-humans are confederates.
  • Group-property analyses (e.g. mean expertise of the room) — the researcher chooses whether non-human participants carry an expertise value at all, and whether it contributes to the mean.

The default is to include only humans in aggregates; the alternative is configured on each aggregate variable definition. The same humans-only / include-all toggle applies regardless of which of the three non-human kinds is present in the chamber.

4.3 Variables as matching constraints

The first place a variable does work is during matching. There are three places in the configuration where a variable can constrain who is matched into what:

4.3.1 Chamberline eligibility

A chamberline can declare a filter — a condition expressed against variables that a participant must satisfy in order to be eligible for that chamberline. When the experiment's chamberline-assignment method is survey-based (see §1.2.2), the filter is the mechanism by which the assignment is made:

Chamberline "Pro-disclosure"   filter:  var.party_id == "Democrat"
Chamberline "Anti-disclosure"  filter:  var.party_id == "Republican"

A participant whose var.party_id is Democrat will be eligible for the first chamberline and not the second; the inverse holds for Republican participants. Participants who do not satisfy any chamberline filter are routed to a default chamberline if one exists, or terminated gracefully otherwise.

4.3.2 Slot requirements

Inside a chamber, each slot can declare a list of required properties — conditions that the participant filling the slot must satisfy. A two-slot chamber with the slot constraints

Slot 1   human communicator   requires:  var.expertise <= 2
Slot 2   human communicator   requires:  var.expertise >= 4

will pair each session into one novice and one expert. Matching does not begin for this chamber until two participants are waiting who jointly satisfy the slot constraints — a queued participant whose expertise is 2 can be matched into Slot 1, and a queued participant whose expertise is 5 can be matched into Slot 2, but two novices cannot fill the chamber on their own.

Slot requirements can reference any variable that has been resolved by the time the chamber begins. In practice this means anything captured in the global pre-survey, anything assigned by the system, and anything from earlier chambers in the participant's run.

4.3.3 Building one: a variable that assigns the pairing

The whole journey, in the builder, end to end. A counterbalance variable with two values is defined once, and each of the chamber’s two human slots then requires one of them. From that point matching will only seat a participant where their value fits, so every pair is one of each — and because the variable is counterbalance rather than random-pick, the draw leans toward whichever side is short.

Recorded by e2e/matching-variable.spec.js. The test drives a real browser against a real server and database and then checks what reached the API, so this recording cannot show a walkthrough that no longer works.

Three things in that recording are worth naming. The kind picker is where the eight kinds are chosen, and each card carries the one-line description of what that kind does — the documentation sits on the control rather than in a panel beside it. The slot’s Required Properties key field is a picker of the variables that resolve at that position, not a free-text box; a variable that is not yet available there is listed with the reason. And once the slots are wired, the variable’s own Used in panel reports gates 2 match slots in 1 chamber, so the dependency is visible from both ends.

To run it yourself: npm run demo:matching seeds the fixture and starts the app; npx playwright test e2e/matching-variable.spec.js walks it in a browser, checks what reached the database, and rebuilds the clip above.

4.3.4 Group-composition constraints

Slot-by-slot constraints are sometimes too weak to express a desired group property. Where the slot constraints care only about who fills each seat, a group-composition constraint cares about a property of the matched set as a whole. Typical examples:

  • The chamber requires a gender-balanced trio — at least one male, at least one female, no constraint on the third seat.
  • The chamber requires that the modal political identification of the trio is Democrat.
  • The chamber requires that no two members share the same self-reported expertise level.

Group-composition constraints are expressed against aggregate variables (3.2.3) and are evaluated against the candidate set during matching. A candidate matching is admitted only if the corresponding aggregate, computed over the candidates, satisfies the constraint.

4.4 Variables as visibility gates

A second use of variables is to gate what a participant sees, not just who they see it with. A chamber can declare a visibility condition against one or more variables. The chamber is included in the participant's run only if the condition is true at the moment the chamberline would otherwise enter that chamber.

This is the canonical mechanism for branching designs:

  • A practice chamber that is shown only to participants whose pre-test score was below threshold.
  • A debriefing chamber for the high-anonymity condition that does not exist in the low-anonymity condition (an alternative to two parallel chamberlines, useful when most of the experiment is identical between conditions).
  • A post-task survey that is shown only to participants who actually completed the preceding chat (skipped for those whose chamber was terminated early).

Visibility conditions take the same form as slot constraints: a boolean expression against variable values, with the standard comparison operators and any / all connectors. Crucially, visibility is evaluated just before the chamber would begin, not once at the start of the run. This means that variables produced during the run — by an earlier chamber's segment submission, by a chamber post-survey — are usable as gating conditions for later chambers.

When a chamber is gated out for a participant, the participant skips directly to the next chamber in their chamberline. The chamberline itself does not change.

4.4.1 Slot requirements vs visibility conditions

A natural question at this point: a chamber whose every slot has a requires constraint already excludes participants who cannot fill any seat — they will never be matched. Why, then, does Carrier also offer a separate visibility condition at the chamber level? Don't they do the same job?

They overlap in one specific case and diverge in the others. The rule that governs the overlap is:

Implicit skip rule. If every human slot in a chamber has a non-empty requires constraint, and the participant satisfies no slot's constraint, then the chamber is silently skipped for that participant — exactly as if it had a failing visibility condition.

The skip is implicit: there is no separate visibility expression to write. The chamber is treated as not part of the participant's run, and they advance to the next chamber. This is what you observed.

Three scenarios make the distinction concrete.

Scenario A — Single fully-constrained slot. A solo-participant chamber whose one human slot requires var.condition == "treatment". A control-condition participant cannot fill it; the implicit skip rule fires; the participant moves to the next chamber. Here, slot requirements alone are sufficient — adding a chamber-level visibility condition would be redundant.

Scenario B — Mixed open and constrained slots. A two-slot chamber where one slot requires var.role == "expert" and the other slot has no constraint:

Slot 1   human communicator   requires:  var.role == "expert"
Slot 2   human communicator   requires:  (none)

A novice participant can fill Slot 2, so the implicit skip rule does not fire — the chamber is visible to them, and they will join it paired with an expert. If you intended this — “novices and experts meet here, with the expert always seated in Slot 1” — that is the correct behaviour and you should add nothing further. But if you intended the chamber to exist only for experts (with Slot 2 reserved for another expert who happens to be unconstrained because of how you defined the slot), the implicit skip rule will not save you. A novice will silently enter. To get a true gate here, add a chamber-level visibility condition var.role == "expert". Slot requirements cannot express this on their own.

Scenario C — Chamber-level gate plus slot-level role assignment. A debriefing chamber that should exist only for participants who completed a treatment chamber upstream, and within that chamber should pair a “writer” participant with a “reviewer” participant:

Chamber "Debrief"
   visible if:  var.completed_treatment == true
   Slot 1   human communicator   requires:  var.debrief_role == "writer"
   Slot 2   human communicator   requires:  var.debrief_role == "reviewer"

Both layers are necessary. The chamber-level visibility expresses who belongs in this chamber at all; the slot requirements express which seat each participant takes once they are here. Trying to collapse the gate into a slot constraint (for example, by adding var.completed_treatment == true to both slots' requires) works only by coincidence — the implicit skip rule fires because every slot is constrained — and it conflates two distinct intents. If a future edit relaxes Slot 2's requires to allow walk-ins, the chamber suddenly becomes visible to participants who never completed the treatment, and the gate is silently gone.

This last point is the deeper reason the two systems are kept separate. Visibility conditions express intent explicitly; slot requirements express it emergently. When the only way a chamber is skipped is “all slots happen to be constrained and the participant happens to satisfy none,” that skip is a side effect of a configuration that was written for a different reason. Edits to the slots — adding a slot, opening one up, retitling roles — can quietly remove the gate. An explicit visibility condition survives those edits and is auditable as a routing rule on its own.

A compact way to choose between them:

You want…Reach for
A chamber that exists only when seat-level requirements alone suffice to exclude the wrong participants, and you do not anticipate slot edits relaxing this.Slot requirements alone.
A chamber that should be hidden for some participants even though other participants would still find an open seat in it.A chamber visibility condition.
A chamber that gates a population and role-assigns within that population.Both: visibility condition for the gate, slot requirements for the seating.
A routing rule that should be self-documenting and resilient to slot edits.A chamber visibility condition (in addition to whatever slot requirements you also want).

In short: when slot requirements happen to act as a gate, treat that as a convenient side effect, not as the gate itself. If the chamber is meant to be conditional on a participant property, say so with a visibility condition.

4.5 Variables in instructions and prompts

The third use of variables is the most pervasive: interpolation into the natural-language content shown to or used by participants. Any place in the experiment where text is shown to a participant or sent to an AI is also a place where variables can be injected.

The interpolation syntax is {{var.<key>}} for a variable read from the current participant, and {{slot:<n>:var.<key>}} or {{slot:<role>:var.<key>}} for a variable read from a fellow slot. A few worked examples:

Instruction segment text

Welcome, {{var.first_name}}. In the next ten minutes you will discuss
climate policy with two other participants. The participant on your
left has expertise level "{{slot:1:var.expertise_label}}"; the
participant on your right has expertise level
"{{slot:2:var.expertise_label}}".

System prompt for an LLM mediator

You are facilitating a discussion among three participants. Their
self-identified political positions are: {{slot:1:var.party_label}},
{{slot:2:var.party_label}}, and {{slot:3:var.party_label}}. Adjust
your tone to be welcoming to all three positions; do not take sides.

Survey question stem

Earlier you said your most important consideration was
"{{var.top_value}}". On the slider below, indicate how strongly you
still feel that this is your most important consideration.

Two things to note. First, interpolation happens at the moment the text is needed — the same instruction segment used in a chamber that appears twice will be re-interpolated each time, with the latest variable values. Second, an undefined variable interpolates to the empty string by default, but the variable definition's default field can be used to specify a fallback. Where it matters, configure a default.

4.5.1 Per-option text vs per-option value in interpolation

Recall that a choice-type variable can carry both a label and a numeric value for each option (3.2.2). When interpolating into text, two conventions matter:

  • {{var.expertise}} interpolates the key of the chosen option (“low”, “moderate”, “high”).
  • {{var.expertise_label}} interpolates the display label of the chosen option (“Low”, “Moderate”, “High”).
  • {{var.expertise_value}} interpolates the numeric value of the chosen option (e.g. 2, 3, 4).

For a five-point Likert variable, the three forms give you the same information at three different levels of formality. Choose by context: instructions to participants benefit from the display label; an LLM system prompt typically benefits from the numeric value or the key.

4.6 Variables in trigger conditions

A fourth use of variables is in trigger conditions (§5): a bot's keyword trigger, time trigger, or message-count trigger can be qualified by a check on a variable. This makes triggers conditional on participant attributes — a bot might only respond to keyword “X” if slot:1:var.condition is “treatment”, for example. The full mechanics of trigger conditions are covered in §5.2; here it is enough to know that the same var.<key> references work inside trigger conditions as work elsewhere.

4.7 Builder walkthroughs

🎞 Builder walkthrough — Defining a variable

The researcher opens Variables, clicks “+ Add variable”, and gives it the key expertise and a description. They set Kind to alias — the kind that reads its value from somewhere else in the study — set the data type to number, and use “Pick source…” to point it at a submission source. “Apply Changes” commits it, and reopening the manager shows expertise in Experiment variables. Variables are one of eight kinds (constant, alias, aggregate, concat, expression, random-pick, counterbalance, llm-processed); there is no “single-choice” kind, and a variable does not generate _label / _value companions.

🎞 Builder walkthrough — Using variables for slot matching

The researcher drags two human participants into a chamber’s Communicators lane, then clicks the first slot and adds a required property under Required properties (matching): var.expertise ≤ 2. They click the second slot and add var.expertise ≥ 4. Only participants whose properties satisfy all of a slot’s conditions can fill it, so the chamber now pairs a novice with an expert.

🎞 Builder walkthrough — Gating a chamber on a variable

The researcher selects a chamber, renames it “Remedial Explainer”, opens its Visibility Conditions panel and clicks “Add Condition”. They enter the key var.pretest_score, choose the < operator and the value 0.5. Only participants satisfying every condition on the chamber will see it; leaving the list empty shows it to everyone.

Triggers — the rule system that drives the behaviour of every non-human participant in the experiment, the final piece of the four-system picture — are the subject of §5.

Part I · §5. Triggers

The rule system that drives every non-human participant's behaviour. A trigger combines a condition, a response, and optionally an action.

5.1 Why triggers exist

The previous three chapters answered the questions what shape is the experiment, who is in it, and what do we know about them. They left one question open: how do the non-human participants behave? A scripted chatbot, an LLM chatbot, and an agent are all just slots in a chamber until something tells them when to speak and what to say.

That “something” is the trigger system. A trigger is a rule that combines:

  • A conditionwhat has to be true before the trigger fires.
  • A responsewhat the bot says if the trigger fires.
  • Optionally, an actionwhat else the bot does if the trigger fires (disable a participant's input, prompt a specific person, transition the chamber, and so on).

You can think of a non-human participant's configuration as a book of rules: an ordered list of triggers, each with a condition–response–action triple. When something happens in the chamber, Carrier consults the book in order, evaluates the conditions against the current state of the chat, and fires the rules that match. This applies equally to deterministic scripted chatbots (whose responses are pre-written sentences), to LLM chatbots (whose responses are generated by a language model on the fly), and to agents (whose responses are generated by a language model that has consulted its tools first).

A useful analogy from the methods literature: a trigger is to an interactive bot what a confederate script is to a confederate participant — a list of contingent rules that say “when X happens, do Y”. Carrier's contribution is to make the list expressive enough to capture the contingencies real conversations contain, and machine-readable enough to be replayed identically across sessions.

5.2 The trigger model: condition → response → action

A single trigger has the structure:

Trigger
Condition
When does it fire?
Response
What does it say? (scripted or LLM)
Action(s)
What else does it do? (optional)
Modifierspriority · cooldown · max-fires · probability · chain target · variable filters
The structure of a single trigger: a condition, a response, optional actions, plus shared modifiers.

The next three sections expand each of these three parts. The modifiers are covered together in §5.6.

5.3 Conditions: when a trigger fires

The condition of a trigger is the part most worth understanding well, because it determines whether the rule ever runs. Carrier supports a catalogue of condition types organised by what they listen for: message content, time, message counts, sequences, participant events, and aggregate states.

5.3.1 The catalogue of condition types

TypeListens forTypical use
keywordA configurable word or phrase in a chat messageRespond when “climate” is mentioned; greet on “hello”.
regexA regular expression match in a chat messageRecognise URLs, profanity, structured statements like “I disagree with X”.
timeA delay (ms) from chamber or segment startSend an opening prompt 5 s in; remind participants of the time after 8 min.
message-countA total count of messages in the chatroomIntervene every N messages; introduce a summarisation at message 20.
participant-message-countA count of messages from a specific participantDetect that one person has dominated; reward an under-contributing participant.
sequenceAn ordered series of keyword matches“First X is said, then Y” — useful for staged conversation steering.
participant-actionA participant event such as join, leave, idleGreet on join; flag a drop-out; chain into a backup-bot prompt.
after-bot-messageA specified bot has just sent a messageCross-bot chaining; staged multi-bot interactions.
event-monitorAn arbitrary chatroom eventCatch dashboard interventions, segment transitions, embedded-child completions.
chain-only(Passive) Only fires when another trigger chains to itBuilding multi-step responses.
llm-drivenThe trigger asks an LLM to decide whether it should fire and what to sayOpen-ended judgement triggers — see §5.4.2.
periodic(Mediator only) Fires at a fixed intervalRegular check-ins, repeated summaries.
aggregate(Mediator only) Fires after N messages have accumulated within a time windowBatched synthesis.
topic-detected(Mediator only) Fires when a topic pattern is detectedTopic steering.
activity-timeout(Mediator only) Fires after a period of inactivityIdle prompting.
participant-count(Mediator only) Fires when the active participant count crosses a thresholdReacting to departures or arrivals.
discussion-phase(Mediator only) Fires at the start, middle, or end of a chamberPhase-appropriate facilitation.

Two practical notes:

  • Most of these conditions presuppose a chat segment. Keyword, regex, message-count, sequence, and the mediator-only message conditions all listen to the conversation; if the chamber is currently in a slide or a survey segment, they are silent. Time and participant-action conditions, by contrast, can fire from any segment.
  • The mediator-only types (periodic, aggregate, topic-detected, activity-timeout, participant-count, discussion-phase) are enforced as mediator-only by both the builder and the runtime. The builder hides them from non-mediator agents' trigger pickers, and the runtime only initialises and evaluates them for bots with role === 'mediator'. Attaching one of these types to a communicator or processor (e.g., by hand-editing the JSON) will silently never fire.

5.3.2 Modifiers shared by all condition types

Every trigger, whatever its condition type, can be qualified by a small set of modifiers:

ModifierMeaning
Case sensitivityFor keyword and regex conditions, whether matching is case-sensitive (default: insensitive).
Match modeFor multi-value conditions, whether any of the values is sufficient (the default) or all are required.
Sender filterRestrict matching to messages sent by humans, by a specific participant, or by a participant of a specific role. The default is to consider every sender.
Variable filterA boolean expression over var.* references; the trigger does not fire unless the expression is satisfied. This is how a trigger becomes condition-dependent — for example, “only fire this prompt if the current speaker's var.condition == 'treatment'”.

The variable filter is especially powerful in combination with §4: a single set of triggers, attached to a single bot template, can produce qualitatively different behaviours in different chamberlines purely on the basis of the variables that are true of the participants in front of it.

5.3.3 An aside: triggers, segments, and the “active segment” filter

Most trigger configurations include an active-segments filter — a list of segment IDs (within the chamber) during which the trigger is eligible to fire. Leaving the list empty means fire in any segment. Restricting it to a specific segment — typically a chat segment — is the canonical way of writing “this rule only applies during the deliberation, not during the survey at the end”. The filter also accepts embedded child segments, which is how a trigger can be made to fire only while a particular embedded vote overlay is open.

This is a small detail, but it is the source of a very common pitfall: a trigger that “doesn't seem to fire” is often a trigger whose active-segment filter excludes the segment the chamber is actually in.

The filter has a second consequence that matters more than it looks: a time trigger's clock restarts at every segment entry. There is no chamber-wide clock. When the chamber moves into a segment, Carrier arms every time trigger scoped to that segment from zero; when the segment ends, any timer that has not yet fired is dropped. Three things follow, and each of them has bitten somebody:

  • A delay longer than the segment lasts never fires. A trigger set to 12:00 in a segment that auto-advances at 10:00 arms, waits, and is discarded — silently, at run time.
  • A trigger with an empty active-segments list is armed in every compatible segment, and its countdown restarts in each one. “Fire once at 30 seconds” becomes “fire at 30 seconds into each of three segments” unless Max triggers says otherwise.
  • Two delays are only comparable within one segment. Ten seconds into a late segment happens after forty-five seconds into an early one, however the two numbers sort.

Idle detection keeps a different clock again: its value is measured from the last message, not from segment entry, so it is the earliest the trigger can fire rather than when it will — and in a busy chat it may never fire at all. The Timeline view (§5.6.2) draws all of this, and reports the cases that cannot work.

5.4 Responses: what the bot says

When a trigger's condition fires, the response tells Carrier what message to send. There are two fundamentally different kinds of response.

5.4.1 Scripted responses

A scripted response is a pre-written sentence (or a list of sentences from which Carrier picks one at random). It is the only kind of response a scripted chatbot can produce, and it is also available to LLM chatbots and agents for cases in which the researcher wants exact-text control.

Configuration is minimal:

  • Message — a single fixed string, or a list from which the platform draws (uniformly, by default).
  • Delay — an interval before the message is actually sent, simulating typing or thought.
  • Probability — the chance that the trigger fires at all when its condition is satisfied (default 1.0). Used when researcher wants stochastic intervention.

Scripted responses are deterministic: identical inputs produce identical outputs. Two participants who encounter the same conversation in the same condition will receive the same scripted response, with the same delay, in the same order. This makes them the right choice for studies in which experimental control matters more than naturalism — confederated-AI conformity studies, attention-check probes, scripted “noise” injections, and so on.

5.4.2 LLM-generated responses

The alternative is to let an LLM generate the response on the fly. A language-model response is produced by sending a request to a language-model provider (OpenAI, Anthropic, Google, or a compatible service) with a prompt assembled from:

  • The system prompt of the bot (its personality, role, instructions).
  • A configurable amount of chat history as context (none, the last N messages, or the full history — see §3.5.5 for the analogous setting on processors).
  • Variable interpolations (§4.5) — the participant's name, condition assignment, current state.
  • Optional chain steps — a sequence of LLM calls in which each step's output becomes part of the next step's input. Chains are how a single trigger can implement plan, then critique, then rewrite behaviour.

The response of an LLM-driven trigger is therefore open-ended: the model can produce any text that satisfies its prompt, modified each time by the current state of the conversation. Where scripted responses trade variation for control, LLM responses trade control for naturalism. They are the right choice for studies in which the realism of the bot's behaviour is itself part of what is being tested.

A note on the response format: Carrier's LLM responses follow a structured JSON shape with three fields — content (the message text, or null to remain silent), rationale (a brief justification, logged but not shown), and an optional actions list (the topic of §5.5). The structure exists so that the AI can decide “remain silent” without producing a blank message, and so that researchers can audit why the AI chose to speak after the fact.

5.4.3 Choosing between scripted and LLM responses

A short heuristic that holds in most studies:

If you want…Prefer…Because…
Exact replicability across sessionsScriptedSame input → same output, always.
Naturalistic, varied bot behaviourLLMThe model adapts to what the participant actually said.
A confederate that does not break characterScriptedLLMs can drift; scripted text cannot.
A facilitator that responds to topics in real conversationLLMResearcher cannot pre-write every possible response.
Strict auditing of bot speechScriptedThe set of possible utterances is finite and visible.

The two are not mutually exclusive on a single bot. A bot template can mix scripted triggers (for greetings, attention checks, and exit messages) with LLM triggers (for the bulk of the discussion), routing each contingency through whichever response type fits best.

5.4.4 Response logic and silence

For LLM chatbots and agents, two additional configuration parameters govern when not to speak. They sit alongside the trigger list and are sometimes more important than it:

  • triggerOnFirstMessage — whether the bot is allowed to make the first move (greet, open the discussion) before any human has spoken.
  • respondToEveryMessage — whether the bot should attempt to respond after every incoming message, or only when one of its triggers explicitly says so.
  • respondOnMention / mentionKeywords — whether the bot only responds when its name (or a configured list of keywords) appears in the chat.
  • initialSalute — a configured opening message sent on chamber start, regardless of triggers.
  • timeoutTrigger — a “fail-safe” prompt the bot sends after a configured silence interval if no other trigger has fired.

These five toggles, together with the trigger list, are how a researcher tells an LLM chatbot or agent the difference between a talkative configuration (“respond whenever spoken to, and also if no one has spoken for a minute”) and a reserved one (“only respond when explicitly addressed by name”).

5.5 Actions: what else the bot does

A trigger's action field — present on every trigger, optional in most cases, central for mediators — is the side effect that fires alongside (or instead of) a response message. Actions are what allow a bot to do something to the chat rather than just in the chat.

Carrier exposes actions through four distinct mechanisms, which are easy to confuse but do different things. Read this list once before the subsections below:

MechanismAvailable toWhat it doesDecided when?
Scripted actionsScripted chatbots, and any LLM bot whose trigger has a baked-in actionApply a Carrier intervention (disable chat, prompt, highlight, …) with parameters fixed in the trigger configurationAt configuration time
LLM-chosen Carrier actionsLLM chatbots and agents acting in a mediator roleThe model emits a structured response whose actions field selects which interventions to fire and with what parametersAt each turn, by the model
Segment-submission actionsAll non-human participants (scripted or LLM) configured as communicators in an interactive segmentSubmit a vote, a ranking, or a free-text answer alongside humans — optionally counting toward the segment's completionAt configuration time, with the submitted value optionally resolved per session
Agent built-in toolsAgents only (Claude Agent API)The Claude Agent autonomously reads files, runs commands, or browses the web to gather information for itself before producing its messageAcross multiple internal steps within a single turn, by the agent

The first two are about the bot acting on the chamber — they affect what participants see and what they are allowed to do. The third is about the bot acting as a participant within an interactive segment — its submission joins the humans' in the segment's results. The fourth is about the agent informing itself — it affects what the agent knows when it speaks, but its only externally visible output is still the eventual chat message. Sections 4.5.1 to 4.5.4 cover each in turn; 4.5.5 helps choose among them.

5.5.1 Scripted actions

A scripted action is an unambiguous instruction baked into the trigger configuration: “when this rule fires, disable participant X's input until either 60 s have passed or all other participants have responded, whichever comes first.” The action is part of the rule. The bot's job is to fire the trigger; the action's effect on the chat is deterministic, predictable, and visible in the configuration before the experiment runs.

Carrier's primary catalogue of scripted-action types — drawn from §3.4.3 — is:

  • disable_chat — temporarily prevent a participant from sending messages, with a composite release condition.
  • enable_chat — explicitly re-enable input.
  • prompt_participant — send a private prompt visible only to a specific participant.
  • highlight_message — visually highlight a past message for a configurable duration.
  • request_attention — trigger a visual or auditory cue at a specific participant.

There are also a small number of chamber-level scripted actions used less frequently:

  • advance_segment — force the chamber to move to its next segment immediately.
  • terminate_chamber — end the chamber early.
  • set_variable — set a variable on a participant or on the chamber, useful for cascading state into later chambers' visibility conditions (§4.4).

Scripted actions are how a confederated-chatbot conformity study can be made literally identical across participants: the rules say “disable the human's input for 30 s after the first confederate's message” or “highlight the confederate's response in green for 5 s”, and the chat then unfolds with the same scaffolding in every session.

5.5.2 LLM-chosen Carrier actions

The alternative — and the more powerful in open-ended designs — is to let the language model itself decide which Carrier interventions to fire. This is the mechanism by which an LLM-driven mediator can act contextually: if the model judges that one participant has been quiet for too long, it can choose to issue a prompt_participant; if it judges that the conversation has drifted off-topic, it can choose to send a styled broadcast.

The rule does not pre-specify the action's parameters. Instead, the trigger fires an LLM call (with the bot's system prompt and chat context), and the model returns a structured response of the form

{
  "content":   "<broadcast text, or null>",
  "rationale": "<one-sentence justification, logged>",
  "actions":   [ { "type": "prompt_participant",
                   "target": "slot:2",
                   "message": "What do you think?" },
                 { "type": "disable_chat",
                   "target": "slot:1",
                   "release_conditions": { ... } } ]
}

The model's selection of actions is constrained by the bot's configured action vocabulary (researchers can choose to expose only a subset of action types to the model) and by the bot's role: a communicator's action vocabulary is small (no disable_chat); a mediator's is large. This mechanism is available to any LLM-driven mediator — both LLM chatbots and agents acting in a mediator role.

This is the most expressive — and also the least replicable — corner of Carrier intervention. The trade-off is real: an LLM-chosen action set gives you a facilitator that adapts, but the cost is that two sessions of the same condition may diverge in their facilitation. The right choice depends on what the experiment is testing.

5.5.3 Segment-submission actions

The interactive segments introduced in §2.2selection, ranking, and input — collect an answer from every participant in the chamber. A non-human participant configured as a communicator can be set up to submit alongside the humans, the way a confederate in a behavioural lab study would. The submission is attached to the bot's participant identity, surfaces in the segment's results, and appears in the exported data with the same shape as a human's answer.

What the bot submits depends on the segment type. For a selection, it is one or more option indices (or, in slider mode, a numeric value within the slider range). For a ranking, it is a permutation of the item indices. For an input, it is a string — or a number, for the numeric input subtype. In every case the submission shape mirrors the human's, so cross-participant aggregates and downstream variable expressions (§4) read bot and human submissions uniformly.

Where the value itself comes from is configured per trigger. Carrier supports four data modes:

  • Static — the researcher hardcodes the value. The bot submits exactly that, every session. Useful when the value is the manipulation.
  • Random — the value is drawn from a configured pool, optionally weighted. For selection and ranking the pool is the segment's own options; for input it is a researcher-provided list of candidate strings, since “random text” without an anchor is not meaningful.
  • Referenced — the value is derived from what humans have already submitted in the same segment. Strategies include match the first human's answer, match the majority option, oppose the majority, and pick a different option at random. For selection and ranking all four strategies translate naturally; for input, only verbatim copy of a target human's text is well-defined.
  • LLM-generated — the bot calls the language model with the segment's prompt, the chamber's chat context, and a JSON-shaped schema instruction. The model returns a structured submission (an index, a permutation, or a string). This is the most flexible mode and the one most often appropriate for input.

Three submission-metadata flags refine how the bot's answer is treated by the rest of the chamber. countTowardTotal decides whether the bot's submission contributes to the “everyone has answered” check that releases the chamber forward — set to false if the bot is a passive confederate that shouldn't gate progression. showInResults decides whether the submission appears in any aggregated results display participants see at the segment's end. tagAsBot decides whether the submission is visually marked as bot-origin in the UI; the default is false, so the bot is indistinguishable from the humans, which is usually what a confederacy design requires.

input deserves a brief separate note. Free-text submissions raise sharper measurement-validity questions than categorical or ordinal ones: a bot's prose is harder to compare across sessions than a chosen option, and small differences in wording can have outsized effects on the humans who read it. The recommended pattern when using input with LLM-generated mode is a tightly scoped system prompt, the raw model output logged in the export for audit, and tagAsBot: true whenever participants will read the submission and the design should be transparent about its bot origin.

A worked example: a confederacy study runs a selection segment (“Which option do you find more compelling?”) followed by an input segment (“In one sentence, why?”). Two configured bots use static mode in the selection segment, picking option A in every session; in the input segment they switch to LLM-generated mode under a system prompt instructed to elaborate on option A in plain, peer-like language. The participant sees four answers in each segment — two human, two bot — and across sessions the design holds: the bots' selections are reproducible to the index, and the bots' free-text answers vary in surface form while cohering around the same content.

5.5.4 Agent built-in tools (Claude Agent API)

The fourth mechanism is internal to the agent itself, and only applies to participants of the agent type (§ note on non-human participants). A Claude Agent has access to a vocabulary of built-in tools provided by the Anthropic Agent API — tools for reading files in a configured document area, executing small commands, and browsing the web. Before producing the message it will eventually send to the chat, the agent's underlying model can autonomously decide to invoke one or more of these tools, examine the results, and iterate.

A typical pattern, from the chamber's point of view:

  1. A trigger fires that asks the agent to respond.
  2. The agent reads (silently) the section of the configured study brief that is relevant to the conversation so far.
  3. The agent runs (silently) a short check against a dataset to confirm a number it is about to cite.
  4. The agent produces a single chat message that quotes the relevant passage and reports the number.

Steps 2 and 3 are internal to the agent. Participants in the chat see only step 4 — a single grounded reply. The agent's internal trace (which tools it called, with what arguments, and what results it got back) is preserved in the exported data for the researcher to audit, but it is not shown to other participants.

The two design decisions a researcher makes for an agent are therefore:

  • Tool scope. Which of Claude's built-in tools to enable, and — for the file-reading tool — what document area to expose. The narrower the scope, the more focused the agent's contributions; the wider the scope, the more open-ended.
  • Step budget / latency. Agents take longer per response than LLM chatbots, because they loop. Configure a maximum step count (or wall-clock budget) so the agent does not hold up the chamber. The platform shows a “thinking” indicator while the agent is in its loop.

Two notes on relating 4.5.4 to 4.5.2:

  • An agent acting as mediator can fire LLM-chosen Carrier actions (4.5.2) and use its built-in tools (4.5.4) in the same turn. The former affects the chat; the latter informs the model.
  • The two channels are logged separately in the exported data. The Carrier action log records intervention actions; the agent trace records tool invocations.

5.5.5 Choosing among the four mechanisms

If you want…Prefer…
Identical turn-taking enforcement across sessionsScripted actions (4.5.1) with fixed release conditions.
Facilitator interventions that respond to what was actually saidLLM-chosen Carrier actions (4.5.2), constrained to a small vocabulary.
To study the effect of a particular intervention patternScripted — the intervention is the manipulation, so it must be uniform across participants.
To study whether an automated facilitator helps at allLLM-chosen — the manipulation is the model's judgement, so it must vary contextually.
A non-human participant that votes, ranks, or writes alongside humansSegment-submission actions (4.5.3) — pick a data mode according to how reproducible the submission needs to be.
A confederacy condition where the non-humans submit identical answers in every sessionSegment-submission actions in static mode (4.5.3).
A non-human participant that cites the study material accuratelyAn agent with file-reading enabled over the materials (4.5.4); the agent quotes what it reads.
A non-human participant that fact-checks live during conversationAn agent with web-browsing enabled (4.5.4).
A non-human participant that runs computations over a dataset before answeringAn agent with code execution enabled (4.5.4).

5.6 Composing triggers

5.6.1 Reading a script: the three views

A chamber's agents are authored together in the Script Builder, which shows the same set of triggers three ways. They are not decoration: each answers a question the others cannot.

ViewAnswers
Flow What does each agent have? One column per agent in the chamber, its triggers in order, the fallback it falls back to, and the selected trigger inspected on the right. Drag a type from the rail onto a column to add one; double-click a node for the full editor.
Storyboard What would a participant see? The whole chamber as one read-through, chaptered by segment, with the chained and follower triggers placed after whatever sets them off. Triggers with no fixed time are grouped after the timed ones in each chapter.
Timeline When does it all happen? See below.

5.6.2 The Timeline view, and what it catches

The Timeline lays the chamber's segments out as bands, one lane per agent, and places a mark for every segment a trigger is armed in. Each band carries its own ruler starting at 0:00, because that is the clock the runtime keeps (§5.3.3). A trigger with no segment scope therefore appears once in every compatible band — which is what actually happens to it.

  • A filled pin is a scheduled time, straight from the trigger's delay.
  • A hatched bar is a reactive trigger — keyword, regex, after-agent-message. It has no position in time, only a window in which it is armed.
  • A bar with an open right edge is an idle timeout: earliest that time, then any time after, or never.
  • A dashed violet bar is a chain-only trigger, which has no condition of its own and runs when something else chains to it.
  • A grey pin in an amber hatch is armed past the end of its segment. It will not run.

Beneath the bands, a panel reports what the schedule actually does. Every row is computed from the configuration alone — no run required — and each one is a failure that is otherwise invisible until a pilot:

  • a time trigger armed past the end of the segment it runs in;
  • a trigger with no segment scope that silently re-arms in every segment;
  • a chain-only trigger that nothing on any agent chains to;
  • a trigger scoped to a segment whose type cannot run it — a response trigger parked on an instruction segment, say.

The same checks run on Deploy, so a script that cannot work is reported before participants meet it rather than after.

5.6.3 Composition modifiers

Real bots rarely consist of a single trigger. The composition surface lets you make the rules interact:

  • Priority. Each trigger has a numeric priority. When multiple triggers' conditions are satisfied by the same event, Carrier evaluates them in descending priority order and fires the highest-priority match. Use priority to handle exceptions: an attention-check trigger with high priority can override a greeting trigger that would otherwise also fire.
  • Cooldown. A trigger can specify a minimum interval that must elapse between successive firings. Used to prevent a bot from spamming when a condition stays true for a while.
  • Max fires. A trigger can cap how many times it ever fires per chamber (e.g. an introduction trigger that only fires once).
  • Probability. A trigger can fire with a configured probability less than 1.0 when its condition is satisfied, producing stochastic interventions.
  • Chain target. A trigger can specify another trigger's ID to fire after it completes. This is how multi-step behaviours are built: trigger A says something, then chains to trigger B which fires a follow-up question after a delay, which chains to trigger C which records the response. The chained trigger's condition can be chain-only, which means it can only ever fire by being chained — useful for keeping cascading sequences out of the normal trigger queue. A chain target is resolved across every agent in the chamber, not just the one that owns the chaining trigger, so one agent can hand off to another — which is why the Script Builder shows all of a chamber's agents side by side, and draws the chains between them.

Together, these modifiers turn a flat list of triggers into a directed graph of contingent behaviour. Most experiments need only flat lists; the modifiers are there for designs that demand them.

5.6.4 Variable conditions in triggers (again)

The variable-filter modifier from §5.3.2 deserves a second mention here because of its compositional consequences. A single trigger list with variable filters

TRIGGER 1   if var.condition == "treatment", respond with X
TRIGGER 2   if var.condition == "control",   respond with Y

is functionally equivalent to two bots, one per condition, with one trigger each. Whether to write the contrast as “one bot with two filtered triggers” or “two bots, one per condition” is a design decision: the former keeps the experiment shorter and easier to read; the latter is sometimes clearer when the two conditions differ in many small ways.

5.7 Builder walkthroughs

🎞 Builder walkthrough — Adding a keyword trigger to a scripted communicator

Triggers are per-chamber, so the researcher places the agent in a chamber first and opens its Script Builder. They drag the Keyword Match template onto the agent’s flow, which opens the trigger editor: keyword climate, a response message, a 1500 ms delay and a probability of 0.7. Saving drops the trigger into the flow between Segment start and the agent’s Fallback, labelled with its condition type. The fallback panel at the foot of the column shows the reply the agent gives when nothing matches, or says so when none is set.

🎞 Builder walkthrough — Chaining two triggers

In the Script Builder the researcher first drags in a Chain Only trigger — one that never fires on its own — and gives it the response “Thanks for sharing.” They then drag in an Event Monitor, set its Activate On mode, and point its On Success → Trigger ID at the chain-only trigger. Both now sit in the agent’s flow, the second reachable only through the first. (Chaining is expressed through the event monitor’s success/failure targets and through LLM-mode deactivation, not as a generic field on every trigger.)

🎞 Builder walkthrough — An LLM-driven trigger with action vocabulary

The researcher drags the LLM Mode template onto an agent’s flow in the Script Builder. The trigger editor asks when the agent enters LLM mode (Activate On — here Segment Start), for the system prompt (“facilitate a balanced discussion…”), the provider and model, who it responds to (Target — here humans only), and how it leaves LLM mode again (Deactivation, which can itself chain to another trigger). The saved node shows the whole rule in one line: llm-driven: segment-start → LLM (timeout).

This concludes Part I. The four systems — chamberlines/chambers/segments, roles, variables, and triggers — together define everything a Carrier experiment can express. Part II turns to running an experiment built with them.

Part II · §6. Running and monitoring experiments

Part II is short by design. Most of what makes Carrier worth using is in Part I; what follows is the day-to-day mechanics of running a study built with the four systems above.

6.1 The experiment lifecycle

Every experiment moves through a small lifecycle. Its status field — visible at the top of the builder and on the dashboard — takes one of five values:

StatusMeaning
DraftThe experiment is being edited. Participants cannot enter it.
ActiveThe experiment is open. New participants who visit the URL begin a run.
PausedNew participants are blocked, but existing runs continue. Use during pilots when you want to freeze enrolment without disrupting in-progress sessions.
CompletedThe experiment is closed. No new runs; existing data remains exportable.
ArchivedThe experiment is hidden from the main dashboard listing. Data remains exportable.

The transition from Draft to Active is the activation step. The builder will refuse to activate an experiment that has obvious gaps — no chamberlines, an unfilled bot template, an invalid variable reference — but it will not catch every error. Pilot every experiment against yourself (and ideally a colleague) before opening it to real participants.

6.2 The dashboard at a glance

The dashboard is the experimenter's command surface during a live experiment. It has four panels, of which the first three are tightly coupled.

Active sessions. A list of every participant currently in a run, with their current phase (initialisation / identity setup / global pre-survey / chamber line execution / global post-survey / completed), the chamberline they were assigned to, and the index of the chamber they are currently in. Clicking a participant opens a per-participant detail view.

Matching queue. A list of every participant currently waiting to be matched into a chamber. Each entry shows the chamber the participant is waiting for, how long they have been waiting, and what slot constraints (§4.3) need to be satisfied for them to be admitted. A queue that grows steadily during an experiment is the symptom of a constraint that is too tight.

Alerts. A rolling list of events that warrant attention: disconnects, long waits, drop-outs, and idle participants. The dashboard surfaces these in priority order; experimenters typically watch this panel rather than the others.

Chatrooms. A list of every active chatroom (matched chamber). Each entry can be opened to show the live transcript, the broadcast log, the action log, and the processor-interaction log. This is the place to watch a chamber unfold in real time.

The dashboard auto-refreshes; no manual refresh is required.

6.3 Live monitoring and intervention

Three kinds of intervention are available from the dashboard during a live session.

Pause a participant's run. Halts the run at its current phase. The participant sees a paused indicator; segments and chamber timers do not advance. Resume with a single click. Use when a participant has hit a problem you want to debug before they continue.

End a participant's run. Terminates the run with a configurable completion message. The participant is shown the message and the global post-survey is skipped (unless explicitly forced). Used for participants who cannot continue — disconnections that will not heal, attention-check failures, withdrawal requests.

Host-advance a segment. Forces a segment whose transition mode is host (see §2.5) to advance for the chamber. Used during pilots to step a chamber through its timeline without waiting for timers or for participant clicks.

A fourth, lighter intervention — broadcast a message into a chatroom — is available from the chatroom detail view. The message appears in the chat as an experimenter announcement. Use sparingly: every dashboard broadcast is recorded in the chat transcript, so it becomes part of the dataset.

6.4 Exporting data

Data export is the final step of an experiment. It is available from the experiment detail page and the dashboard.

Two parameters control what comes out:

ParameterChoicesEffect
FormatJSON · CSVThe serialisation of the export. JSON preserves nesting; CSV flattens.
TypeAll · Participants · Chatrooms · ResponsesWhat subset of the experiment to include.

The four export types correspond to four levels of granularity:

  • Participants. One row per participant per chamberline assignment, with their identity, demographics, status, and variable values.
  • Chatrooms. One row per chatroom (matched chamber), with the chamber's participants, settings, and timestamps.
  • Responses. All survey responses across all surveys (global pre, global post, chamber pre, chamber post, embedded segment surveys), keyed by participant and survey ID.
  • All. Every preceding type, plus the full chat transcripts and the broadcast / action / processor-interaction logs.

For a quantitative analysis pipeline, Responses and Participants in CSV are usually the right starting point; for a qualitative pass over conversation, All in JSON gives you the structure to operate on.

Exports are produced on demand; there is no waiting queue. For very large experiments, the export endpoint accepts a participant filter, so you can export a single chamberline or a single date range without downloading the entire experiment.

For a category-by-category description of what each export actually contains, see §6.5.

6.5 What's in your data

An export is not a single thing. It is a layered snapshot of a run viewed from several angles — the survey angle, the conversation angle, the timing angle, and so on. Most analysis questions touch two or three of these layers at once. This section walks through what Carrier captures for every run, what it does not capture, and which export type each kind of data lands in.

The map below shows, at a glance, which export type carries which category. All is a superset; researchers who plan to do anything beyond the simplest summary should default to it.

Data category Participants Chatrooms Responses All
Identity, assignment, status
Survey responses
Chat transcripts
Timing and pacing
AI / processor interactions
Behavioural events
Attention checks and face monitoring
Assignment and reproducibility

The eight sub-sections that follow describe each category in turn, including the cases in which a category is empty by design.

6.5.1 Survey responses

For most studies, this is the primary data. Carrier captures survey responses in four places:

  • The global pre-survey, completed before any chamber, once per run.
  • The global post-survey, completed after the final chamber, once per run.
  • Chamber pre- and post-surveys, completed at the boundaries of each chamber.
  • Embedded segment surveys — the survey segment type — completed within a chamber as part of its segment timeline.

Two shapes come out together. The raw Survey.js JSON preserves nested question structures (matrices, panels, conditional branching) and is appropriate when the response shape itself matters. The flattened response rows give one row per question per participant, with questionId, questionText, response, responseType, and a stage indicator pointing at the survey instance the answer belongs to.

For most quantitative pipelines, Responses in CSV is the right starting point. For qualitative analyses or for questions where the survey was deliberately non-trivial, All in JSON preserves the structure you need to operate on.

6.5.2 Chat transcripts

Every message exchanged in every chatroom is preserved verbatim. Each message carries a sender (human participant, LLM chatbot, scripted chatbot, agent, mediator bot, or system), an ISO timestamp, and a message type that distinguishes ordinary text from system notifications, joins and leaves, mediator broadcasts, bot and AI responses, and processor suggestions.

System messages are interleaved with the conversation rather than stored on the side, which means a researcher reading the transcript chronologically sees joins, disconnects, broadcasts, and attention-check events in situ. Chatrooms and All exports include the full chat history; Participants does not.

6.5.3 Timing and pacing

Several layers of timestamps come out together.

  • Run-level. When the run started, when each phase transitioned, when the run completed or was terminated, and the reason for termination.
  • Chamber-level. When matching happened, when the chatroom began, when each segment within the chamber started, when the chamber ended, and the actual elapsed duration.
  • Per-message. Every chat message carries an ISO timestamp.
  • Connection-level. Heartbeats, reconnection counts, and the participant's total time in the experiment.

These together let researchers reconstruct any per-participant duration of interest — time-to-first-message, time between segments, time spent re-reading instructions — without custom instrumentation.

6.5.4 AI and processor interactions

When chambers use the processor role, every assist event is logged with its full text. Review interactions carry the draft text that was submitted, the feedback that came back, and whether the communicator accepted, rejected, or edited the suggestion. Generate interactions carry the request and the generated response. Real-time assist suggestions carry their content and outcome.

For chambers that use an LLM chatbot, mediator, or agent, the model's reply is stored in the chat history alongside human messages, with sender metadata identifying the role and, where set, the provider. For agents on the Claude Agent path — where memory is provider-managed — the provider's session handle is preserved on the chatroom so that Carrier-side and provider-side timelines can be aligned after the fact.

6.5.5 Behavioural events

When client-side instrumentation is active for a segment, Carrier captures a stream of low-level events: tab visibility changes, focus changes, pointer activity, clicks, and a small set of custom events raised by specific segment types. Per-segment summaries are produced automatically — most commonly tab-away count and total tab-away time — and the raw event stream is preserved for replay or fine-grained sequence analysis.

This data is opt-in by segment. Researchers who want it should confirm that the relevant segments have behavioural-events instrumentation enabled in the builder before piloting.

6.5.6 Attention checks and face monitoring

The attention-check segment captures a result record per attempt: the mode (face-based or survey-based), whether it passed, the retry count, and any mode-specific details. The record appears in two places — a structured array attached to the run, and a corresponding system message interleaved into the chat transcript at the moment of the check.

Face monitoring, when enabled on a chat segment, emits its own event stream: warning shown, face returned, grace expired, paused, resumed, terminated. It is stored the same way: a structured array on the run plus interleaved system messages in the transcript.

Both categories are present only when the experiment was configured to produce them. Their absence in an export is not a missing value; it means the experiment did not ask for them.

6.5.7 Assignment and reproducibility

For anyone who needs to reconstruct, after the fact, why a given participant saw what they saw, the export carries:

  • The chamberline each participant was assigned to, and the reason (random, counterbalance, survey-based, or fixed).
  • A frozen snapshot of the participant's run plan — the chambers in their assigned order, each with its role and slot assignment for that participant.
  • The experiment's version at the moment the run was created, so that a later configuration change does not corrupt the interpretation of earlier runs.
  • A condition seed where randomisation was involved.

Combined with the admin-side activity log (see §7), this is sufficient to reproduce a participant's path through the experiment exactly.

6.5.8 What's conditional

Several categories appear only when the experiment is configured to produce them. Worth flagging up front, so that an absent column is not mistaken for a bug:

  • Behavioural events require client-side instrumentation enabled on the relevant segments.
  • Attention-check results require an attention-check segment in the chamberline.
  • Face-monitoring events require face monitoring enabled on a chat segment.
  • Processor interaction logs require at least one chamber to use a processor role.
  • Non-human sender metadata (role, provider) is populated when the message originates from a chatbot, mediator, or agent; for human messages those fields are empty by design.
  • Variable values appear only for variables the experiment defined; there are no system-provided demographic variables.

If a researcher expects one of these and finds it missing, the place to check is the experiment configuration, not the export.

6.6 Pilot first, ramp second

A short note that does not fit anywhere else in this guide but matters in practice. Every Carrier experiment benefits enormously from a small pilot — three to five participants, ideally including the researcher themselves — before being opened to a larger sample. Pilots are the only reliable way to catch the kinds of issues that the builder cannot validate: a slot constraint that is unsatisfiable in practice, a chamber timing that is too short to read the instructions, an LLM mediator whose system prompt produces unexpected behaviour on real conversations, a survey question that is ambiguous to actual participants.

Pilot with the experiment status set to active and the dashboard open. Watch the matching queue, watch the chat transcripts, and watch the action log. Most experiments end up requiring at least one round of revision after the first pilot. This is normal; budget time for it.

Part II · §7. Administration

Accounts, collaborators, and the admin portal — the parts of Carrier that exist to keep multiple researchers working on the same platform.

7.1 Accounts and collaboration

Every researcher account in Carrier has a role: either researcher or admin. Researchers can create, edit, run, and export their own experiments; admins additionally manage the user list and the activity log.

An experiment has one owner and any number of collaborators:

  • The owner can edit everything, transfer ownership, add and remove collaborators, and delete the experiment.
  • A collaborator can edit the experiment's configuration and view its data, but cannot transfer ownership, add other collaborators, or delete the experiment.

This separation is the simplest model that supports the common pattern of one PI owning each study and several lab members helping to configure and run it.

7.2 The admin portal

The admin portal is available only to users with the admin role. It exposes three sub-areas.

User management. Create, update, enable, and disable user accounts. Disabling an account preserves all of the user's experiments and data but prevents them from logging in. This is the right action when a lab member leaves; deletion is rarely necessary.

Registration approval. When self-registration is enabled, new sign-ups arrive in a pending state. The admin reviews each request — typically by checking the requester's institutional email and the project they intend to use Carrier for — and approves or rejects.

Activity logs. A chronological log of meaningful actions across the platform — logins, experiment creations, role changes, exports. Useful both for accountability and for understanding usage patterns when scaling the platform across multiple labs.

Appendices

Glossary, type × role matrix, and quick-reference indexes for segment types and trigger types.

Appendix A · Glossary

TermDefinition
Aggregate variableA variable computed over multiple participants in a chamber. Configurable to include or exclude bot/agent participants.
Active-segment filterA list of segment IDs during which a trigger is eligible to fire. Empty list = fire in any segment.
Chain targetAnother trigger ID that fires after this one completes. Used to compose multi-step bot behaviour.
ChamberA timed grouping of matched participants who share the same segments and remain together until the chamber ends.
ChamberlineAn ordered sequence of chambers, representing one experimental condition. A participant is assigned to exactly one.
Chamberline filterA condition under which a participant is eligible for a given chamberline; used by survey-based assignment.
ChatroomThe live, runtime instantiation of a chamber for a particular matched group.
CommunicatorThe role of a primary conversational participant. The “default” role in any chamber.
Embedded segmentA selection or ranking segment displayed as an overlay on a chat segment, so participants can vote or rank without leaving the conversation.
Global pre-survey / post-surveySurveys at the very start and very end of a run. Distinct from chamber-level surveys.
AgentAn autonomous non-human participant built on Anthropic's Claude Agent API. Has built-in tools for reading files (in a configured document area), running code, and browsing the web; uses them on its own initiative to inform its messages. Distinct from an LLM chatbot.
Agent built-in toolsThe file-reading, code-execution, and web-browsing tools available to an agent via the Claude Agent API. Used for information gathering; distinct from Carrier intervention actions.
LLM chatbotA non-human participant that produces chat messages from a language model, with no tools and no scripted rules. Open-ended, varies across sessions.
LLM-chosen Carrier actionAn intervention action (disable_chat, prompt_participant, …) selected at runtime by an LLM-driven participant via its structured response. Available to any LLM chatbot or agent acting as mediator.
LLM-driven responseA response produced by a language model on the fly, rather than from a pre-written script.
MatchThe event of assembling enough participants of the right kinds to fill a chamber's slots.
MediatorThe role of a facilitator participant — sees everything, broadcasts, controls turn-taking.
Non-human participantUmbrella term for the three kinds of non-human entity Carrier supports: LLM chatbots, scripted chatbots, and agents.
Phase scriptAn ordered list of phases for a processor, each with a mode and a transition trigger.
PriorityA numeric ranking among triggers; higher priority fires first when multiple triggers match.
ProcessorThe role that assists composition before a communicator's text becomes a message. Three modes: review, generate, real-time assist.
ResponseThe message a trigger sends when it fires. Either scripted or LLM-driven.
RunOne participant's complete pass through the experiment, from arrival to completion.
Scripted chatbotA rule-driven, deterministic non-human participant. Configured by triggers; produces pre-written messages. Can fill communicator and mediator roles, but not processor.
Scripted responseA pre-written message (or random pick from a list) sent when a trigger fires.
SegmentAn activity within a chamber: a chat, a slide, a survey, a timer, a vote, etc.
SlotA position in a chamber, with a type (human / LLM chatbot / scripted chatbot / agent) and a role (communicator / mediator / processor).
Standalone segmentA segment that occupies the participant's entire screen, as opposed to embedded.
TriggerA condition–response–action rule that governs when a non-human participant speaks or acts.
VariableAn attribute attached to a participant, used for matching, visibility, interpolation, or trigger conditions.
Visibility conditionA condition on a chamber that, if false, causes the participant to skip the chamber.

Appendix B · Type × Role compatibility matrix

Type \ RoleCommunicatorMediatorProcessor
Human
LLM chatbot
Scripted chatbot
Agent (Claude Agent API)

Reproduced from §3.1 for quick reference. The only forbidden combination is scripted chatbot as processor.

Appendix C · Segment types — quick index

Every segment type, with the section that documents it. The descriptions live in §2.2; this index is deliberately a pointer rather than a second copy of them.

TypeReferenceCompatible with AIEmbeddable
slide§2.6
instruction§2.7
media§2.8
pdf§2.9
timer§2.10
survey§2.11
input§2.12
selection§2.13
ranking§2.14
chat§2.15
social§2.16
task§2.17
process§2.18
attention-check§2.19

Appendix D · Trigger types — quick index

TypeListens forNotes
keywordConfigurable word / phraseMost common.
regexRegular expression matchUse for structured patterns.
timeDelay from chamber / segment startFires regardless of chat activity.
message-countTotal messages in chatroomFires once per matching count.
participant-message-countMessages from a specific participantSupports total / consecutive / since-reset.
sequenceOrdered series of matchesFor staged steering.
participant-actionJoin, leave, idle, etc.Fires across segments.
after-bot-messageAnother bot's messageCross-bot chaining.
event-monitorArbitrary chatroom eventCatches segment transitions, dashboard interventions.
chain-only(Passive) Only fires from chainFor multi-step bot behaviour.
llm-drivenAn LLM judges the conditionMost expressive; least replicable.
periodicFixed intervalMediator-specific.
aggregateN messages in a windowMediator-specific.
topic-detectedTopic / keyword patternMediator-specific.
activity-timeoutInactivity durationMediator-specific.
participant-countActive participant thresholdMediator-specific.
discussion-phaseChamber start / middle / endMediator-specific.

Appendix E · Walkthrough — building a social feed

A tour of the Feed Builder, from the seeded demo experiment to a feed saved in the database. It covers the same ground as §2.16 in the order you would actually meet it.

✓ These pictures come from a test that passes

Every step below is executed by e2e/social-feed-builder.spec.js against a real server and a real database, and the screenshots are the ones that run produces. If the walkthrough and the product ever disagree, the test fails — which is the point of writing it this way. To run it yourself: npm run demo:social seeds the demo and starts the app; npm run test:e2e walks this page in a browser and checks it.

1 · General — the workspace

The Feed Builder General tab: feed outline, editor and live participant preview.

Three columns, and they are the whole job. The outline on the left is the feed as a timeline of arrivals, with the cast of accounts beneath it — two panes scrolling independently, so a feed of a hundred posts never buries the cast. Agents get a row each with their binding inline; crowd accounts, which a CSV import derives one-per-handle and which do nothing at run time, wrap as chips. The editor in the middle follows whatever is selected. The preview on the right is the participant’s own view, rendered in the participant stylesheet rather than an impression of it.

2 · Layout is not presentation-only

The Reddit layout selected: the preview grows a vote gutter and the forum fields open up.

Switch the layout above the preview and three things move together: the preview re-renders, the affordance chips change (votes lights up only on Reddit), and the Forum fields group opens. On the other two layouts that group is dimmed and states its own condition, because those layouts render none of it and cannot collect a vote at all. This is the setting most worth understanding before you run anyone — see the validity note in §2.16.

3 · Edit a post, and see it as a participant would

A post selected in the timeline, its caption being edited, the preview following.

Pick a post in the timeline and the centre column follows; the preview updates when you leave the field. Timing is authored as a direction and a unit — before entry, at entry, after entry — rather than as raw milliseconds. Seed comments are edited as the thread they already are, with Reply inserting a child; on Instagram and Facebook that button disappears on a comment that is itself a reply, because those layouts render one level only.

4 · Save refuses an invalid feed, and says why

The Feed Builder refusing to save, with an error naming the empty seed comment.

A new seed comment starts empty, and an empty comment is not a valid feed. The same goes for a post with no caption, and for an agent account bound to nothing. Worth knowing before you fight it: an unbound agent account is an error, not a warning — the feed will not save and the experiment will not deploy until you bind it or turn it back into a crowd account.

5 · Find things in a long feed

The outline filtered to a single matching post, with the count reading one of nine.

The filter matches post id, caption and author, and never renumbers anything, so it is safe to filter and then act on what is shown. Past eight distinct arrival times the timeline groups into Before entry · On entry · After entry and moves each post’s own offset onto its row.

6 · Agents — who replies, and how

The Agents tab: persona prompt on the left, model and timing cards on the right.

An agent account’s persona prompt, provider and model, temperature, context window, when it speaks and its reply delay — all without leaving the Feed Builder. These fields belong to the chamber agent, not to this feed: it is the same agent card that sits on the builder canvas, so the edits apply immediately rather than waiting for the feed’s Save.

7 · An account with nothing behind it

An unbound agent account showing the bind control rather than a configuration form.

Select an unbound account and there is no form to fill — there is nothing to configure until something replies as it. The tab offers the bind instead, and the readiness chip in the header counts it among the things to fix. An agent already bound to another account is offered disabled, with the reason: one chamber agent answers as one account.

8 · Closing the readiness loop

The account switched to crowd in General, and the readiness count falling.

The other way out: in General, select the account and set its kind to Crowd. It keeps authoring posts and seed comments and stops claiming it will answer; the agent-only fields (binding, scope, reply delay) go with it, and the readiness count falls.

9 · Feed — every post at once

The Feed tab: the three-band arrival axis above a sortable table with rows selected.

A sortable table of the whole feed, with row selection and bulk Retime, Reassign author and Delete. Above it, arrival on a real axis in three bands, because a backlog post at −2h and a post at +45s cannot share one linear scale without collapsing the segment itself to a sliver. The amber band is posts scheduled after the segment ends — authored, exported, and never seen.

10 · Deploy

The deploy dialog pausing on a warning about reddit-only fields on an Instagram layout.

Save closes the Feed Builder and writes to the builder’s model; the experiment reaches the database through the builder’s own Update, which re-validates every segment — blocking on errors, pausing on warnings. The warning here is the honest one: reddit-only fields authored while the layout is Instagram. They stay in the data and come back if you switch, but they are not rendering now.

The experiment saved to the database.
What the walkthrough actually checks

The test asserts against the server, not the screen:

  • the caption edited in step 3 is in the saved configuration;
  • the two posts retimed in step 9 are stored at showAt: 90000;
  • the post added in step 5 is present, and the layout is back to instagram;
  • the account switched to crowd has lost boundAgent and scopePostIds;
  • the persona typed in step 6 reached the chamber agent’s llm-driven trigger, and not a copy inside the feed configuration.

Annotator Documentation

The Annotator is a batch LLM annotation engine for processing text data at scale. Upload a CSV, configure LLM annotators, and download structured results.

What is the Annotator?

The Annotator is a batch LLM annotation engine. Upload a CSV, configure one or more LLM annotators with prompt templates, run the task at scale, and download structured results.

Common use cases include text classification, sentiment analysis, content coding, and replicating published annotation schemes from peer-reviewed research.

Key Concepts

Concept Description
Task Top-level container holding CSV data, LLM configs, and processing settings
Row One CSV record, processed independently
LLM Config A provider + model + prompt template combination
Repetition Running each row through each config multiple times for reliability
Template Reusable annotation configuration that can be shared
Work Unit One row × one config × one repetition = one API call

Your First Annotation Task

Get started in four steps:

1
Upload a CSV with a text column

Your CSV should contain the text you want annotated. Column names become template variables.

2
Add an LLM config with a classification prompt

Choose a provider and model, then write a prompt template using {{columnName}} syntax to reference your data.

3
Run the task

Start processing. The engine sends each row through your LLM config and stores the results.

4
Download results

Export your annotated data as CSV, Excel, or JSON.

Providing API Keys

The Annotator requires API keys for the LLM providers you use: OpenAI, Anthropic, and/or Google.

User-level keys are set in your account settings and reused across all your tasks. Per-task keys can be provided when creating or editing a task and override user-level keys for that task only.

API keys are never visible to collaborators. Each user must provide their own keys.

Upload & Preview CSV Data

Upload a CSV file (max 10 MB). After upload you can preview the headers and the first rows of data. Column names become {{columnName}} template variables for use in your prompt templates.

Configure LLM Annotators

Add one or more LLM configurations to a task. Each configuration specifies a provider (OpenAI, Anthropic, or Google), a model, and prompt templates. You can add multiple configs to compare models or prompt strategies side by side.

Each config supports temperature and maxTokens settings to control response variability and length.

Write Prompt Templates

Each LLM config has a system prompt and a user prompt. Use {{columnName}} syntax to insert values from each CSV row into the prompt.

Tips for effective prompts: request structured output (e.g., JSON or a single label), define clear categories with descriptions, and provide examples of expected classifications in the system prompt.

Set Repetitions

Set between 1 and 20 repetitions per row per config. Multiple repetitions let you measure reliability and use majority voting to determine final labels.

The total number of work units (API calls) is: rows × configs × repetitions.

Estimate Costs

Before running a full task, use the cost estimator. It runs a sample of up to 10 rows, measures the tokens consumed, and extrapolates to give you an estimated cost for the complete task.

Standard Processing

Standard mode streams results in real time using 1–20 parallel workers. Failed requests are retried automatically with exponential backoff. Processing is crash-safe — results are saved per row, so progress is never lost.

Batch Processing

Batch mode uses the OpenAI and Anthropic batch APIs for approximately 50% cost savings with a 24-hour turnaround. Google requests fall back to standard processing automatically.

Batch jobs cannot be paused or resumed. Use standard mode if you need fine-grained control over execution.

Pause, Resume & Cancel

In standard mode, you can pause processing at any time. All completed results are preserved. Resume picks up where you left off. Cancel stops the task permanently but keeps all results that were completed before cancellation.

Pause and resume are only available in standard processing mode. Batch jobs run to completion or can only be cancelled.

Use Research Templates

The Annotator includes 25+ peer-reviewed annotation presets from published research. Select a template to pre-fill your LLM configs with validated prompt designs.

Authors Configs Domain
Gilardi et al. (2023) 7 annotators Text classification
Rathje et al. (2024) 6 annotators Psychological text analysis
Bhatia et al. (2025) 3 annotators Choice dilemma annotation
Bojic et al. (2025) 5 annotators Latent content analysis
Kumar et al. (2026) 4 annotators Empathic communication evaluation

Create Custom Templates

Save any task configuration as a reusable template. Custom templates are private by default and available only to you. They capture the full LLM config including prompts, model settings, and repetition count.

Share Templates

Submit a custom template for public review. An administrator reviews and approves or rejects the submission. Approved templates become available to all users. Usage is tracked so you can see how often your shared templates are being used.

Monitor Progress

A progress bar shows real-time completion status. Each task follows a status lifecycle: pendingprocessingcompleted or cancelled. In standard mode, a paused state is also available.

Download Results

Export results in CSV, Excel, or JSON format. You can download partial results while the task is still running — useful for spot-checking quality before the full run completes.

Understanding Output Format

Results use a flattened format with one row per input record. Columns include all original input data, the rendered prompts, and response columns for each config and repetition combination.

For analysis in R, read the CSV directly with read.csv(). In Python, use pandas.read_csv(). In Excel, open the Excel export for automatic column formatting. Response columns follow the naming pattern [configName]_rep[N].

Carrier Workspace

Carrier Workspace brings your research team's Claude Code activity — session transcripts and shared memory — into one place inside Carrier, so the way your team used AI assistance to build and analyse a study is searchable, reviewable, and preserved alongside the study itself.

What is Carrier Workspace?

When a team uses Claude Code while building an experiment, writing analysis scripts, or preparing materials, each developer accumulates a local history of sessions (the back-and-forth transcripts of their work) and memory (durable notes the assistant keeps about the project). That history normally lives buried in each person's local ~/.claude directory, invisible to the rest of the team.

A workspace collects that data for a single repository and shows it on one page in Carrier. Carrier Workspace is powered by the team-claude-view skill, which provides the small client scripts that package and upload a machine's data, plus a /private command for marking sessions you don't want shared.

A workspace is tied to one repository, and you choose how its data arrives when you create it. There are two modes:

Mode How data arrives
Carrier Workspace mode (default) Each developer's machine packages its local Claude Code cache and uploads it to Carrier with a small Python client. Works with no GitHub repository involved.
GitHub-linked mode Carrier connects to a GitHub repository and pulls the shared data automatically on a schedule. Lowest-effort once set up — nobody runs anything by hand.

Both modes end up in the same place: a workspace page showing sessions and memory.

When a research team needs it

This is a team-tooling feature, separate from running experiments. It does not touch participant data or your experiment configuration — it concerns how your team worked, not what your participants did.

The deeper reason to keep this record is delegation. Empirical research now routinely hands real methodological work to agentic AI: cleaning a dataset, deciding which records to exclude, choosing a transformation, drafting an analysis script, selecting a model specification. Those are not neutral chores — they are methods decisions, and when an agent makes them they tend to vanish the moment the session closes. A workspace turns that delegated work into a durable, shareable record of what was asked, what the assistant decided, and why. Making AI use visible in this way is squarely in the spirit of open science: the same disclosure norms that ask us to share data, code, and pre-registrations extend naturally to disclosing how AI shaped the work.

Transparency also guards against a subtle integrity risk that agentic workflows can introduce without anyone intending it. An assistant pointed at a loosely specified goal — “find the effect,” “get the model to fit,” “clean this up so the result holds” — can quietly explore many exclusion rules, covariate sets, and specifications, then surface only the one that reaches significance. That is the garden-of-forking-paths / researcher-degrees-of-freedom problem, arrived at as unintentional p-hacking rather than deliberate fishing. Because the workspace preserves the full transcript — every fork the agent tried, not just the final answer — you, your collaborators, reviewers, and your future self can tell whether a reported result survived a single principled analysis or emerged after dozens of silent attempts. The record makes the exploration auditable, which is precisely what keeps delegation honest.

Concretely, reach for it when:

  • Reproducibility & provenance. You want a durable record of how AI assistance produced study materials, analysis code, or stimuli — and which analytic decisions were delegated — the kind of provenance a methods section or a replication package benefits from.
  • Research integrity. You want the agent's exploration to be auditable, so a reported effect can be traced back to a principled analysis rather than an opaque search.
  • Onboarding. A new RA or collaborator can read how the project was built rather than starting cold.
  • Coordination. Several people on the team use Claude Code on the same repository and you want a shared, searchable view instead of scattered local histories.

Carrier Workspace mode (default)

This is the default mode. There is no GitHub connection: each developer runs a small Python client (provided by the team-claude-view skill) that bundles their local Claude Code cache and uploads it to Carrier with an API key. The data travels straight from your team's machines to Carrier.

1
Save the API key (shown once)

When you create a Carrier Workspace, Carrier displays an API key exactly once, right after creation. Copy and save it now — it is never recoverable. The creation screen also shows the exact upload URL and a ready-to-paste configure command. If you lose the key, you'll need to recreate the workspace to get a new one.

2
Configure each machine once

On every machine that should contribute data, run the configure command once. It saves the upload URL and key locally so later syncs don't need them. Use the exact --url and --key shown in the create modal:

python3 scripts/team-claude-client/carrier_configure.py --url <upload-url> --key <api-key>
3
Sync

After configuring, push the machine's Claude Code data. The client packages your local sessions/ and memory/ directories into a gzip tarball and uploads it, then prints how many sessions and memory entries were sent:

python3 scripts/team-claude-client/carrier_sync.py
4
Automate with a SessionEnd hook (recommended)

Running the sync by hand is easy to forget. Claude Code's SessionEnd hook in .claude/settings.json fires when a session ends — wire the sync script in so every finished session uploads automatically:

{
  "hooks": {
    "SessionEnd": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "python3 scripts/team-claude-client/carrier_sync.py"
          }
        ]
      }
    ]
  }
}

Adjust the path if your repo lays the script out differently.

GitHub-linked mode

In GitHub-linked mode, Carrier holds a personal access token (PAT) for your repository and uses it to keep a private mirror of the shared Claude Code data up to date — nobody has to run anything by hand. Choose this if your team already publishes shared data to GitHub.

1
Create a fine-grained personal access token

Carrier needs read access to one repository's contents. Create a token at github.com/settings/personal-access-tokens/new:

  • Repository access — scope to the single repository you're linking; don't grant access to all repositories.
  • Repository permissions — set Contents: Read-only. That is the only permission Carrier requires.
  • Expiration — a 90-day expiry balances safety against re-linking too often.

Copy the token when GitHub shows it — you won't be able to see it again. A classic PAT (with the repo scope) also works, but Carrier will show an advisory banner recommending you switch to a fine-grained, single-repo, read-only token.

2
Link the repo in Carrier

From the Workspaces page click Link a repo, choose the GitHub tab, fill in a name, the repository URL (e.g. https://github.com/your-org/your-repo), and paste the PAT, then click Create.

3
What the first sync does

Carrier clones the repository and fetches its claude-team-share branch — the branch your team uses to publish shared data — and reads its sessions/ and memory/ directories into a private server-side mirror. If that branch doesn't exist yet, the workspace simply shows an empty state until it appears; nothing is broken.

Choosing a mode

  Carrier Workspace mode (default) GitHub-linked mode
Auth API key, shown once, bcrypt-hashed Fine-grained PAT (Contents: Read), encrypted at rest
Cold start Create workspace, save key, run carrier_configure.py per machine Create token, paste into the GitHub tab
Updates Run carrier_sync.py (or a SessionEnd hook) after each session Automatic — Carrier polls ~every 30s; Re-sync now for immediate
Works without GitHub Yes — no GitHub repo needed No — requires a repo and the claude-team-share branch
Data path Straight from your machines to Carrier From GitHub to Carrier

Pick Carrier Workspace mode (the default) when you want to keep data flowing through your own machines or have no GitHub repo in the loop. Pick GitHub-linked mode when your team already publishes a claude-team-share branch and you'd rather not run a client by hand.

Browsing sessions & memory

However the data arrives, the workspace page presents it the same way. Open a workspace from the Workspaces page to see two things:

  • Sessions — the Claude Code transcripts contributed to this repository. Open one to read it as a linear transcript of the work.
  • Memory — the durable notes the assistant kept about the project.

In Carrier Workspace mode, every machine that runs the client contributes to the same workspace: Carrier derives the workspace from the repository's root folder name, so all checkouts of the same repository map to one workspace and each machine's contribution is additive.

Keeping data current

How a workspace stays fresh depends on its mode:

  • Carrier Workspace mode — data updates whenever a machine runs carrier_sync.py. The recommended setup is the SessionEnd hook (see setup, step 4), so every finished session uploads on its own.
  • GitHub-linked mode — Carrier polls the repository roughly every 30 seconds and re-syncs stale workspaces automatically as your team pushes new data. Use the Re-sync now button on the workspace page for an immediate refresh.

Privacy & security

  • GitHub PATs are encrypted at rest using AES-256-GCM. Carrier stores the encrypted token, not the plaintext.
  • API keys are bcrypt-hashed. They are shown once at creation and never recoverable — Carrier cannot display or email them again.
  • Marking a session private. The team-claude-view skill provides a /private slash command that marks a session as non-shareable, so it won't be included when your data is shared.
  • Removing a workspace deletes its data. Carrier deletes the server-side mirror and, for GitHub-mode workspaces, the encrypted token along with it.
The scrubber is a convenience, not a guarantee
Before storing data, Carrier runs a scrubber that removes known credential patterns only. It is not a general PII or secrets detector and cannot catch everything. Do not rely on it to sanitise sensitive transcripts — if a session contains something you wouldn't want your team to see, don't share it.

Troubleshooting & FAQ

Carrier Workspace mode (manual upload)

Symptom What it means
401 Unauthorized The API key is wrong or has been rotated. Re-run carrier_configure.py with the correct key for this workspace.
413 Payload Too Large The tarball exceeded the 50 MB upload cap. Trim older sessions from your local cache before syncing again.
400 Bad Request The upload was rejected as malformed — usually a corrupt tarball, or one containing paths outside the allowed sessions/ and memory/ prefixes.

GitHub-linked mode

Symptom What it means
Auth-error banner The PAT expired or was revoked. Create a fresh token and re-link the repo (or update the token on the existing workspace).
Empty state persists The claude-team-share branch doesn't exist on the remote yet. The workspace stays empty until the branch is created and pushed.
Can't read a private repo Make sure the PAT actually has access to that specific repository.

Common questions

How do multiple machines work together? In Carrier Workspace mode, every machine that runs the client contributes to the same workspace — Carrier derives the workspace from the repository root's folder name, so all checkouts of the same repository map to one workspace. Each contribution is additive.

Can I share privately, without GitHub? Yes — that's exactly what Carrier Workspace mode is for. The data travels straight from your machines to Carrier with no GitHub repository in the loop.

How do I rotate a GitHub PAT? Create a new fine-grained token and either re-link the repo or update the token on the existing workspace, then revoke the old token on GitHub.

How do I regenerate an API key? There is no in-place regenerate. Delete the workspace and recreate it to issue a fresh key (shown once), then reconfigure each machine with carrier_configure.py using the new key.

What gets deleted when I remove a workspace? Its server-side mirror of sessions and memory. For GitHub-mode workspaces, the encrypted PAT is deleted as well. After removal there is nothing left server-side for that workspace.