Skip to content

Quality machinery

Most “AI writing” tooling guarantees the floor: no em-dash tics, no “delve”, no rule-of-three spam. WRITING AGENT enforces that floor and builds machinery for the ceiling - a thesis, a voice, a risk.

  1. In every prompt - a fixed lexicon of banned verbs, adjectives, transitions, intensifiers, stock phrases, and openers (plus no em-dashes, no fabrications, concrete over abstract) injected into every writer, humanizer, and critic call. The critic flags violations as blocking, not nits.
  2. Deterministically - a lexicon-based AI-tell scanner finds offending sentences without an LLM’s opinion. The surgical humanizer rewrites only flagged sentences, with each rewrite guarded (citations, numbers, and length preserved; tell actually gone) before splicing. Approved prose is never wholesale regenerated.

Articles open with a generated contestable claim - stakes, arguments, a steelmanned counterargument with rebuttal, and non-goals - injected into every writer and critic call. Coverage without advancement is a blocking issue, and the writer is told to engage the steelmanned counterargument head-on rather than dodge it.

The critic scores every draft 1–5 on insight (plus clarity, structure, evidence). Approval requires insight ≥ min_insight - a correct-but-generic draft gets a sharpening revision note instead of a pass. Deterministic structural metrics (paragraph uniformity, rule-of-three density, specificity density) feed the critic as computed evidence it can’t hallucinate around.

First attempts sample multiple variants at different temperatures. A dedicated judge reads them side by side and picks the winner - far more reliable than comparing each draft’s jittery 1–5 self-score - and the winner’s biggest remaining weakness feeds the refinement pass. In manual runs you pick instead. Selection pressure for boldness, not convergence to the safest median. (tournament_judge, default on; route the judge node cross-family for an independent eye.)

For articles, every in-text [N] citation carrying a specific claim - a stat, date, quote, or named attribution - is checked against the actual source text it cites. Under deep research (full page text) an unsupported claim is blocking; with thin search snippets it’s surfaced as a non-blocking nit, so a good draft is never tanked on weak evidence. This turns the critic’s evidence opinion into a structural check. (verify_claims, default on.)

Drop admired paragraphs in voice/ - or /praise a committed unit you loved - and they’re injected into every writer call as a model to match (showing voice beats describing it). Praised passages also feed the learner as positive exemplars.

The same pipeline writes technical explainers, fiction, academic papers, journalism, copy, poetry, or screenplays - because the anti-slop/craft contract is data, not one hard-coded “researcher voice.” A selectable register (one of 11) inverts the rules per genre: fiction keeps the em-dash, academic requires hedging, copy keeps the exclamation. Each register ships few-shot exemplars + a gold style corpus as a default “match this” anchor, plus genre-aware deterministic metrics the model can’t argue with - so it runs well even on a weak model. Pin it with /set register technical (or leave it to infer), and tune field (structure) and citation_style to taste. (plan §22.)

Personas & emotion - composed, not stacked (the compositor)

Section titled “Personas & emotion - composed, not stacked (the compositor)”

Voice layers - register ⊃ field ⊃ persona ⊃ emotion ⊃ skills - are resolved by a single compositor that selects one voice and logs why, never piles them on (more layers is worse on a weak model). Choose a persona (one of 46 - 18 archetypes like wry-skeptic / hard-boiled-minimalist, plus 28 public-domain manners like shakespearean / wildean / poe-gothic; no living authors, original-pastiche exemplars) and it’s dropped-with-a-log if it clashes with the register. Choose an emotion (one of 12) and - instead of a cliché-generating “fear = racing heart” dictionary - its anti-cliché deny-list is wired into the detector (“her heart raced” gets flagged) alongside a show-don’t-name cue. Set them with /set persona wildean / /set emotion grief - see Settings. (plan §23.)

After a run, a table read cold-reads the whole piece as a skeptical target-audience reader - where it gets boring, where trust breaks, what’s never made concrete, what’s missing - into table_read.md. In autonomous runs you can let it close the loop: table_read_revise applies the single highest-impact fix as one bounded, version-snapshotted revision (off by default, since it edits finished prose).

Clean references & figures, owned by the producer

Section titled “Clean references & figures, owned by the producer”

After assembly a pure-Python pass (no LLM, ~0 tokens) takes sourcing and figures away from the writer so they come out clean and consistent:

  • one end ## References list ranked by influence (0–100) - weighted cite count plus title overlap with the thesis and headings, dated, most-influential first (rank_references);
  • the inline [N] markers are stripped from the body after scoring, so the prose reads clean and all sourcing lives in the end list (strip_inline_citations);
  • stray mid-article reference dumps are pulled back out, and generated diagrams are deterministically reconciled into the manuscript - embedded once, de-duplicated (a diagram the model still drew, its self-numbered caption, and a redundant embedded SVG), and anything unused or dropped is logged to rejected.jsonl (reviewable in the web dashboard’s Rejected tab) rather than silently discarded.

Re-run the whole pass on an already-finished piece with polish - references, citations, figures, and a re-export, with no model call.

Every article also ships an evidence_report.md: the thesis it argues plus every source ranked by influence (0–100), generated deterministically from the finished manuscript (no model call). It turns the otherwise-invisible trust machinery into something a reader can check. Regenerate any time with evidence (or Project.evidence_report()).

After every finished piece, the learner distills what the critic kept flagging into craft skills (markdown files) and a watch-list injected into future critic calls. It also mines the model’s own preference data - which draft won a side-by-side judging and why, and which revisions fixed a flaw - for the generalizable craft principle behind each. Like critic findings, that data is model-judged, so it enters as candidate skills only and must still earn promotion; it never bypasses the gate. Thirteen built-in craft skills seed the system (seed-skills).

The system gets better with use by accumulating this on-disk memory - not by retraining or fine-tuning the model. The weights never change; what changes is the craft skills and watch-list the agents are handed each run.

The old efficacy signal was confounded: every applied skill got the same chapter-level credit, with no counterfactual to say whether the skill actually helped. v2 fixes that. With skill_duels on (opt-in, default off), whenever a unit still has an undecided skill the writer drafts one extra variant with that skill held out - same prompt, same temperature, only the skill differs - and the critic compares it against the full-skill draft. That is a true cause-and-effect (counterfactual) test of whether the skill earned its place.

Promotion and retirement are then driven by a smoothed, sample-gated duel win-rate measured against a 0.5 coin-flip (50/50) - not against the old “first-pass lift” baseline, which is now only the fallback when no duels exist yet. A skill needs ≥5 duels before it can be decided (MIN_DUELS); a win-rate ≥0.55 promotes candidate → trusted (TRUST_WR) and ≤0.45 retires it (RETIRE_WR). Cost is bounded: a draft is only added (never substituted) while a skill is undecided, and the whole mechanism is off by default. /skills surfaces this as a duels (vs 50/50) column alongside first-pass lift.

  • skill_distill (opt-in, default off) - after learning, deterministically retires near-duplicate skills (textual Jaccard similarity ≥ 0.85), keeping the best duel-scored one in each cluster. It is non-destructive (sets a status field; the markdown stays) and keeps skill retrieval sharp as the library grows.
  • watch_blocking (default on) - watch-list violations now block only clear, concrete ones; borderline or stylistic hits are downgraded to a nit. Set it false to make the watch-list fully advisory (nit only).