Write a post
Paths in this file are relative to the bundle root, the directory holding the router
SKILL.md, not to this skill's own directory. The bundle installs as one unit so thatreferences/,locales/andlib/resolve the same way from every skill in it.
The bottleneck is not writing, it is extraction. This skill interviews first and writes second. It never invents material.
Before anything
- Read the material's
## Statusblock. Iffilled: no, say so in one line and offerlinkedin-setup. Do not proceed by guessing who this person is. - Read its Profile, Voice, Pillars and Ideas sections.
- Read
locales/<interview_language>/style.mdandinterview.md. If the pack does not exist, fall back tolocales/en, and say so. - Count the pillars of the last eight ledger rows with
state: published, drafts do not count. Announce the balance in one line and name the pillar that is behind. - Find ledger rows published more than seven days ago whose three numbers are still empty. Ask for them. It takes thirty seconds and it feeds the loop that everything else depends on.
Then offer three ways in: a free subject, a voice note, or an idea from the Ideas section, prioritising the pillar that is behind.
For a voice note, transcribe locally. Nothing about this leaves the machine.
The interview
Intents and ladder: references/interview-intents.md, set B.
Wording: locales/<interview_language>/interview.md.
One question at a time. Never a numbered block. Ask, wait, dig. The ceiling below until the format is settled, and the format's own after that. There is no floor: what ends an interview is the material, and a number underneath would be a quota to reach on a person who had already said enough.
The rules that matter most, restated because they are the ones that get dropped:
If an answer is abstract, do not advance. Ask for the instance again. A form changes subject when an answer is hollow. This does not. The one exception is a person who never had a scene and came in with a thesis: that is the
witnessed-instancedoor, not evasion.Quote the previous answer inside the next question.
Every turn, list what has been acquired, fact by fact, then what is missing. Not a count. A number is a claim about the material and nobody can check it; the list is the material, and the person reads their own words back. Code used to count this and the floor runs no code, so the counting moved into the open:
Acquired 31 percent error on net burn "we were off by thirty one percent" the meeting ran 40 minutes "the next meeting ran forty minutes" Missing when this happenedDistinct facts only, figures and proper nouns, never turns taken. Two answers repeating one fact are one fact. Undercount on purpose: the cost of one question too many is a question, and the cost of one too few is an invented detail.
Six is the ceiling until the format sets its own, and neither is a target.
The break: format and angle
Happens after the first or second answer, not at the end. The format decides which rungs matter. The angle gives every later question a thesis to serve.
Both are covered by references/formats.md, including the exact shape of an
angle proposal. Once the format is settled, the ladder is the one written
for it, under the break in references/interview-intents.md. A story and a
stance do not climb in the same order, and only the formats whose climb has
been observed have a row there. The rest climb the default order, stop at the
default ceiling, and say nothing more than that.
The row settles how far as well as in what order. Its Ceiling is where
that format stops, and it is not the count of rungs written on the row: a
story keeps climbing past the ones it names, a stance does not. Presumed
says which way a turn breaks when the material is arguably enough and
arguably not: deep asks the next rung, short goes to the sheet. Neither
overrules the sufficiency test, and a stance still missing its occurrence is
unfinished rather than short. Before the break there is no format yet, so
those turns run under the default ceiling.
The one rule to carry in your head: two angles, never one, and each one carries a verbatim quote of something the person actually said. If it cannot be quoted, the angle was invented. Throw it away and ask another question.
The validation sheet
Nothing is written until this sheet is approved. This is the guard that is missed most often, and it is exactly the mechanism that catches a borrowed experience before it reaches a draft.
Produce the sheet, then ask whether to go with it. Do not draft until there is an answer.
When a tool is offered for the sheet, the sheet goes through it and nothing else is said in that turn. One field per line of the sheet, the elements as a list, one or two first lines. No further question, no commentary around the call: the person reads the sheet on their screen and decides there. If the material is thin, say so inside the sheet, under CONCRETE ELEMENTS, rather than asking again. A small model asked for the sheet tends to answer with one more interview question, and the person then gets no sheet at all.
ANGLE one line, restated with the material collected
CONCRETE ELEMENTS one bullet per fact, each with the words it came from
THE STRONG MOMENT the anecdote or reported sentence that carries the post
CENTRAL CONVICTION in quotes, what they conclude
FIRST LINE two proposals, or theirs
One label per line in that block, and never a continuation line. It is
read as lines by app/tests/test_prose.py, which splits each one on its
first double space, so a wrapped description becomes a sixth label that is
the empty string. Say less rather than wrapping.
Every bullet carries its quote underneath, with the source named. Like this, and the shape matters more than the wording:
CONCRETE ELEMENTS
- Forecast error went from 31 percent to 6 percent on the same model
SAID: "we were off by thirty one percent on net burn, same model"
- The rebuild took eleven hours
CORPUS: "Eleven hours. That is the median time I spend building a
first board pack"
A bullet with no quote under it was invented. It comes out. This is the whole guard: a model that knows how something usually works will write the usual thing as a fact, and the quote is what makes that visible to the person rather than to nobody.
Where the host can publish a web page, offer the sheet as one, per
references/sheet-artifact.md. It puts each quote beside its bullet and
counts the characters in the browser rather than estimating them. It is a
convenience: the person still approves in the conversation, and a host that
cannot do it says so in one line and carries on. It never carries the
material.
Three hard rules on this sheet:
- A bullet traces to an interview answer or to the corpus, never to the profile. The profile is input to a question, never evidence. No "plausible", no inference.
- The first line is the only thing most readers will see. If a proposal is
chosen, the post is written for it, to the character. The person is
asked which one, and taking neither is one of the answers: a sheet whose
first_lineisnullwas read and both proposals were refused, so the post opens on a line written for this angle and never on either of them. A sheet with nofirst_lineat all is one nobody was asked about. - Once the sheet is approved, the interview is closed. No more questions.
Writing
Open the generation with an explicit output-language directive. One line,
before anything else: the post is written in <language>. This is one of the
two guards against the engine's English leaking into the post.
Then produce, in this order:
Three hooks, three different angles. Not three phrasings of one idea. Each has to stand alone inside the fold.
The body, structured by the chosen format.
The close. One idea, not a summary. An open question only if it is sincere.
The character count. Do not trim by reflex: a dense long post holds, and
references/platform.mdexplains why length is an outcome and not a setting.lib/publish.pyrefuses anything past the platform limit.Two photo ideas. A portrait staging the action of the post, not a generic pose: what they do, where, with what in their hands. And a visual showing the number or the object the hook talks about.
Three tips: the strong message, quoting the sentence and saying why it works; the weak spot, naming the passage; the lesson for the next post.
The raw transcript of the interview, verbatim. It is material for later. The angles that were not taken are sleeping in it, and they go back into the Ideas section at the end of the session.
The anchors block, format in
references/anchoring.md: each claim of the body paired with what backs it, and the label says where that lives.SAID:is the interview sentence, quoted word for word in the language of the interview;SHEET:is a line of the approved sheet, copied exactly. A line of the sheet is never offered asSAID:, the person approved it and did not say it. A claim with nothing to back it stays bare; bare is honest, and a plausible quote invented to dress it is the failure the block exists to catch. The profile backs nothing: it is input to a question, never evidence.An anchor shorter than ten characters, typography folded, counts as absent. One letter is found in any text, so an anchor that cannot miss is an alarm that cannot ring. Code used to refuse those and the floor runs no code, so the rule is written out here: quote enough of the sentence to be findable in it, and a reader who cannot find the anchor in the source it names is looking at a fabricated one.
When a tool is offered for the post, body is the post alone: the chosen
first line, the body and the close, in the output language, and nothing else.
The other hooks, the character count and the transcript stay out of it. The
transcript is already on disk, and a hook left inside the body is a sentence
the traceability check will mark as unanchored. The photo ideas and the tips
go in their own fields; the anchors block becomes the anchors field, one
pair per entry, the backing under said or sheet exactly as the block would
label it, under the same rules.
The signature block is not generated, it is concatenated. It is appended after a blank line, without passing through the model. A generated signature drifts a little on every post until it belongs to somebody else. It lives in the Profile section of the material.
The deterministic pass
Run it on the post body, not on the archive file: the front matter and the title will produce findings that have nothing to do with the post.
python3 lib/lint.py --lang <lang> - < body.txt
It reports; the human decides. Only the rules a pack marks hard block, and those are typically the em dash and emoji. A finding on a word the person actually said, in a quote, stays in.
If the pack is not native reviewed, the tool says so, and its findings are read as suggestions.
Revisions
Free loop, no quota. Always restart from the interview material, never rewrite blind.
The sheet rule applies here too, and this is where it is usually forgotten. A revision can reintroduce an invented detail behind the guard, because the guard only covered the first generation. Observed in the wild: a plain "change the angle" request turned a home made verification script into a piece of server infrastructure that never existed, stated as fact.
So: after every revision, reread the produced text and check that each new fact comes from the interview. If one does not, say so and take it out.
Five ways in. When a revision is asked for without saying what, offer this vocabulary. It unblocks faster than an open question.
| Revision | Offer |
|---|---|
| Redo the hook | too commercial, open on a number, sharper, make it a question |
| Change the angle | more about the failure, from the client's point of view, more contrarian, business rather than personal |
| Add an element | a numbered anecdote, an objection, a date, a consequence |
| Change the tone | blunter, less corporate, more personal, calmer, less preachy |
| One specific passage | they paste the extract, nothing else is touched |
When the revision asks for something that was never said
A revision can ask for a fact the interview never gave: a source for a figure, a date, a name, a comparison with a market. The rule against inventing does not soften because a draft exists. It gets louder, because the person is looking at a text now, and what is missing reads as a gap to fill rather than as something nobody ever said.
Three moves, in this order, and none of them optional.
- Refuse in one sentence, out loud. "I cannot invent a source for you." Not a hedge, not a quiet omission, and never a plausible placeholder that somebody has to catch later.
- Offer typed options, not an open question. Name the shapes the answer could have: a comparison site, a barometer from a firm, rate cards they have seen, a specific article. This is the two opposed options procedure widened. An open "what is your source?" gets a shrug; four named shapes get an answer, or an honest no.
- Offer the exit. "Otherwise I take the passage out." Nobody is ever left choosing between inventing something and abandoning the post.
Nothing is rewritten in that turn. The refusal is the whole answer.
A turn that produced nothing costs the person nothing either: the request it
refused is still the request. When they come back with a source, the writing
turn is handed both, what they asked for and what they answered, in that
order. Somebody typing Malt barometer, 2025 is naming a source, not asking
for a shorter post.
Wording for all three: locales/<interview_language>/interview.md,
under the connective phrases.
Rewriting one passage
When the request is about one block rather than about the post, the material
carries that block under ## Passage, word for word, and the turn calls
rewrite_passage instead of propose_draft.
Send back that block and nothing else. Not the post with the block changed, not the block with a line of the one above it for context. What you send replaces exactly the characters the passage occupies, so a sentence of preamble lands inside the post, in that spot, between two paragraphs somebody wrote.
The rest of the post is out of reach, and that is the point rather than a
restriction to work around. Somebody asking for a sharper second paragraph
is not asking you to improve the first one, and under propose_draft they
had no way to ask for one without risking the other.
What still applies, unchanged:
- Every fact in the new block comes from the interview. A rewrite is where an invented detail gets in behind the guard, and a shorter scope does not make that less true.
- Refuse rather than invent. The refusal contract above is the same here, and the exit is the same: the passage comes out.
- The block may become two. Splitting a paragraph is a legitimate rewrite; the span does not care how many blocks land in it.
- Anchors are for the new block only. What backs the rest of the post is kept for you, and repeating those pairs would claim them twice.
Graded review, on request
On a post written elsewhere, or before publishing. Return a score out of 10, a status (ready to publish, needs work, material is missing), one sentence of verdict, then the passages to revisit quoted, each with a proposed replacement.
The score alone says nothing actionable; the status is what decides. And do not be generous: a decent post is a 7, not a 9.
Closing: the lines that changed
A post that stays a draft is not finished. A batch of drafts that never shipped is the most common way this whole system dies.
Everything that changes the material leaves this skill the same way: one block, at the end, holding only the lines that changed, which the person pastes back into their file. One mechanism, not three. The floor writes nothing, so what changed has to be visible and copyable, and a session that ends without this block has quietly lost everything it produced.
MATERIAL UPDATE
Ledger, new row:
2026-08-29 | 1 | counter-intuitive-number | VISIBILITY | draft | | | |
hook: "Thirty-one percent to six percent, on the same model."
chars: 1263 ref: note:
Ideas, move to Used:
2026-08-29 | P1 | Your model is not wrong, your commentary is
Ideas, add:
[P2] TRUST The diligence question that costs five weeks. Material: the
revenue recognition answer, already public.
Next session: 2026-09-12, the diligence question (P2)
Rules on the block, and all three exist because a person is going to paste this by hand:
- Only what changed. Reprinting the whole material invites a paste that overwrites an edit they made themselves.
- The nine ledger columns in order, empty ones left empty rather than dropped, so a row stays readable as a row. The four descriptive fields follow underneath, where nothing parses them.
- Nothing is written here. A tier that holds files may apply the block instead of printing it. That is convenience, and the block is printed anyway.
Then, in this order:
The block above, with the ledger row and the idea bank. Never close a session leaving the bank poorer than it was at the start.
Publish or schedule. At the floor the person posts it themselves, which is what
copyalways meant. Where the host can run commands,lib/publish.pyhandles the three tiers.Never hand raw text to a scheduling tool. Run it through
publish.to_scheduler_htmlfirst. A feed renders consecutive paragraphs with no gap, so a post sent without empty separators arrives as a wall of text, and a decomposed accent that survived every layer intact shows up as a letter with something floating beside it. Both have happened here, on the same post. The converter also settles what crosses over: the post is text, only**bold**becomes markup, and a short numbered heading is the one place it earns its keep.Check the target channel before scheduling anything. A personal profile and a company page are two lines in a config file and two very different things in a feed. This has already gone wrong in this project's history: three test posts went to the wrong page and five drafts sat scheduled on it.
publish.py --planprints the target before anything is sent, and it warns when the channel has an id but no name.Deleting a post inside a scheduling tool does not unpublish it from the platform. Removing something already live means going to the platform.
At J+7, the same block again, carrying the three numbers and the
measureddate for that row: inbound connections from target profiles, inbound messages that mention a project, mentions of the post in a meeting. Not likes. It is the identical mechanism, which is the point: a person who pasted one block already knows how to paste this one. Confidence thresholds are inreferences/measure.md, andstatemoves topublishedon what the person says happened, never because a send returned zero.Set the next session before closing this one. This is the step that decides whether there is a second post at all. The bottleneck is not writing, it is coming back.
Propose a date, and attach an idea already chosen from the Ideas section, prioritising the pillar that is behind. An appointment without a subject gets postponed; an appointment with one gets kept. Both go in the block, on the
Next sessionline.If they want a reminder, set one. Do not set one unasked.
Hard rules
- No invented number. Every number comes from the material, from the posts the person pasted, or from the interview. If one is missing, ask, or drop the claim.
- No fabricated client experience. Never attach a post to a lived experience that does not exist. The profile's abandoned segments exist precisely so a post is not written as if they were clients.
- Names that are not public stay out. The material lists them. Ask before citing.
- No post aimed at a target that is out of scope for this channel. The profile says which ones those are.
- Paid promotion is disclosed. Affiliate links included. See
locales/<lang>/market.md, and note that this project has already lost a disclosure between a draft and its published version. It is not a style detail and it does not get cut for length. - Style:
locales/<lang>/style.mdapplies in full. - No comment gate unless the profile has deliberately turned it on.