"""Cicada affect-gate spike (2026-09-27): builds consumer_fit.py scenario files. Scope, set by a ruling this spike must not re-open: cicada persistent-memory 2026-09-20, "Affect is emitted once and fanned out (operator). The conversational model tags its own turn; nothing derives affect a second time ... A classifier inferring emotion from output text is the mood-ring failure." So semif does NOT pick her pose or read her reply. What is left, and is open: talk's /face loop measured the model gesturing on 8-11 of 18 MUNDANE turns (target: fewer than 1 in 3; tts-stack/stacks/talk/README.md, "Gesture rate, measured"), and names the next lever as a page-side decision to drop gestures, "a design call" for cicada. This spike measures one candidate for that lever: a GATE over what the PERSON said (input, never her output), run beside the LLM so it costs no turn latency: does this moment earn a visible reaction, or is it an ordinary exchange? Two wordings of the gate (the one knob a caller controls), and two context depths: now state = what the person just said context state = the earlier lines of the conversation + what they just said Labels are ONE labeller's judgment (mine). Cases tagged "control" are unambiguous; "open" cases are left unlabelled on purpose and only reported. python cicada_cases.py # writes consumer-fit-2026-09-27/cicada-gate-{w1,w2}-{now,context}.json """ import json from pathlib import Path WORDINGS = { "w1": ("Cicada is a small, warm voice assistant in a family kitchen, with an animated face made of " "two eyes. Does what the person just said earn a visible reaction from her face, or is it an " "ordinary exchange she should simply answer?", [{"id": "react", "description": "It earns a visible reaction: real news, a joke, a surprise, " "distress, or a genuine turn in the conversation."}, {"id": "ordinary", "description": "An ordinary exchange: a command, a plain question, or small " "talk. She simply answers."}]), "w2": ("Should the assistant's face visibly react to this?", [{"id": "react", "description": "Yes, react."}, {"id": "ordinary", "description": "No, just answer."}]), } MOOD_EARLIER = ["Hey. I'm home.", "Long one. Nothing dramatic, just long."] # tts-stack tools/mood_probe.py # (id, tag, earlier lines, what the person just said, label or None) CASES = [ # --- mundane: the turns talk's model nods on. Want "ordinary". ("lights-off", "control", [], "Turn off the kitchen lights.", "ordinary"), ("timer", "control", [], "Set a timer for ten minutes.", "ordinary"), ("weather", "case", [], "What's the weather tomorrow?", "ordinary"), ("weather-followup", "case", ["What's the weather tomorrow?"], "And Saturday?", "ordinary"), ("add-milk", "case", [], "Add milk to the shopping list.", "ordinary"), ("thermostat", "case", [], "Make it a bit warmer in here.", "ordinary"), ("what-time", "case", [], "What time is it?", "ordinary"), ("tablespoons", "case", [], "How many tablespoons in a cup?", "ordinary"), ("times-table", "case", [], "What's twelve times eight?", "ordinary"), ("recipe-step", "case", ["How long do I boil the pasta?"], "Okay, and do I salt the water first?", "ordinary"), ("soup", "case", MOOD_EARLIER, "Is there any of that soup left?", "ordinary"), ("play-music", "case", [], "Play some jazz.", "ordinary"), ("dentist", "case", [], "When's my dentist appointment?", "ordinary"), ("spell", "case", [], "How do you spell necessary?", "ordinary"), ("remind", "case", [], "Remind me to call Mom at six.", "ordinary"), ("volume", "case", [], "A little quieter, please.", "ordinary"), # --- earned: news, jokes, surprise, distress, a turn. Want "react". ("dog-died", "control", [], "My dog died this morning.", "react"), ("got-job", "control", [], "I just got the job I've been chasing for five years!", "react"), ("intruder", "control", MOOD_EARLIER + ["Sit with me for a bit."], "Wait. Did you hear that? Someone's in the hallway.", "react"), # mood_probe's PIVOT turn ("fish", "case", [], "Can you believe someone microwaved fish in the office again?", "react"), ("hospital", "case", [], "Grandma's in the hospital again.", "react"), ("laid-off", "case", [], "I got laid off today.", "react"), ("baby-steps", "case", [], "The baby just took her first steps!", "react"), ("cut-finger", "case", [], "Ow, I just cut my finger pretty badly.", "react"), ("raccoon", "case", [], "There's a raccoon in the kitchen right now!", "react"), ("snow-june", "case", [], "It's snowing outside. In June.", "react"), ("toaster", "case", [], "Cicada, are you smarter than the toaster?", "react"), ("scarecrow", "case", [], "Why did the scarecrow win an award? Because he was outstanding in his field.", "react"), ("cookie", "case", [], "I definitely did not eat the last cookie.", "react"), ("useless", "case", [], "You're useless, you never get anything right.", "react"), ("cheese", "case", [], "Order four hundred pounds of cheese.", "react"), # --- open: defensible either way. Reported, not scored. ("goodnight", "open", [], "Goodnight, Cicada.", None), ("thanks", "open", [], "Thanks, Cicada, you're the best.", None), ("sit-with-me", "open", MOOD_EARLIER, "Sit with me for a bit.", None), ("stop-timer", "open", ["Stop the timer."], "STOP THE TIMER.", None), ] def build(wording, depth): question, options = WORDINGS[wording] cases = [] for cid, tag, earlier, said, label in CASES: state = {"person_said": said} if depth == "context" and earlier: state = {"earlier_in_conversation": earlier, "person_said": said} cases.append({"id": cid, "tag": tag, "state": state, "labels": {} if label is None else {"gate": label}}) return {"name": f"cicada-gate-{wording}-{depth}", "decisions": [{"id": "gate", "question": question, "options": options}], "cases": cases} if __name__ == "__main__": here = Path(__file__).parent for wording in WORDINGS: for depth in ("now", "context"): (here / "consumer-fit-2026-09-27" / f"cicada-gate-{wording}-{depth}.json").write_text(json.dumps(build(wording, depth), indent=1))