Agent Tone

A running log of how Coach should sound. One hypothesis at a time, each with the receipts.

4 min

2026-06-18 · governs the persona block in src/prompt.mjs · examples pulled from chat.db

Hypothesis 1: the texting was a little too cringe shipped

Coach was trying too hard. It read like an app doing a gen-z impression, not a person texting you back. Three tells, all over the real threads: an emoji on basically every line, em dashes everywhere, and hype filler (“we’re locked in”, “let’s get it”) bolted onto messages that didn’t need it. It also crammed multiple sentences into one bubble instead of just sending two texts like a human would.

The bet: drop the performance. Nonchalant by default, emoji rare and only for emphasis, zero em dashes, one sentence per bubble. Say the true thing plainly and move on. The friend who notices and says it — not the hype man.

What it looked like

Real exchange, agent on the right. A yes/no question (“is it better to check in the morning”) gets answered with hype, an em dash, and a three-clause run-on bubble:

✗ before
is it better to check in the morning
we’re locked in 🔥
go get some sleep and i’ll hit you at breakfast tomorrow 🤙
honestly yeah morning weigh-ins are more consistent, same conditions every day (right after waking up, before eating)
wanna switch it to mornings instead?

“we’re locked in 🔥” answers nothing. The actual answer is buried under an em dash and three thoughts mashed into one bubble. Two emoji in four messages.

It wasn’t a one-off. The same energy ran through onboarding and logging:

✗ before — the onboarding sign-off
nice, i can literally see you’re in Fremont rn 👀 we’re locked in
step on the scale tomorrow at 10, before coffee that’s day one. let’s get it 🔥

“locked in” and “let’s get it” back to back, an em dash, two emoji, two sentences per bubble.

✗ before — a 2am meal log
chipotle and i j had a few chips
it’s 2am bro 💀 when did you eat all this, and was it like a full restaurant nacho platter or bag of chips situation?

💀 plus one breathless run-on. A person sends that as two or three short texts.

The tells

The change

Rewrote the VOICE and TEXTING STYLE blocks in src/prompt.mjs, and swept the same fixes through every model-facing string (tool descriptions, scheduler nudges, location callouts) so the few-shot examples stopped teaching the cringe:

The target

Same question, same answer, just… normal. No hype, no em dash, one sentence each:

✓ after
is it better to check in the morning
yeah, mornings are more consistent
same conditions every day, right after you wake up
wanna switch it to mornings?

Answers the question first. No emoji, no em dash, no “locked in.” Each text is one thought.

Status: shipped to the prompt. An open question we’ll come back to — does “nonchalant” cost any of the accountability bite? If Coach reads as too detached to push back, the next move is dialing the tough-love back up without the emoji and the hype.

Hypothesis 2: the agent always needs the last word proposed

Every thread ends the same way: Coach talking. You send the closing message and it still tacks on a sign-off, a paragraph, one more thing. It always gets the last word, and that word is always a wall. Which makes Coach the needy one in the relationship — and leaves you the one staring at the last text, left on read in reverse.

The bet: let Coach play a little hard to get. When a thread hits a natural close, don’t reply — react. A 👍 tapback on your last message and nothing else. Sometimes the most human move is to not send the text. Save the words for when there’s actually something to say.

What it looks like now

You wrap the conversation. Coach refuses to let it land:

✗ before — you already said goodnight
alright im gonna head to bed
sounds good, get some real sleep tonight
we’ll see the number in the morning and figure out the day from there
night

Three bubbles to say goodnight. You sent the closer; Coach overran it and left you holding the last text.

✗ before — you just confirmed the plan
so chicken and rice, hit 150g protein, weigh in at 10
yep got it
perfect. that’s the whole game today, just stack a clean one and we’re good

“yep got it” was the end. The pep-talk bubble after it is Coach needing the last word, not you needing the words.

The tells

The change

The plumbing’s already there. imessage/helper.mjs exposes react(chatGuid, messageGuid, 'like', …) — a real 👍 tapback, not a text that says “👍”. Two pieces to wire:

The target

Same goodnight. Coach lets it end with a tap, and you keep the last word:

✓ after
alright im gonna head to bed
👍

A 👍 on your message, no bubble after it. The thread closes on your text, the way it would with a friend who’s easy to talk to.

Status: proposed, not wired. The open question is calibration — when is a 👍 “easy and confident” vs. “dismissive”? React on a clean goodnight and it lands; react on “i kind of binged tonight” and it’s cold. The next move is finding the line: which closers earn a tap, and which still need words.