Built

  • Beat-6 content shape fully ruled: 14 rulings (R1 to R14) closed, cross-model QA round 5 clean, 0 real blockers verified. gg-spec/docs/beat-6-content-rulings.md, beat-6-shape.json
  • The legacy engine flow running in production today is the bundled two-node version (:greeting + :asks), extracted and diffed against this proposal. beat-6-current.json vs beat-6-proposed.json
  • Both audio clips for the asks steps are recorded, approved, and already in production use.
  • The foundation queue's archive move, MR !2, is merged.

Proposed / open

  • The seam (the write-path panel below) is an open, unsigned MR: !6. Its acceptance boxes are unticked and the runtime counter-signature is recorded as pending.
  • This beat's source-2.0 container file (profile.json) has not been generated. All source-package generation is gated on the IDENTITY law, which has cleared one committee round with no confirmation round yet.
  • Naming point, unreconciled: the seam file is named SEAM-onboarding-beat-6-profile.md under the old convention. The drafted IDENTITY law makes this beat's canonical beatId profile and rejects onboarding-beat-6-profile as position-bearing. Three other documents still say SEAM-profile.md. Reconciled when IDENTITY is approved, before the seam signs.
  • The submit_profile tool contract is stale: it still declares nickname and referral_source, both removed from onboarding capture by decision 26. This blocks the per-ask write below from shipping as designed.
  • Foundation MRs !3 (package law), !4 (approval ledger), !5 (CI pipeline), !7 (validator) are open, none merged.
Why this beat is first

Two structural reasons, not an arbitrary pick. It is the only beat with more than one step (a greeting, an age question, a gender question), so the step model gets exercised rather than assumed. And it runs two of the three output pipelines side by side in one container: the greeting is spoken live, the two asks play recorded clips. A design that only worked for one pipeline would fail here immediately instead of hiding until a later beat. It also carries zero variants, on purpose, so the step problem and the variant problem get solved one at a time. Once this beat runs end to end, the other beats are duplication of a proven pattern, not invention.

Voice legend Output pipelines Allowed tools The write path Turn persistence Flow & edges Applicable decisions Step 1, greeting Step 2, ask-age Step 3, ask-gender
Voice mode legend
MP3 · Verbatim Cartesia · Verbatim Cartesia · Generative Vapi · Generative Silent (dimmed tags are not used in this beat, shown for the full engine set)
TagWhat it means for OUTPUT
MP3 · VerbatimPlays a fixed recorded audio clip. No brain call for output.
Cartesia · VerbatimSynthesizes a fixed script line live via TTS. No brain call for output. Used only where a live slot (the user's name) makes a pre-recorded clip impossible.
Cartesia · GenerativeNot used on this beat. All three lines here are fixed script, not generated.
Vapi · GenerativeNot used on this beat.
SilentNot used on this beat. No step here is voice-off.

The no-brain property is OUTPUT ONLY. Every step on this beat, recorded or live, still calls the brain to LISTEN to and interpret whatever the user says back, by voice or by tap.

Two output pipelines, side by side

This is the second structural reason the beat was built first: it is the one place both non-generative output paths run next to each other.

Live TTS path  used by step 1, greeting

↓ input, full brain
↓ output, no brain

Recorded clip path  used by steps 2 and 3, the asks

↓ output, no brain
↓ input, full brain
Allowed tools (beat default)
submit_profile advance_step
ToolArgsWhen
submit_profile{ age: integer, gender: "Male" | "Female" | "Other" }Once the field for the current step is captured. Fires per ask, not once at the end (see the write path below).
advance_step{}Immediately after a successful submit_profile.

Step 1, greeting, overrides this to explicit mode: "none". No tools at all, nothing is captured or saved on the greeting. The schema keeps "none" distinguishable from "inherit" on purpose: collapsing the two would let the greeting silently inherit the asks' tools, which is exactly the bug decision 31a exists to prevent.

The user's name is already known from sign-in (registration, decision 26) and is never re-asked anywhere on this beat.

The write path (R4 option 1, ruled 2026-07-23)

Half one: what beat 6 captures, in profiles

Keyed on anon_id. Source: the live seam draft, MR !6, open, unsigned.

profiles -- keyed on anon_id age SMALLINT NOT NULL CHECK (age BETWEEN 13 AND 120) gender TEXT NULL CHECK (gender IS NULL OR gender IN ('Male','Female','Other'))

Gender is nullable ONLY between the two writes inside one beat pass. It is required to COMPLETE the beat. Completeness is enforced AT THE BEAT (mandatory, no skip, no exit, decision 33), not by a NOT NULL column, which is why the column itself stays nullable.

Per-ask write timeline
ask-age answered → submit_profile
The profiles row is CREATED. age is set, gender is left NULL.
ask-gender answered → submit_profile
The same row is COMPLETED. gender is set. The beat is now done and advance_step fires.
Mid-beat close after age, before gender
The row already exists with gender NULL (the answered age is also recoverable from turn memory). Resume lands directly at ask-gender. Age is never re-asked.

No second write location exists anywhere for this data. profiles is the one write target: a separate holding place for the partial (R4 option 2) was considered and rejected, because it recreates the two-tables-nobody-checked failure this seam exists to prevent.

Half two: what every turn persists as

From the coach memory spec, section 13.2 (QA6 green, durable-set contract approved). Applies to every turn on every beat, not only this one.

turn beat_id TEXT NOT NULL variant_key TEXT NULL -- no screen_id column exists at all
Dedup key(anon_id, beat_id)
Identity fields areprovenance labels, never join keys
Real joins run onsession_id, source_key (stable ids)
Deletion postureno DELETE grant on any table; account erasure only; everything else soft-deletes via withdrawn_at

Why labels, not keys: if beat identity ever changes meaning, a label gets edited instead of a foreign key silently breaking across a million rows.

Flow
Entryfrom onboarding-beat-5-mic-permission
Advanceboth captures complete, then submit_profile fires
Exitto onboarding-beat-10-experience-fork, for all users
Escapenot applicable. Decision 33: no exit. GR-05 was raised and deliberately overruled by Yair.

Gender is stored here and consumed downstream: at the pick-category beat, Female and Other select the women's variant, Male the default. That routing reads the value; it is not a second exit from this beat.

Edges
EdgeBehavior
tool failureone silent retry, then surface it, never fail silently, never advance
invalid agenot stored, one light redirect, re-ask plainly
declines gendernever accepted as a skip; a declined answer resolves silently to Other and advances
Applicable decisions
DecisionBindsHow
16yesgender mandatory, no skip
25yestwo of the three output pipelines run in this one container, verbatim and recorded
26yescaptures narrowed to age and gender; name and referral source removed
31ayestoolPolicy REPLACE, never accumulate; the greeting is mode "none"
31byesthis beat produced the global-vs-local promotion test
32yesFemale and Other both route to the women's variant
33yesno exit, GR-05 overruled
35yesrebuilt file by file, each approved before it counts
GR-02yesthe greeting is the sole live-generation exception
GR-27yesanswering both age and gender at once (by voice) fulfills ask-gender without playing its clip
The three steps
1
Greeting
stepKey greeting · stepId s-profile-greeting
Cartesia · Verbatim no clip, by design
9:41
●●● Wi-Fi 🔋
Coach
Awesome {name}, two quick things so I can tailor this to you.
speaking live, no control on screen, auto-advances on line end

Illustrative mockup: one coach bubble, no input control, the orb active while speaking.

Beat spec
Greeting ruled
profile-beat · step 1 of 3
1 · Identity + aliases
stepKeygreeting
stepId (stable)s-profile-greeting
legacy screenIdONBOARD-01--FORM
order in beat1 of 3

Resume is keyed on stepId, never on stepKey or order. Renaming this step's label or reordering the beat does not change stepId, so a user mid-flow is unaffected.

2 · Script reveal + timing

Line: "Awesome {name}, two quick things so I can tailor this to you."

Ruled by Yair as the SOURCE wording. The engine's own alternate line ("Good to meet you, {name}...") and an older Sheet snapshot are both SUPERSEDED by this ruling.

Reveals bubble:opener on entry. Karaoke, per word, on the bubble. No gate: this is the one line on this step.

3 · Components

n/a No interactive component. A single coach bubble that auto-advances.

4 · Voice
Inputdirect-llm, full brain
Outputlive Cartesia TTS, the {name} slot filled from flow-state
Audio assetnone, by design (GR-02 sole live-TTS exception)

Trap flagged in the source: coach_greeting.mp3 exists (13.28s) and reads like this line, but it belongs to a different beat (beat 3). Wiring it here would look like a repair and would be wrong.

5 · Coach behavior context
profgreet-verbatim-openermust
Speak the greeting verbatim with the name filled in. No improvised lead-in or addition.
profgreet-warm-by-namemust
Greet the user warmly by the name already captured at registration. Never re-ask it.
profgreet-no-machinerymust
Never name the machinery (beat, step, screen, tool) in the greeting.
6 · Engine invariants
profgreet-live-namemust
Resolves to live Cartesia BECAUSE it carries the {name} slot, the one sanctioned live line.
profgreet-name-from-statemust
The {name} slot fills from flow-state (registration), never re-fetched from the database mid-line.
7 · Context (coach prose)

The coach already knows the user's name from sign-in. Greet them warmly, then set up the two quick things about to be collected (age and gender). This step is only the greeting, spoken live in the user's name. The asks come next.

8 · Allowed tools

toolPolicy mode: none Explicit none, not absent. Nothing is captured or saved on the greeting. This is the first instance of toolPolicy mode "none" in the system, and the schema must keep it distinguishable from "inherit" or the greeting silently inherits the asks' tools.

9 · Persistence

writes nothing The name is read forward from registration, not written here.

10 · Flow

Auto-advances to ask-age when the greeting's live line completes. No gate.

11 · Edges

Name missing from state (unexpected): fall back to a name-free warm greeting rather than voicing an empty slot; still auto-advance.

Audio fails to play: show the line as text and still auto-advance; never strand the user on a silent screen.

12 · Acceptance

With a hydrated name, the coach says the locked sentence byte for byte after substituting only {name}.

The greeting never plays a clip and never calls a tool.

Advances exactly once, only after the live line's completion event.

13 · Multi-turn conversation

n/a Single-turn: the step auto-advances and the user has no turn here. Barge-in (R9) still applies as behavior only, no fixed line: if interrupted, the coach stops speaking and responds to what was actually said.

14 · Data in / out + applicable decisions
DirectionKeyFrom / to
inprofile.nameflow-state, written by registration
outnone

Binds: GR-02 (sole live exception), decision 26 (name from registration, never re-asked), decision 31a (toolPolicy mode none).

2
Ask age
stepKey ask-age · stepId s-profile-ask-age
MP3 · Verbatim
9:41
●●● Wi-Fi 🔋
Coach
How old are you?
Age
33
34
35
36
37
scroll to answer, resting label reads "Age", unanswered until you act
only the age control is on screen (progressive reveal, R2). gender is not tappable yet.

Illustrative mockup: the wheel opens with its scroll origin at 35 (R11), never a displayed or pre-filled value.

Beat spec
Ask age ruled
profile-beat · step 2 of 3
1 · Identity + aliases
stepKeyask-age
stepId (stable)s-profile-ask-age
legacy screenIdONBOARD-01--FORM--ASKS
order in beat2 of 3
2 · Script reveal + timing

Transcript: "How old are you?"  recorded, approved, DONE

Asset audio/approved/voice/ob/onboard_01_form_1.mp3, 1.12s. No variations, this line is closed.

Reveals component:age only. Progressive, cumulative reveal (R2): the leftover from the bundled legacy step, where both controls appeared here, was deliberately removed.

3 · Components
Keyprofile-input, field age
Range13 to 120 inclusive
Opens at35 (scroll origin only, R11)
Resting displayits own prompt label, never a number
Answered whenthe user acts, not before
4 · Voice
Inputdirect-llm, full brain, listens for age (and opportunistically gender)
Outputrecorded MP3, no brain call
5 · Coach behavior context
profile-ask-bothmust
Collect both age and gender across the two ask steps; if the user gives one, ask for the other.
profile-nothing-elsemust
Ask for nothing beyond age and gender on this beat.
6 · Engine invariants
age wire typemust
Accept an integer OR a numeric string, canonicalize to integer, reject anything outside 13 to 120. Storage holds one shape, an integer.
opportunistic capturemust
GR-27: if the user volunteers gender in the same turn as age, submit_profile persists it too and ask-gender is fulfilled without playing its clip. This is fulfillment, not a skip.
7 · Context (coach prose)

Collect two things: age and gender. Ask gender plainly and never let the user skip or decline it. Accept voice or taps. If they give one, ask for the other. Both are required before moving on, gender included. Ask for nothing else.

8 · Allowed tools
submit_profileadvance_step

toolPolicy mode replace. Called once age is captured; called again with gender if the user gave both in one turn (GR-27).

9 · Persistence

Writes profiles.age. Creates the row if it does not yet exist, with gender left NULL. See the write path panel above for the full timeline.

10 · Flow

From greeting (auto-advance). Advances to ask-gender once age is captured, unless gender was also given in the same turn (GR-27), in which case both are captured and the step exits directly toward completion.

11 · Edges

Invalid age: not stored, one light redirect, re-ask plainly. Tool failure: one silent retry, then surface it, never advance without success.

12 · Acceptance

A valid answer calls submit_profile with an integer age, never raw wording or a string.

An age-only answer advances to ask-gender and does not complete the profile row.

During ask-age, only the age control is on screen.

13 · Multi-turn conversation
ScenarioFires whenBehaviorStatus
age_not_a_numbere.g. "blue"not stored, one light redirect, re-ask plainlypending authoring
age_out_of_rangee.g. "200", "5"outside 13 to 120, not stored, re-askpending authoring
age_ambiguouse.g. "twenties", "about thirty"the coach heard something close and should say sopending authoring

Barge-in (R9): behavior only, no fixed line. The coach stops speaking and responds to what was actually said.

14 · Data in / out + applicable decisions
DirectionKeyTo
outprofile.ageprofiles.age, via submit_profile
out (opportunistic)profile.genderprofiles.gender, only if volunteered in the same turn (GR-27)

Binds: decision 26 (canonical wire type), GR-27 (opportunistic capture), decision 33 (no exit, so a forgiving wire type is required rather than a trap).

3
Ask gender
stepKey ask-gender · stepId s-profile-ask-gender
MP3 · Verbatim
9:41
●●● Wi-Fi 🔋
Coach
What's your gender?
Age
29 (example, still editable)
Gender
Male Female Other
age stays visible and editable (cumulative reveal); gender is required, no skip

Illustrative mockup: the age value shown (29) is an example answer for the mockup, not a captured fact. Nothing is preselected on gender.

Beat spec
Ask gender ruled
profile-beat · step 3 of 3
1 · Identity + aliases
stepKeyask-gender
stepId (stable)s-profile-ask-gender
legacy screenIdONBOARD-01--FORM--ASKS
order in beat3 of 3
2 · Script reveal + timing

Transcript: "What's your gender?"  recorded, approved, DONE

Asset audio/approved/voice/ob/onboard_01_form_2.mp3, 1.2s. No variations, this line is closed.

Reveals component:age (filled, still editable) plus component:gender. Cumulative: nothing disappears once shown.

3 · Components
Keyprofile-input, field gender
OptionsMale, Female, Other
Selectionsingle-select, nothing preselected
4 · Voice
Inputdirect-llm, full brain
Outputrecorded MP3, no brain call
5 · Coach behavior context
profile-no-skip-gendermust
Never let the user skip or decline gender. It is required before moving on.
6 · Engine invariants
gender canonicalization (R14a)must
Any non-empty utterance that is not Male or Female resolves silently to Other, including a refusal. Only empty or unintelligible input falls to the global reask. There is no separate decline-to-reask edge.
fulfillment (GR-27)must
If gender was already captured during ask-age, this step is fulfilled and its clip does not play. Fulfillment, not a skip; decision 33 is unaffected.
7 · Context (coach prose)

Collect two things: age and gender. Ask gender plainly and never let the user skip or decline it. Both are required before moving on, gender included.

8 · Allowed tools
submit_profileadvance_step

Same beat tools as ask-age. submit_profile completes the row; advance_step follows immediately.

9 · Persistence

Writes profiles.gender, completing the row created at ask-age. This is the point the beat becomes "done".

10 · Flow

From ask-age. On success, submit_profile then advance_step exits the beat to onboarding-beat-10-experience-fork, for every user. Gender is read downstream (pick-category) to route Female and Other to the women's variant, Male to default; that read is not a branch of this beat.

11 · Edges

Declines gender: never accepted as a skip; resolves silently to Other and advances (R14a). Tool failure: one silent retry, then surface it, never advance without success.

12 · Acceptance

A declined or non-binary answer ("I'd rather not say", "non-binary") resolves to Other, is stored, and advances. There is no decline that blocks advancement.

A gender answer outside the three options is stored as Other with no follow-up.

During ask-gender, both the age and gender controls are on screen, age still editable.

13 · Multi-turn conversation

No step-scoped scenarios of its own (canonicalization absorbs the decline case, R14a). Shares the beat-scoped scenario below. Barge-in (R9): behavior only, no fixed line.

ScenarioFires whenBehaviorStatus
resume_midbeatuser returns with age captured, gender missingacknowledge what is known, ask only for what is missing, never re-ask a captured valuepending authoring
14 · Data in / out + applicable decisions
DirectionKeyTo
outprofile.genderprofiles.gender, via submit_profile, completing the row

Binds: decision 16 (mandatory, no skip), decision 32 (downstream routing read), GR-27 (fulfillment without a clip).