linkedin-studio/references/fold-in-loop.md
Kjell Tore Guttormsen f0532dce3f feat(linkedin-studio): N14 — foldIns-fangst + Step 11 retro + background-headless + språkregel-akkumulering [skip-docs]
Lukker sløyfen pluginen manglet: en rettelse operatøren gjør i utgave N
håndheves i N+1 i stedet for å bli gjenoppdaget. Maskineriet fantes
(foldIns-skjema + promote→ratify siden fix #1); det som manglet var wiring —
ingenting skrev til køen og ingen fase tømte den.

- Fangst (A2-F7): Steps 2.5/3a/5.5/6.5 appender rettelsen ordrett med trigger,
  decision "pending". Ubetinget og bevisst dum — å avgjøre ved fangst om noe
  "fortjener" en regel er nettopp slik køen holder seg tom og sløyfen dør.
- Step 11 retro (A2-F8), ≤5 min ETTER scheduling: promoter køen med eksplisitt
  JA/NEI (mekanisk → atomisk contract-gate-promotering, teller kun med grønn
  --ratify; dømmekraft → operatørens språkregelfil; NEI → rejected, beholdes),
  effort-oppsummering fra MÅLT phaseLog (aldri re-estimert), og ÉN
  friksjons-spørring som besvares tilbake til operatøren. Faser 18 → 19;
  resumption-tabellen ruter scheduling → Step 11, retro → complete.
- articles.NN.retro: additiv-valgfri (default null), schemaVersion forblir 1.
- Background-headless (A2-F6): --background kjører pakken i en bakgrunnsagent
  som skriver rapporten til disk; drafting-sesjonen leser fila. Samme isolasjon
  som fersk sesjon, uten copy-paste-sømmen. Inline fan-out = eksplisitt fallback.
- Språkregler (C-10): ${DATA}/language-rules/<lang>.md (opt-in, template).
  Leses TO ganger — av language-reviewer (fanger) og av Step 4 (forebygger).
  Shippet banliste forblir baseline; brukerfila utvider.
- references/fold-in-loop.md (A2-F9): loopen dokumentert domene-generelt in-tree,
  så en adopter uten ekstern skrivekontrakt har hele sløyfen. Refs 28 → 29.

Suiter (alle grønne): test-runner 197 → 217 (Section 16u: 18 ubetingede greps +
non-vacuity self-test; fase-sveip 16 → 17 faser; floor 179 → 198) · hooks 174 ·
trends 300 · brain 134 · editions 72 · specifics-bank 45 · contract-gate 33 ·
tests 35 · render 60.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QxvWAjte7vPcF79QeSRvRJ
2026-07-25 12:39:16 +02:00

8.5 KiB

The fold-in loop — how a correction made once gets enforced forever

Capture → Classify → Promote → Enforce. This is the plugin's answer to the single most expensive failure mode in a long-running writing practice: you correct the same thing in edition after edition, because the correction lives in a chat transcript that is gone by the next session.

The loop makes a correction stick. You fix something in edition N; the machine enforces it in edition N+1 without you remembering it existed.

Domain-general by construction. Nothing here is specific to a topic, a language, or an author. The stages and the artifacts they write are the same for any adopter. Where this document says "the contract", read "whatever document holds your writing rules" — the plugin ships one (references/longform-quality-rules.md) and it is enough.


The four stages

Stage Where it runs What it writes Who decides
Capture /linkedin:newsletter Steps 2.5, 3a, 5.5, 6.5 — every step where the operator corrects prose one row in articles.NN.foldIns[], decision: "pending" nobody — capture is unconditional
Classify /linkedin:newsletter Step 11 (retro) classification on the row: mechanical-block / mechanical-warn / judgment the command layer proposes, the operator confirms
Promote /linkedin:newsletter Step 11 (retro) mechanical → a scripts/contract-gate rule; judgment → a line in the language-rules file / the craft checklist the operator (JA/NEI per row)
Enforce Step 4.5 (contract-gate), Step 4 + language-reviewer (language rules) nothing — it reads deterministic; no model in the loop for the mechanical half

1. Capture — never lose a correction

The four fold-in steps are the four places where an operator's judgment enters the text: skeleton annotations (2.5), spine annotations (3a), editorial flags (5.5), cold-review flags (6.5). Each appends the correction near-verbatim with the trigger that surfaced it, and leaves it pending.

Capture is deliberately dumb and unconditional. Deciding at capture time whether a correction is "worth" a rule is how the queue stays empty and the loop dies — a five-second append is cheaper than re-discovering the rule in three editions' time. Capture only what the operator actually corrected; do not invent rules the operator did not ask for.

Not every correction becomes a rule. A fix that is true only of this edition's facts ("that number is 4.2, not 4.4") is edition-specific and does not belong in the queue; a fix that would be true of the next edition too ("never open with a meta-sentence about what the text will do") does.

2. Classify — mechanical or judgment

Two kinds of correction, two different permanent homes:

  • Mechanical — provable by string, regex, or count with ~zero false positives ("this exact phrase is banned", "no more than one em-dash per 50 words"). It becomes a gate rule and is enforced before a draft ever reaches a human.
  • Judgment — real editorial discretion ("the conclusion overloads; it needs one grip, not four"). No gate can prove it. It becomes a checklist line read by the craft reviewer and by the drafting step.

Misclassifying a judgment call as mechanical is the expensive error: a gate rule with false positives trains the operator to ignore the gate, and a gate nobody trusts enforces nothing. When unsure, classify as judgment.

3. Promote — the operator gates, atomically

Each pending row gets an explicit JA / NEI from the operator. NEI marks the row rejected — kept for traceability, never deleted, because "we considered this and said no" is itself worth not re-discovering.

JA promotes the row atomically: the rule entry, the contract row that documents it, and the manifest row that binds them are written together, and the fold-in row is marked promoted with the resulting rule id. A rule that exists in code but not in the contract is a gate enforcing something undocumented; a contract row with no rule is a rule nobody enforces. Both are drift, and both are what the ratify check exists to refuse:

cd "${CLAUDE_PLUGIN_ROOT}/scripts/contract-gate" && node --import tsx src/cli.ts --ratify

Green ratify is what makes the promotion count. If it is red, the promotion is not finished.

4. Enforce — the loop's whole point

  • Mechanical rules run at Step 4.5 (contract-gate) on the full draft, before the costly AI gates. A violation dies there.
  • Judgment rules are read at Step 4 (drafting/consistency) and by language-reviewer at Step 6.5 — so they prevent the defect as well as catch it. A rule that only ever catches is worth half a rule.

Language rules accumulate in a user-owned, per-language file under the per-user data dir (${LINKEDIN_STUDIO_DATA:-$HOME/.claude/linkedin-studio}/language-rules/<lang>.md, template: config/language-rules.template.md). The shipped ban-list in references/longform-quality-rules.md rule 3 stays the baseline and is never edited by the loop; the user file extends it. Absent that file, the baseline applies alone and nothing warns.


Adopting the loop without a private writing contract

The author's own setup keeps an upstream writing contract with a rule manifest outside this plugin. You do not need one. The plugin ships everything the loop requires:

  • rules as data — scripts/contract-gate/src/rules.ts
  • the baseline craft rules — references/longform-quality-rules.md
  • the accumulating language file — config/language-rules.template.md
  • the in-tree craft checklists — agents/editorial-reviewer.md, agents/language-reviewer.md

If you run the plugin without an external contract, --ratify has nothing to bind against and says so plainly; promote to rules.ts + the language-rules file and treat those as your contract. That absence is not a gap — it is the default configuration. (Same mirror rule as agents/editorial-reviewer.md: an upstream contract, when it exists, is the source of truth and the in-tree copy must not drift from it; when it does not exist, the in-tree copy is the contract.)


The other accumulation silos

The fold-in loop is one of five places the plugin accumulates. Step 11 is where they are all in view at once, because a retro that only empties one queue leaves the operator believing the others are empty too:

Silo What accumulates Written by
fold-in loop (this doc) corrections → enforced rules Steps 2.5/3a/5.5/6.5 → Step 11
specifics-bank (scripts/specifics-bank) the operator's lived specifics + where each was used Step 1.5 / Step 8 record-usage
brain (scripts/brain, consolidate) published-only knowledge from locked editions Step 8
voice (voice-trainer, drift log) the chronicle voice + its recurring drifts Step 8 auto-gold / voice-scrubber
A/B learnings (/linkedin:ab-test) which variant actually won /linkedin:ab-test adopt verdicts

Each is deterministic, each is read by an upstream phase, and each is independently useless if never read. A silo nothing reads is a diary, not a loop.


Anti-patterns

  • Capturing nothing because the queue "should stay clean". An empty queue after a full edition means capture is broken, not that the edition was perfect.
  • Promoting everything. A rule set that grows every edition without ever rejecting becomes noise, and the gate loses the operator's trust.
  • Promoting without ratify. A promotion that leaves rules and contract out of sync has not happened, however good it felt.
  • Emptying the queue silently. The retro promotes with an explicit verdict per row; a queue drained by the model's own judgment is the operator's authority quietly taken away.
  • Writing into the operator's own notes. The retro asks one friction question and hands the answer back. Their register is theirs.

  • ${CLAUDE_PLUGIN_ROOT}/scripts/contract-gate/README.md — the deterministic mechanical gate + the ratify binding
  • ${CLAUDE_PLUGIN_ROOT}/references/longform-quality-rules.md — the shipped baseline rules (rule 3 = the ban-list the language file extends)
  • ${CLAUDE_PLUGIN_ROOT}/config/edition-state.template.jsonarticles.NN.foldIns + articles.NN.retro schema
  • ${CLAUDE_PLUGIN_ROOT}/commands/newsletter.md — Steps 2.5/3a/5.5/6.5 (capture) and Step 11 (classify → promote)