Voice calibration
A forced-choice test, not a writing exercise.
Why this way. Asking someone to produce ten sample posts is
friction dressed up as rigour. Most people cannot think of what to
write on the spot, the samples overfit to whatever mood they were in,
and the people who most need a calibrated voice are exactly the ones
who will not do the homework. Then the drafts read generic and the
tool gets blamed.
Picking between two sentences takes two seconds and needs no ideas. Do
enough of them and a position emerges that no single sample could
give.
Two voices, and which one this trains
Open a first run with this, before anything else. Connected, verbatim:
There are two voices here. Your brand has one, and it is on your
website, so we have already read it. You have one too, and it is
different: it is how you write when it is you talking, on LinkedIn, on
X, in a Reddit reply. The website cannot teach us that one. You can, in
about two minutes, by picking between pairs of sentences until it is
clear which way you lean. Pages and listings get the brand voice.
Anything posted as you gets yours.
With no key nothing has read the site and there is no brand register to
hand, so the clause saying we already have comes out, and so do the two
sentences about which surface gets which voice:
There are two voices here. Your brand has one, and it is on your
website. You have one too, and it is different: it is how you write
when it is you talking, on LinkedIn, on X, in a Reddit reply. The
website cannot teach us that one. You can, in about two minutes, by
picking between pairs of sentences until it is clear which way you
lean.
Then ask which voice this round calibrates, and default to theirs.
Never call the brand register "your voice". It came from the website,
so it is the brand's. That one word is where the confusion lives.
Where things live
Two files, in the estate this suite shares: voice/ under
SKILLS_ESTATE, which defaults to ~/.claude/content. Create the
directory if it is not there.
The location matters more than it looks. draft reads the profile from
exactly this path, so a voice written somewhere else is a voice no draft
ever uses. One estate, configured once, is the whole convention: every
skill in the suite reads and writes its own subdirectory of it.
| File |
What it is |
Written by |
profile.json |
The raw state: per axis, the observation count and the split, plus the date of each round. Resumable by design. |
every round |
VOICE.md |
The style section rewritten as instructions a writer can follow. Written once there is enough to say something, not before. |
every round with enough observations to update |
Connected, both files are a cache of what was sent to the record, not the
master copy. See the connected section below.
How a round runs
Read the product context before the first pair. It is
product-context/PRODUCT.md in the same estate
(~/.claude/content/product-context/PRODUCT.md by default) and it says what
this business is, who it is for, and which words it keeps and refuses. The
pairs are then drawn from your own subject matter rather than from writing
in general, and a refused word never turns up in a sentence you are asked to
prefer, which would teach the profile the opposite of what you decided. If
the file is not there, say so in one line and calibrate anyway: the test
still works, the pairs are just blunter.
- Read
profile.json if it exists. This is resumable: a round
adds to what is there rather than starting again.
- Cover every axis once, then go to the axis nearest to settling.
Read
AXES.md. profile.json carries, per axis, an observation count
and a split. The first pass covers all eleven axes once, in AXES.md
order, before any axis is asked a second time. After that, draw the
next pair from the unsettled axis with the MOST observations, because
it is the one nearest to settling; among ties, the one whose split is
closest to even; among ties on that, AXES.md order. An axis is
SETTLED two ways, and both retire it: a LEAN, six or more observations
with majority >= 2 x minority, or VARIES, six or more observations
without that lean. Varies is an answer, not a failure to answer, so a
settled axis of either kind stops being offered. The six is a starting
rule to be tuned once real rounds exist, not a measured finding. Note
which family an axis belongs to: a lean in the unvalidated group is
reported as unknown signal, not as a finding.
- Write the items fresh. Do not reuse pairs from a previous round;
repeating an item measures memory rather than taste. Follow the
item-writing rules in
AXES.md: one axis varying, everything else
held still, both options obeying whatever hard style rules already
exist for this brand (a banned-word list, a formatting rule,
contractions on or off).
- Ask in rounds of four using AskUserQuestion. Each question is
one pair, the two options are the two poles, and the axis is never
named. Naming it invites a theory about oneself instead of a
reaction.
- Score. Each answer is one observation on one axis. Store the
count and the split.
- Stop when every axis is settled, at the bound, or when the user
stops. The bound is about twenty pairs on a first run and twelve on
a later one. After each four, say how many axes are still open and
offer to stop there. Long tests get abandoned halfway and half a test
is worse than none, so an early end with an honest list beats a round
that grinds on.
- Write the profile, then say what changed.
Reading the answers
Per axis: how many observations, and how lopsided.
- A LEAN (six or more observations,
majority >= 2 x minority) is
a real position. Write it as an instruction.
- VARIES (six or more observations, no such lean) is not indecision,
it is context-dependence. Write it as "varies", and the writer should
take its cue from the format rather than from the profile.
- Fewer than three observations is not a reading. Say so and
prompt another round rather than inventing a position.
Never present a thin result as settled. A confident profile built on
four answers is worse than an honest "not enough yet", because
everything downstream trusts this file.
What gets written
profile.json, updated every round with the new counts and split
per axis, plus the round's date. This is what makes the next run a
nudge rather than a rebuild.
VOICE.md, rewritten as instructions a writer can follow. Not
"sentence length: 0.7 toward short" but "Keep sentences short. One
clause, usually." Include the axes that came out even, marked as
varying, because knowing what is NOT fixed is useful too.
Then say plainly which axes settled with a lean, which came out varies,
and which are still open, and how many rounds it would take to close the
open ones.
The honest limit, and what to do about it
This measures taste, not habit. It captures what someone thinks
sounds like them, which is not always how they actually write. Real
samples carry rhythm, tics and word choices no preference test can
reach.
So the two methods are complements, not rivals, and the order
matters:
- The test first, because it works immediately and needs nothing.
- Samples later, gathered from posts that actually went out and
landed. Keep those in a
samples/ folder next to VOICE.md.
That turns samples from homework into a byproduct. Nobody has to sit
down and invent them; they accumulate from doing the thing anyway.
Re-run this test after a stretch of posting and the profile firms up
from both directions at once.
That is also the edge of what this skill can do alone. It cannot ask
the engines whether writing in this voice earns more attention than
the alternative would; it cannot rank one calibrated axis against a
real result; and it cannot prove a post performed because it matched
the profile. All three need a measurement this test does not have.
With AfterLaunch connected (optional, and the test works the same without it)
The calibration itself needs no key: the pairs, the scoring and the
profile it writes are the whole test. With an AfterLaunch key
(AFTERLAUNCH_API_KEY, the remote MCP server at
https://afterlaunch.io/api/mcp), the record is where the voice lives and
the local files become a copy of what was sent. A site read already gives
you the brand register; the personal one is what only this test can reach.
- Read the brand register first, before the first pair. Call
get_voice_profile with register brand. It returns the structured
voice the product itself drafts in, tone and phrases and sentence style
and what it avoids. Show it labelled plainly as the brand's voice, read
from the site, so the user sees the contrast before being asked to pick
anything, which is what the opening above is for. On either call that
sets a register, and only where has_profile came back true, check the
register field equals the one you asked for: a mismatch means the
parameter was ignored, so treat it as the tier below and label by the
field that came back. A false has_profile returns a null register and
is the honest empty answer, not an ignored parameter.
- Then read the personal register, in the same breath. Call
get_voice_profile with register personal. If has_profile is true,
show it labelled as the founder's own voice already on record, taken
from writing they supplied, so what the record already believes about
how they write is on the table before the first pair and they can say
there and then if it is wrong. If has_profile is false, say plainly
that no personal voice is on record yet and that this round is what
starts it.
- Degrade honestly, three tiers, and label every one of them. If the
call is rejected because the tool does not take a register, which means
an older deployment, call
get_voice_profile with no parameter, read
the register field in the response if it is there, and label what you
show by that. If the tool is absent entirely, fall back to
get_kb_page with slug about-my-voice, the rendered page of the same
thing, and label by its two section headings when the page carries them,
"Your brand's voice, read from your site" and "Your voice, from your
writing", calling the whole page the brand's only in the older
single-section shape. Whichever tier you land on,
the user always knows which register they are looking at.
- What this round trains is the personal register. The leans it sends
back as voice insights below are the personal one, never the brand's.
- Send every lean back after the round. For each axis with enough
observations to state a lean, one
record_insight with kind voice:
one plain sentence in the user's own terms and nothing else, for example
"Voice leans to short clipped sentences over flowing ones." Between ten
and five hundred characters. One insight per axis, not one for the whole
round. An axis with too few observations to read is not a lean and is
not sent.
Keep that sentence stable for the same axis and the same lean. No
counts, no dates, no round number. The record supersedes an insight by
the text itself, so a stable sentence means a repeat round replaces the
earlier note cleanly, while a sentence carrying "7 of 9 picks" never
matches the next round and the contradictions pile up. Known limit: an
axis that FLIPS leaves the old sentence standing, because it is
different text. Say so when it happens rather than pretending the record
is clean.
There is a daily cap of 50 insights per product run. If any write to
the record comes back rate_limited, stop writing, say so plainly, and
note that the local profile still holds the round.
- Write the settled leans back, and only those. After the round, one
set_voice_axes call carrying every axis that settled with a LEAN:
lean is a or b by the side that won, and strength is the share
of that axis's observations that fell on the leaning side, so 0.5 is an
even split and 1 is every observation one way. Nothing below 0.5 or
above 1 is a strength. An axis settles on its sixth observation, so a
settled lean carries one of three values: 0.67 (four of six), 0.83
(five of six), 1.0 (six of six). Round to two decimals. Use the ids in
AXES.md. An axis that came out VARIES is not sent, because varies is
not a position, and neither is an axis still open. If no axis settled
with a lean this round, do not call the verb at all, and say so in one
line. This writes the founder's own register and never the brand's. If
the verb is absent, which means an older deployment, say so plainly:
the voice insights above carry the round on that deployment.
- A draft already sitting on the board can be rewritten against the
fresh profile and saved back. Find it with
list_feed or get_move,
rewrite the copy against whatever this round changed, and save it with
update_draft. The voice just calibrated then travels with every move
that already has a draft, not only the ones written after this round.
Connected, the record is home. profile.json and VOICE.md are a
cache of what was sent: readable from a terminal, and they survive a
dropped connection. They are not the master copy. A correction that never
reached the record is a correction the product does not have.
Without a key there is nothing to send to, so profile.json is the only
home the round has, and VOICE.md travels only as far as whatever reads
that file by hand. The free scan at afterlaunch.io is the
honest pointer otherwise, and skip the rest of this section.
House rules
British English. No em-dashes, no exclamation marks, no Americanised
-ize spellings. Every test item obeys these too: they are hard rules
this skill already follows, not something the calibration measures.
Next
Once the profile exists, drafting is the next step, wherever drafts
get written. If the drafts still feel off after a few posts, run this
again: it will put the questions toward whatever is still unsettled.
1---2name: voice3description: Calibrate the writing voice with a short forced-choice test. Shows pairs of sentences and asks which is closer to your brand, then turns the answers into a voice profile every posting skill reads.4---56# Voice calibration78A forced-choice test, not a writing exercise.910**Why this way.** Asking someone to produce ten sample posts is11friction dressed up as rigour. Most people cannot think of what to12write on the spot, the samples overfit to whatever mood they were in,13and the people who most need a calibrated voice are exactly the ones14who will not do the homework. Then the drafts read generic and the15tool gets blamed.1617Picking between two sentences takes two seconds and needs no ideas. Do18enough of them and a position emerges that no single sample could19give.2021## Two voices, and which one this trains2223Open a first run with this, before anything else. Connected, verbatim:2425> There are two voices here. Your brand has one, and it is on your26> website, so we have already read it. You have one too, and it is27> different: it is how you write when it is you talking, on LinkedIn, on28> X, in a Reddit reply. The website cannot teach us that one. You can, in29> about two minutes, by picking between pairs of sentences until it is30> clear which way you lean. Pages and listings get the brand voice.31> Anything posted as you gets yours.3233With no key nothing has read the site and there is no brand register to34hand, so the clause saying we already have comes out, and so do the two35sentences about which surface gets which voice:3637> There are two voices here. Your brand has one, and it is on your38> website. You have one too, and it is different: it is how you write39> when it is you talking, on LinkedIn, on X, in a Reddit reply. The40> website cannot teach us that one. You can, in about two minutes, by41> picking between pairs of sentences until it is clear which way you42> lean.4344Then ask which voice this round calibrates, and default to theirs.4546**Never call the brand register "your voice".** It came from the website,47so it is the brand's. That one word is where the confusion lives.4849## Where things live5051Two files, in the estate this suite shares: `voice/` under52`SKILLS_ESTATE`, which defaults to `~/.claude/content`. Create the53directory if it is not there.5455The location matters more than it looks. `draft` reads the profile from56exactly this path, so a voice written somewhere else is a voice no draft57ever uses. One estate, configured once, is the whole convention: every58skill in the suite reads and writes its own subdirectory of it.5960| File | What it is | Written by |61| --- | --- | --- |62| `profile.json` | The raw state: per axis, the observation count and the split, plus the date of each round. Resumable by design. | every round |63| `VOICE.md` | The style section rewritten as instructions a writer can follow. Written once there is enough to say something, not before. | every round with enough observations to update |6465Connected, both files are a cache of what was sent to the record, not the66master copy. See the connected section below.6768## How a round runs6970Read the product context before the first pair. It is71`product-context/PRODUCT.md` in the same estate72(`~/.claude/content/product-context/PRODUCT.md` by default) and it says what73this business is, who it is for, and which words it keeps and refuses. The74pairs are then drawn from your own subject matter rather than from writing75in general, and a refused word never turns up in a sentence you are asked to76prefer, which would teach the profile the opposite of what you decided. If77the file is not there, say so in one line and calibrate anyway: the test78still works, the pairs are just blunter.79801. **Read `profile.json`** if it exists. This is resumable: a round81 adds to what is there rather than starting again.822. **Cover every axis once, then go to the axis nearest to settling.**83 Read `AXES.md`. `profile.json` carries, per axis, an observation count84 and a split. The first pass covers all eleven axes once, in `AXES.md`85 order, before any axis is asked a second time. After that, draw the86 next pair from the unsettled axis with the MOST observations, because87 it is the one nearest to settling; among ties, the one whose split is88 closest to even; among ties on that, `AXES.md` order. An axis is89 SETTLED two ways, and both retire it: a LEAN, six or more observations90 with `majority >= 2 x minority`, or VARIES, six or more observations91 without that lean. Varies is an answer, not a failure to answer, so a92 settled axis of either kind stops being offered. The six is a starting93 rule to be tuned once real rounds exist, not a measured finding. Note94 which family an axis belongs to: a lean in the unvalidated group is95 reported as unknown signal, not as a finding.963. **Write the items fresh.** Do not reuse pairs from a previous round;97 repeating an item measures memory rather than taste. Follow the98 item-writing rules in `AXES.md`: one axis varying, everything else99 held still, both options obeying whatever hard style rules already100 exist for this brand (a banned-word list, a formatting rule,101 contractions on or off).1024. **Ask in rounds of four** using AskUserQuestion. Each question is103 one pair, the two options are the two poles, and the axis is never104 named. Naming it invites a theory about oneself instead of a105 reaction.1065. **Score.** Each answer is one observation on one axis. Store the107 count and the split.1086. **Stop when every axis is settled, at the bound, or when the user109 stops.** The bound is about twenty pairs on a first run and twelve on110 a later one. After each four, say how many axes are still open and111 offer to stop there. Long tests get abandoned halfway and half a test112 is worse than none, so an early end with an honest list beats a round113 that grinds on.1147. **Write the profile**, then say what changed.115116## Reading the answers117118Per axis: how many observations, and how lopsided.119120- **A LEAN** (six or more observations, `majority >= 2 x minority`) is121 a real position. Write it as an instruction.122- **VARIES** (six or more observations, no such lean) is not indecision,123 it is context-dependence. Write it as "varies", and the writer should124 take its cue from the format rather than from the profile.125- **Fewer than three observations** is not a reading. Say so and126 prompt another round rather than inventing a position.127128Never present a thin result as settled. A confident profile built on129four answers is worse than an honest "not enough yet", because130everything downstream trusts this file.131132## What gets written133134**`profile.json`**, updated every round with the new counts and split135per axis, plus the round's date. This is what makes the next run a136nudge rather than a rebuild.137138**`VOICE.md`**, rewritten as instructions a writer can follow. Not139"sentence length: 0.7 toward short" but "Keep sentences short. One140clause, usually." Include the axes that came out even, marked as141varying, because knowing what is NOT fixed is useful too.142143Then say plainly which axes settled with a lean, which came out varies,144and which are still open, and how many rounds it would take to close the145open ones.146147## The honest limit, and what to do about it148149**This measures taste, not habit.** It captures what someone thinks150sounds like them, which is not always how they actually write. Real151samples carry rhythm, tics and word choices no preference test can152reach.153154So the two methods are complements, not rivals, and the order155matters:156157- **The test first**, because it works immediately and needs nothing.158- **Samples later**, gathered from posts that actually went out and159 landed. Keep those in a `samples/` folder next to `VOICE.md`.160161That turns samples from homework into a byproduct. Nobody has to sit162down and invent them; they accumulate from doing the thing anyway.163Re-run this test after a stretch of posting and the profile firms up164from both directions at once.165166That is also the edge of what this skill can do alone. It cannot ask167the engines whether writing in this voice earns more attention than168the alternative would; it cannot rank one calibrated axis against a169real result; and it cannot prove a post performed because it matched170the profile. All three need a measurement this test does not have.171172## With AfterLaunch connected (optional, and the test works the same without it)173174The calibration itself needs no key: the pairs, the scoring and the175profile it writes are the whole test. With an AfterLaunch key176(`AFTERLAUNCH_API_KEY`, the remote MCP server at177`https://afterlaunch.io/api/mcp`), the record is where the voice lives and178the local files become a copy of what was sent. A site read already gives179you the brand register; the personal one is what only this test can reach.180181- **Read the brand register first, before the first pair.** Call182 `get_voice_profile` with register `brand`. It returns the structured183 voice the product itself drafts in, tone and phrases and sentence style184 and what it avoids. Show it labelled plainly as the brand's voice, read185 from the site, so the user sees the contrast before being asked to pick186 anything, which is what the opening above is for. On either call that187 sets a register, and only where `has_profile` came back true, check the188 `register` field equals the one you asked for: a mismatch means the189 parameter was ignored, so treat it as the tier below and label by the190 field that came back. A false `has_profile` returns a null register and191 is the honest empty answer, not an ignored parameter.192- **Then read the personal register, in the same breath.** Call193 `get_voice_profile` with register `personal`. If `has_profile` is true,194 show it labelled as the founder's own voice already on record, taken195 from writing they supplied, so what the record already believes about196 how they write is on the table before the first pair and they can say197 there and then if it is wrong. If `has_profile` is false, say plainly198 that no personal voice is on record yet and that this round is what199 starts it.200- **Degrade honestly, three tiers, and label every one of them.** If the201 call is rejected because the tool does not take a register, which means202 an older deployment, call `get_voice_profile` with no parameter, read203 the `register` field in the response if it is there, and label what you204 show by that. If the tool is absent entirely, fall back to205 `get_kb_page` with slug `about-my-voice`, the rendered page of the same206 thing, and label by its two section headings when the page carries them,207 "Your brand's voice, read from your site" and "Your voice, from your208 writing", calling the whole page the brand's only in the older209 single-section shape. Whichever tier you land on,210 the user always knows which register they are looking at.211- **What this round trains is the personal register.** The leans it sends212 back as voice insights below are the personal one, never the brand's.213- **Send every lean back after the round.** For each axis with enough214 observations to state a lean, one `record_insight` with kind `voice`:215 one plain sentence in the user's own terms and nothing else, for example216 "Voice leans to short clipped sentences over flowing ones." Between ten217 and five hundred characters. One insight per axis, not one for the whole218 round. An axis with too few observations to read is not a lean and is219 not sent.220 **Keep that sentence stable for the same axis and the same lean.** No221 counts, no dates, no round number. The record supersedes an insight by222 the text itself, so a stable sentence means a repeat round replaces the223 earlier note cleanly, while a sentence carrying "7 of 9 picks" never224 matches the next round and the contradictions pile up. Known limit: an225 axis that FLIPS leaves the old sentence standing, because it is226 different text. Say so when it happens rather than pretending the record227 is clean.228 There is a daily cap of 50 insights per product run. If any write to229 the record comes back `rate_limited`, stop writing, say so plainly, and230 note that the local profile still holds the round.231- **Write the settled leans back, and only those.** After the round, one232 `set_voice_axes` call carrying every axis that settled with a LEAN:233 `lean` is `a` or `b` by the side that won, and `strength` is the share234 of that axis's observations that fell on the leaning side, so 0.5 is an235 even split and 1 is every observation one way. Nothing below 0.5 or236 above 1 is a strength. An axis settles on its sixth observation, so a237 settled lean carries one of three values: 0.67 (four of six), 0.83238 (five of six), 1.0 (six of six). Round to two decimals. Use the ids in239 `AXES.md`. An axis that came out VARIES is not sent, because varies is240 not a position, and neither is an axis still open. If no axis settled241 with a lean this round, do not call the verb at all, and say so in one242 line. This writes the founder's own register and never the brand's. If243 the verb is absent, which means an older deployment, say so plainly:244 the voice insights above carry the round on that deployment.245- **A draft already sitting on the board can be rewritten against the246 fresh profile and saved back.** Find it with `list_feed` or `get_move`,247 rewrite the copy against whatever this round changed, and save it with248 `update_draft`. The voice just calibrated then travels with every move249 that already has a draft, not only the ones written after this round.250251**Connected, the record is home.** `profile.json` and `VOICE.md` are a252cache of what was sent: readable from a terminal, and they survive a253dropped connection. They are not the master copy. A correction that never254reached the record is a correction the product does not have.255256Without a key there is nothing to send to, so `profile.json` is the only257home the round has, and `VOICE.md` travels only as far as whatever reads258that file by hand. The free scan at afterlaunch.io is the259honest pointer otherwise, and skip the rest of this section.260261## House rules262263British English. No em-dashes, no exclamation marks, no Americanised264-ize spellings. Every test item obeys these too: they are hard rules265this skill already follows, not something the calibration measures.266267## Next268269Once the profile exists, drafting is the next step, wherever drafts270get written. If the drafts still feel off after a few posts, run this271again: it will put the questions toward whatever is still unsettled.