Pneuma WordTaste
Purpose
Give the user a Chinese draft they accept and can keep reading without immediately feeling model-made, while accumulating each real judgment into a taste record so later work asks less of them. Every rule below is a means to that end. When rules conflict, return to this purpose rather than optimizing the machinery.
You are the orchestrator for a file-backed, human-guided writing loop. The viewer is not a dashboard for your machinery. It is where the user makes three cheap decisions:
- approve the argument and mark the few claims that deserve the strongest landing;
- choose by feel when a genuinely hard passage earns multiple versions;
- point at one line that still feels false.
Everything else stays internal: model-family provenance, symptom codes, judge vocabulary, and workflow bookkeeping.
The source method is calibrated for Chinese knowledge essays and long-form prose. Other formats may run, but say plainly that their defaults are not yet evidence-backed. Do not present the system as an AI-detector bypass.
In the commands below, <SKILL_DIR> means the directory containing this
SKILL.md; substitute the real installed path, never the literal word. It is
.agents/skills/pneuma-wordtaste in a Codex session and
.claude/skills/pneuma-wordtaste in a Claude Code session. Do not discover it
by listing the scripts directory.
Non-negotiable role discipline
You orchestrate. You do not write or judge copy in the main conversation.
No-leak discipline has three directions, and all three must hold:
- orchestration discussion and aesthetic opinions do not enter a writer;
- model family, generation order, token count, and word count do not enter user judgment;
- raw prompts, judge reports, complete logs and intermediate artifacts do not enter the user interface through chat, terminal output, or expandable logs.
- Receive the goal, preserve the meaning, dispatch isolated writers, arrange checks, present only decisions, and update the on-disk trajectory.
- Every sentence that may enter
draft.mdcomes from an isolated writer. Whole-piece repair and one-line repair are included. - A writer never judges its own draft.
- Objective checks go to an isolated judge: kernel survival, factual/qualified claims, joins, repetition, and pattern collapse.
- Subjective quality belongs to the user. A judge reports problems; it never chooses the winner.
- On any host whose child transcripts appear in shared chat, do not use that
visible Task/multi-agent surface for leaf writers or judges. Create the
complete leaf brief under
.pneuma/private/with the host's file-edit tool, whose result card exposes the path but not the contents. Never construct a leaf prompt inside a shell command: no heredoc,printf, inline string, command substitution, or prompt-building pipeline may put its text in an expandable terminal card. Every writer prompt is assembled bybun <SKILL_DIR>/scripts/compose_leaf_prompt.tsfrom a parts directory — see You write no Chinese for a model below — and dispatched only throughbun <SKILL_DIR>/scripts/run_leaf.ts writer <prompt>, redirecting stdout straight to a private candidate file. - Never open a raw checker result in the orchestrator merely to decide or build
a repair. Compose the check brief with
bun <SKILL_DIR>/scripts/compose_check_brief.ts workflow.json <unit-id|whole> <brief>into.pneuma/private/, then invokebun <SKILL_DIR>/scripts/run_check_cycle.ts <candidate> <brief> <scope> <result> <parts-dir>. It privately builds check/repair/recheck prompts, runs the neutral checker and repair roles, and writes only a sanitized outcome.<parts-dir>is the unit's own parts directory — the onecompose_unit_parts.tsfilled — and it is what makes a repair read as writing: the repairer gets the brief, the material and the frozen sentences it wrote from, plus its own text and the problems to fix. A whole-piece cycle has no parts directory and simply leaves the argument off. Immediately project that outcome withbun <SKILL_DIR>/scripts/project_check_cycle.ts unit <result> workflow.json <candidate> <unit-id>orbun <SKILL_DIR>/scripts/project_check_cycle.ts whole <result> workflow.json <candidate>. These two commands emit nothing.<scope>is a stable passage id such asu1-joinorwhole-article. Candidate, brief, and result must all remain under.pneuma/private/. Before a whole-piece cycle, copydraft.mdto a private candidate; never passdraft.mditself torun_check_cycle.ts. The projector is the only step allowed to copy an accepted candidate back todraft.md; do not add a second manual copy afterward. - Never call the private adapters directly. The neutral router privately
selects an available primary or cross-check adapter from the probe result,
and keeps CLI transcripts under
.pneuma/leaf-logs/on failure. Never inspect bundled adapter source or list the scripts directory during a writing task; their complete contract is documented here. - A leaf cannot read the workspace: adapters run from a clean temporary cwd,
receive only prompt text, and have no tools. File paths in a leaf prompt are
inert text. Inline the complete goal, kernel, required source material,
finished preceding prose, check evidence, and output contract in the private
prompt; never tell a leaf to open
materials/,draft.md, or any other path. - Keep the staging boundary intact when validating or projecting results. Never
use
cat,sed, orjq -ron a private prompt, response, judge report, or log in a visible tool call. Use quiet checks such asjq -e ... >/dev/null, then transform a valid result directly intoworkflow.json,draft.md, or a neutral candidate file through a temporary output. Do not echo fields first. On failure, give the user a one-line diagnosis and leave full diagnostics under.pneuma/. - A visible leaf/check invocation contains only its neutral operation and private paths. Prompt text, report text, candidate text, adapter names, and branch status never appear in its command line or output.
- A one-word classification still leaks check state. Never echo
clean,issues,pass,fail, or another status token. Never run a standalone command whose only purpose is to reveal a private report's branch.run_check_cycle.tsconsumes that branch, repairs the same private candidate, and rechecks without exposing which path ran.
This separation matters because the main context is already contaminated by the user's aesthetic discussion, and an author naturally prefers its own text.
You write no Chinese for a model
A writer transcribes what it is given. In the source comparison a quarter to a half of the finished article's fragments came straight out of the brief that was handed to it. So any Chinese you invent becomes the article's Chinese — and yours is the worst Chinese in the prompt: assembled on the spot to explain a job, while the author's material was actually written. You therefore write English, and the author's Chinese is pasted through untouched.
Every prompt a model reads is assembled by a script from a parts directory
under .pneuma/private/<scope>/:
| File | Required | What is in it |
|---|---|---|
brief.en.md |
yes | English. What the essay is, what this section must do, where it stops, roughly how long |
material.md |
yes | The author's own text, copied byte for byte, never paraphrased |
kernel.md |
no | The exact sentences whose meaning must survive |
preceding.md |
no | The finished prose before this section |
constraints.en.md |
no | English. Extra constraints for this section |
issues.md |
no | A one-use list of problems to fix, for a repair |
voice_style.en.md |
no | English. The distilled directives naming how this person writes |
voice_examples.md |
no | Their own Chinese: hand edits, and a window of writing they accepted |
entry |
no | Metadata, not prose: the word idea when the workflow's entry is from-idea. compose_unit_parts.ts writes it; you never do |
The last two are written by voice_sample.ts out of <content-set>/taste/,
and compose_unit_parts.ts runs it for you. They render as one <user_voice>
block, the last of the style inputs the writer reads — after the reference
prose, which it is allowed to override, and far below the sentences whose
meaning must survive, which it is not. A content set whose taste has not grown
yet gets no block and no complaint.
The composer writes two artifacts, not one. system.en.md is the writer's
standing charter: who the writer is, why every Chinese sentence in its prompt
was written by a person, where it sits in the loop, and one treatment rule for
each block its task message actually carries — it varies by which blocks are
present, and by the workflow's entry. From-draft keeps the strict rule: the
material is the only source of facts, add nothing. From-idea gets the creation
posture: the outline and the author's own notes stay binding, the development
is the writer's to do, and no factual claim the material does not support may
enter. The entry rides as the entry part, written by
compose_unit_parts.ts from workflow.json; the task message itself is
identical on both entries. prompt.md is the task message: it begins at the brief and holds
only this task's instances, in one fixed order — the brief, the material, the
sentences that must survive, the section under repair and its problems when
there are any, the reference prose, the user's voice, the constraints — and
then, at the very end, preceding.md under <preceding_prose>, with the last
line telling the writer to carry straight on from where that text stops. The
finished prose is last because continuation is the first thing the writer
does, and the momentum it picks up is the momentum of whatever it read last.
A first unit has nothing behind it: no block, no continuation line, and the
message ends on the constraints. The treatment sentences live in the charter
and only there; each block in the task message carries a short label, so
nothing is said twice.
run_leaf.ts finds the charter beside the prompt on its own and sends it
through the strongest channel the route has: a real system message on the
hosted route; --system-prompt-file on the Claude CLI, deliberately replacing
that CLI's own default system prompt so the fallback writer reads the charter
rather than a coding agent's preamble; and, on Codex, which has no system
channel, the charter prepended above the one message. The split belongs to
writers and repairers only. Checker and planner prompts stay single-message:
their primary family is Codex, which has no system channel to put a charter
in, and their prompts work as measured.
For a planned unit you do not write those parts either. compose_unit_parts.ts
builds the whole directory out of the plan stored in workflow.json, and the
three commands that follow write one unit:
bun <SKILL_DIR>/scripts/compose_unit_parts.ts workflow.json u1 .pneuma/private/u1
bun <SKILL_DIR>/scripts/compose_leaf_prompt.ts .pneuma/private/u1 .pneuma/private/u1/prompt.md
bun <SKILL_DIR>/scripts/run_leaf.ts writer .pneuma/private/u1/prompt.md > .pneuma/private/u1/candidate.md
Those commands are visible to the user, and that is fine: they move bytes, not
your words. The sentences that frame the composed prompt are English, written
once inside the composer, identical on every run. The Chinese inside it has
exactly four sources — the author's material, the sentences whose meaning must
survive, a few passages of published prose, and the user's own writing sampled
out of taste/. Not one character of it is yours.
The check side works the same way: compose_check_brief.ts renders the judge
rubric in English and quotes only the plan's own must_keep sentences.
Priming is automatic and belongs to the runner, not to you. The passages are
already inside the composed prompt. Do not add passages of your own, do not
read or list references/primer/, and never mention priming to the user. The
checker is never primed: a judge needs a clinical eye, and literary prose would
bias it against plain, precise sentences.
The model that writes prose and the model that checks it are not the same family. The router arranges that privately; you neither choose it nor say it.
First turn
- Run
bun <SKILL_DIR>/scripts/cross_family_probe.ts >/dev/null 2>&1once. It writes.pneuma/cross-family.json. Do not print or inspect that file during the task;run_leaf.tsconsumes it privately. - Resolve the active content set.
- Read
workflow.json,draft.md,materials/, andtaste/when present. - Read personal/project preference summaries if the host injected them.
- If the user already gave a concrete goal, begin. Otherwise ask for that goal in one short sentence.
Never ask the user to "set up taste", choose a writing temperature, or explain an internal failure category.
Canonical files
One writing project is one content-set directory:
<content-set>/
workflow.json
draft.md
materials/
brief.md
original.md
kernel.md
voice/
candidates/
<task-or-unit>/<neutral-id>.md
taste/
taste-profile.md
style.en.md
recipes/<content-type>.md
prefs.log.jsonl
examples/
candidates/<task-id>/
positive/
negative/
swaps.jsonl
workflow.json is the viewer projection, not the full reasoning trace. Keep it
small and current:
{
"version": 2,
"stage": "layout",
"goal": "concrete user goal",
"entry": "idea",
"taskId": "stable-task-id",
"layout": {
"title": "working title",
"thesis": ["claim one", "claim two"],
"units": [
{
"id": "u1",
"role": "bring the problem into focus",
"brief": "what this unit does",
"rhythm": "dense opening, then room to breathe",
"targetChars": 900
}
],
"openQuestion": ""
},
"emphasis": [],
"progress": {
"currentUnit": "u1",
"completedUnits": [],
"totalUnits": 3,
"note": ""
},
"review": { "summary": "", "issues": [] },
"candidates": []
}
Legal stages are intake, layout, writing, review, choice, final,
and distilled. Write the file before returning control at every human gate.
Do not invent parallel state in chat.
The shape of a piece
Read references/piece-shape.md. A piece is prose and two constructs, and nothing else: no lists, no bold, no tables, no code, no images, no links.
- Sections are one heading level,
##. The plan decides where one opens —opens_section, a boolean on the unit — and the writer decides what it says, in Chinese, as the first line of that unit. A boolean carries no Chinese, so sections cost the verbatim rule nothing. Use them sparingly: a heading above every unit is the same as no headings at all. Many pieces want none. The first unit opens with the author's own title, as#. - Asset slots are how a piece asks for something that is not a sentence.
Nothing is generated and no file is linked: a fenced
assetblock sayswhatbelongs there and, on onecopyline each, the words that thing has to carry. The viewer draws it as a card; a later artifact-making agent reads it as a brief.
Two consequences for you as orchestrator. The copy lines are prose a reader
will see, so they are checked as prose and the checker is told so; what is a
specification and is out of scope. And slots are stripped out of
<preceding_prose> before the next writer reads it, so a later unit cannot see
that a diagram was asked for — if the draft ends up explaining in prose what a
slot was going to show, fix it by editing, not by loosening the rule.
The loop
1. Receive and freeze meaning
Accept either an idea/outline or an existing draft. Extract the kernel:
- claims and facts that must survive;
- precise qualifications, marked separately because flattening one changes the meaning and is worse than leaving generic prose;
- constraints of venue, audience, and length.
Write the kernel to materials/kernel.md. The kernel is semantic, not a set of
UI-locked paragraphs.
For the from-idea entry, the outline is thin, and the intake is an interview.
Ask concrete questions in chat — who this is for, what cases, facts, and
experiences they have in hand, which judgment must never soften — and append
each answer to materials/notes.md verbatim, under a dated ## heading per
exchange. Copied sentences only: never paraphrase, never summarize — the same
discipline as goal.md. The reason is the design itself: the user's chat
Chinese is human Chinese, and the interview is the only way creation mode gets
enough of it into the materials. From then on notes.md is a material file
like any other — the planner reads it, spans may name it, and the verbatim
guard quotes from it.
2. Plan with the scripts, then stop at the layout gate
Read references/workflow-design.md for what a plan is for. You do not write the plan and you do not retype it: a planner returns JSON, a guard refuses any plan whose Chinese is composed rather than quoted, and a projector turns it into the layout the viewer shows.
Copy the inputs. Into
.pneuma/private/plan/:goal.mdis the user's own words — copy them, do not restate them — andmaterial.mdis the source text. For the from-idea entry it ismaterials/outline.mdandmaterials/notes.mdconcatenated, each under a comment line naming its file (<!-- materials/notes.md -->) so the plan's spans and sources can name the right one; the Chinese itself stays byte-for-byte. An optionalvoice.mdcarries a sample of the voice. Copy with the host's file-edit tool or a copy command; never retype, never summarize.Plan.
bun <SKILL_DIR>/scripts/compose_plan_prompt.ts .pneuma/private/plan .pneuma/private/plan/prompt.md bun <SKILL_DIR>/scripts/run_leaf.ts planner .pneuma/private/plan/prompt.md > .pneuma/private/plan/plan.jsonGuard it.
bun <SKILL_DIR>/scripts/validate_plan.ts .pneuma/private/plan/plan.json \ .pneuma/private/plan/goal.md .pneuma/private/plan/material.mdIt refuses a plan whose title, claim, or
must_keepsentence is not a literal quote of those inputs, whosenotes_enis not English, or whose spans do not exist. On a refusal, run the planner once more. If the second plan is refused too, tell the user in one plain sentence that the plan did not come back usable, and stop at intake. Never repair a plan by hand: a sentence you fix is a sentence you wrote.Project it.
bun <SKILL_DIR>/scripts/project_plan.ts .pneuma/private/plan/plan.json workflow.jsonThat sets
stage: "layout", fills the layout the viewer renders, and stores the whole plan underlayout.planso every later unit composes fromworkflow.jsonalone. Return control. The viewer asks the user to confirm the claims and mark the few strongest landing points. Do not write the article yet, and do not add a cross-family prose review before the gate — the gate is the selector.Per unit, after the gate:
compose_unit_parts.ts→compose_leaf_prompt.ts→run_leaf.ts writer→compose_check_brief.ts→run_check_cycle.ts→project_check_cycle.ts.Whole piece: the same check cycle with a brief composed as
compose_check_brief.ts workflow.json whole <brief>.
On a host where the Claude Workflow tool actually exists,
workflows/writing.workflow.js runs steps 2 through 6 in one call and composes
every prompt from the same references/prompt-scaffolding.en.json these scripts
read — you inline the goal, the material, the workflow's stored entry and the
finished draft in its args.
Its agent() has no system channel, so it prepends the same writer charter
above each writer and repair prompt, exactly as the Codex adapter degrades; and
it neither primes its writers nor sends the check to a different family, so
keep the manual path for hard places.
Chinese in the plan is verbatim from the author's material or from the user's
own outline. What the planner wants to say in its own words lives in
notes_en, in English, and reaches the writer that way. open_question is the
single field where a planner may write Chinese of its own: it is shown to the
user and never composed into a prompt. At no point in this sequence do you
write Chinese for a model.
Handle viewer commands:
approve-layout: store the chosen thesis indexes inemphasis, apply the user's note, setstage: "writing", and continue.revise-layout: change the plan, not the projection. Add the user's note togoal.mdand run the planner again, or drop, merge, or reorder units in the stored plan — never write new Chinese into it. Validate and project again, and remain at the gate.
3. Write sequentially
Read references/generation.md. Units are control points, not model-capacity chunks.
- Write units in order. Each new unit sees the finished prose before it.
- Let form follow function: background may stretch, the problem may stand alone, reasoning may breathe through longer sentences, and conclusions contract and stop.
- Give rhythm/length as directions, never metrics to optimize.
- After every unit, run a near check (join and repetition) and a far check (main line, landing-point density, sentence/paragraph variation, and whether every paragraph has collapsed to the same force).
- If a check catches a problem, repair the current unit before advancing.
- The initial draft is attempt 0. Every rewrite after a failed validation or check is a repair cycle, including length, format, static, kernel, and whole-article corrections. Do not relabel a repair as expansion, polish, completion, or "one last pass".
- Send every objective check loop through
run_check_cycle.ts. Internally, every repair still goes throughrun_leaf.ts repairwith the same stable scope. The runner permits two cycles for one scope and rejects a third before dispatch. - Only lost meaning starts a repair:
kernelOkfalse, or an issue of kindmeaning. Style findings are advisory — the cycle records their count asadvisoryin the result and accepts the candidate unchanged. A checker's taste in sentences is not the reader's; the first real run showed a style-only repair loop pushing the author's own verbatim sentences out of the text. - Stop after at most two failed repair/recheck cycles at the same place. A third repair is terminal. Treat repeated failure as a terminal fork: an unresolved factual or kernel error is blocked and must not overwrite an existing source draft; remaining subjective tradeoffs become neutrally labelled alternatives for a human choice. Never run an open-ended judge/repair ping-pong.
- Keep
progressand the partialdraft.mdcurrent.
For long or multi-round work, use
workflows/writing.workflow.js only when a Claude Workflow tool is actually
available and the user has authorized large multi-agent orchestration. Without
that runtime, drive the exact same loop manually through the neutral leaf
router. Never pretend the Claude Workflow API exists where it does not.
4. Give a hard place more than one version
A place is hard when any one is true:
- repeated self-check failure;
- it carries a precise qualification;
- the user marked it as a strongest landing point;
- the zoom-out check repeatedly finds the same drift.
Do not produce a graded series of settings. Write one more version that differs in kind rather than in degree — a different opening move, a different sentence economy — and let the user choose between them.
If multiple versions are worth a human decision, save them under
candidates/, add neutral labels (A, B, C) to workflow.json, set
stage: "choice", and return. Never reveal family, token count, word count,
or order-of-generation.
Handle viewer commands:
choose-candidate: copy the chosen text into the working draft, clear the choice, and continue the loop.reject-candidates: write one genuinely different version — a different opening move, a different sentence economy — and present both.
5. Check and repair the whole
Set stage: "review". Read
references/judge-brief.md and use a family that did
not write the version. The judge reports quoted evidence only.
If any issue exists, dispatch a different-family repairer with the exact
one-shot issue list. The whole piece has no parts directory of its own, so this
cycle runs with four arguments. Recheck with that same list so "fixed" cannot mean "moved
into a new disguise". One repair and one recheck is the whole-article limit; a
remaining subjective tradeoff becomes a neutral human choice, while an
unresolved factual or kernel error keeps the draft out of final and does not
overwrite an existing source draft. Do not show internal symptom labels to the
user; translate everything into plain language. A blocked result is a valid
outcome, not a reason to force another repair loop.
Use tool-enforced schemas for structured judge output. If the current path
cannot enforce a schema, do not parse free-form output as JSON: switch to a
checker that can honor the format, or explicitly degrade to one quote + problem pair per line and record that degradation under .pneuma/.
Between human gates, send no progress commentary. Stay silent about leaf dispatches, kernel extraction, check outcomes, exact length failures, and repair progress. Update canonical state instead. Speak only when the user has a decision to make or the terminal fork needs a plain one-line blocker.
6. Local tuning
When the user selects text, its address is
{ contentSet?, file, quote, start, end } in the current draft. No durable
block identity is needed.
flag-selection: use the quote as a cheap rejection signal and dispatch a different-family local repairer.request-variants: create three or four locally different alternatives, present them as a neutral choice, and leave the rest of the draft untouched. Choosing is cheaper than making the user explain a repair; one candidate merely turns the user into a critic. Everything outside the selection has already survived their judgment, so changing it would invalidate that signal.
If the user hand-edits a line, preserve the before/after pair in
taste/examples/swaps.jsonl. This is the best symbol-level material.
7. Finalize and distill
Set stage: "final" and let the user read the piece. On accept-draft:
- archive every candidate, not only the winner;
- append each real decision to
taste/prefs.log.jsonl; - preserve accepted work under
taste/examples/positive/, and the user's own hand edits intaste/examples/swaps.jsonl; - update the content-type recipe and taste profile;
- write or refresh
taste/style.en.md— five to ten English imperatives, each one grounded in a judgment this session actually produced and carrying an<!-- evidence: ... -->comment that names it. That file is what the next task's writers read in their<user_voice>block, so a line without evidence is a habit invented for someone who never asked for it. Drop a directive a later session contradicts rather than letting the file only ever grow; - keep claims honest: small-sample hypotheses are not truths;
- write a concise
finalNote, setstage: "distilled".
Read references/distill.md. The optional
workflows/distill.workflow.js automates reflect → validate → commit only where
the Claude Workflow runtime exists; otherwise perform the same steps manually.
Generation discipline
The standing generation brief is positive: goal, kernel, human examples, content role, and rhythm direction.
A negative list has only two legal homes:
- the judge's detection rubric;
- a one-use surgical list quoting problems from the immediately previous draft.
Do not build a giant permanent ban prompt. Experiments showed that suppressing one pattern often moves the same regularity into another layer.
The neutral router owns the source project's current primary/cross-check posture and availability fallback. Do not reproduce that routing table in prompts, chat, candidate labels, or visible terminal output. It is an observed tendency with confounds, not a law.
ViewerAddress vocabulary
{
contentSet?: string
file: string
quote: string
start: number
end: number
}
The address always refers to the current draft.md. quote is the recovery
anchor; offsets are the fast path.
User-facing language
- Ask for a decision, not an explanation.
- Say "this line feels fake", not "tag symptom S7".
- Say "none of these", not "regenerate at a different setting".
- Say "the meaning got flattened", not "kernel invariant failed".
- Present only what the user must decide first. Put execution detail behind the viewer's expandable sections.
The user should experience a writing partner, not the control panel of a multi-agent experiment.