core/docs/curriculum/writing-chain-harvester-spec.md
Shay ac75cfc659
docs(adr-0087): rhetorical style as selection axis + writing-chain harvester spec (#72)
Pre-work for a writing-curriculum extension to CORE.  Two companion
documents, both Proposed status (no code shipped).

docs/decisions/ADR-0087-rhetorical-style-axis.md
  Pins rhetorical style as a third selection axis — sibling to anchor
  lens (ADR-0073), orthogonal to register (ADR-0070).  Substantive
  axis: trace_hash DISTINCT across styles (style changes which moves
  the composer requires and which frames the realizer emits, which
  changes the propositional plan, which changes the trace).

  Four anti-patterns explicitly named and rejected:
    - style as motor (re-couples realizer to geometry; same shape as
      the ADR-0085 fusion-operator rejection)
    - style as register variant (conflates substantive with stylistic)
    - style as identity axis (bloats identity doctrine)
    - style auto-detected from user input (operator-chosen only)

  Pack shape mirrors packs/anchor_lens/.  default_unstyled_v1 is the
  null-lift pack identical to no-style behavior.  Three CI invariants
  proposed: rhetorical_style_null_lift, schema validation, three-axis
  orthogonality.

  Substrate-only ADR — no consumer code, no genre packs.  Consumer
  integration is a follow-up ADR (composer + realizer extensions
  that read permitted_frames + required_moves_per_claim +
  forbidden_moves).

docs/curriculum/writing-chain-harvester-spec.md
  Layer 0 of the writing curriculum.  A deterministic tool that
  extracts candidate (subject, predicate, object) triples from
  reviewed expert prose and surfaces them as proposals to the
  existing teaching/review pipeline.

  Five stages (segment → classify → extract → propose → audit) —
  pure-Python rule-based, no LLM generation, no auto-acceptance.
  Trust boundary: reviewer accept/reject via the existing
  core teaching propose/review path.  No bypass permitted.

  The harvester is a proposal PRODUCER, not a proposal CONSUMER.
  Plugs into the existing pipeline without inventing a new review
  mechanism.  Each proposal carries source_id + source_line + the
  exact source_clause it came from for reviewer verification.

  First-implementation acceptance criteria deliberately tight:
  Stage 0+1 with dry-run only.  Stages 2-5 are follow-up PRs.

Substrate-first sequencing pattern (ADR-0084 → 0085) reused
throughout.  Both documents acknowledge open questions deferred to
implementation phase rather than pre-deciding.

Why now: a writing curriculum is being scoped.  Without this ADR,
every downstream PR faces the same "should style be a motor?"
question and the temptation to reach for the geometry will recur
every time the realizer produces a stilted surface.  Pinning the
axis up front prevents that recurrence.
2026-05-20 16:09:16 -07:00

12 KiB

Writing-Chain Harvester — Specification

Status: Proposed (specification only — no code shipped) Date: 2026-05-20 Author: Shay Companion to: ADR-0087


Purpose

The writing curriculum needs thousands of ratified rhetorical chains (claim → evidence → warrant; evidence → support → conclusion; hedge → uncertainty → revision; etc.) to operate at PhD level. Today the entire cognition_chains_v1 corpus has ~21 active chains for ~22 lemmas — a number chosen so each chain could be hand-authored and reviewed by the project author. PhD-level writing operates on orders of magnitude more.

Two paths to fill that gap:

Path Cost Risk
Hand-author every chain Prohibitive None (matches existing discipline)
LLM-generate then accept Cheap Violates the no-LLM-content rule; reintroduces drift

Neither is viable. This spec defines a third path: a harvester that proposes candidate rhetorical chains by extracting them from reviewed expert prose (peer-reviewed journals, ratified technical documentation, the project author's own canonical writing) — one proposed (subject, predicate, object) triple at a time, each surfaced to a human reviewer for accept/reject via the existing teaching/review loop.

The harvester does no LLM-style generation. It extracts structure from already-reviewed text, surfaces it as a proposal, and lets the existing core teaching propose / review / accept pipeline carry the proposal to ratification. The reviewer's accept/reject is the load- bearing trust boundary, identical in shape to the one ADR-0084 content used.

The harvester is the writing curriculum's Layer 0.


Trust boundary

This spec touches a high-risk surface: external prose ingestion. The input is text the author did not write. The output is candidate content for a corpus that downstream surfaces draw from. Every step must enforce that no input prose token ends up on a user surface without passing through a human review gate.

Boundary Enforcement
Input prose Read-only from a manifest of approved sources (each source pinned by checksum). No web fetch at harvest time. No automatic source admission.
Candidate proposals Land in the existing teaching-proposal queue. NEVER auto-accepted. NEVER ratified without core teaching review --accept.
Tokens in proposals Restricted to lemmas already in mounted packs (lexicon residency check) + a small "novel-token" candidate flag that requires explicit operator promotion before the proposal is ratifiable.
Output corpus Existing teaching-corpus path. Harvester proposals are tagged with provenance harvester:<source_id>:<line_no> so any accepted chain can be traced back to the prose it came from.

No bypass of the review path is permitted. The harvester produces proposals, never content.


Architecture

Stage 0 — Source manifest

packs/writing_sources/<corpus_id>/manifest.json declares an approved-source corpus:

{
  "corpus_id": "scientific_method_canon_v1",
  "version": 1,
  "issued_at": "2026-05-21T00:00:00Z",
  "sources": [
    {
      "source_id": "popper_logic_of_scientific_discovery_1959_ch01",
      "title": "The Logic of Scientific Discovery, Ch.1",
      "author": "Karl Popper",
      "license": "fair-use:short-quotation",
      "path": "raw/popper_1959_ch01.txt",
      "checksum": "<sha256 of raw text bytes>"
    },
    ...
  ],
  "checksum": "<sha256 of this manifest minus checksum field>",
  "provenance": "writing-harvester:reviewed:2026-05-21"
}

Sources are added by human admission only. The harvester never auto-discovers a source. The manifest is checksum-pinned the same way every other CORE pack is.

Stage 1 — Sentence segmentation + clause extraction

writing_curriculum/harvester/segment.py:

  • Tokenize raw text into sentences (deterministic, rule-based — same discipline as existing pack compilers; no statistical tokenizer).
  • For each sentence, identify candidate clauses by punctuation + conjunction markers.
  • Output: a typed stream of (source_id, line_no, sentence_no, clause_no, raw_clause_text) records.

This stage produces zero CORE content. It only structures the input.

Stage 2 — Rhetorical-move classification

writing_curriculum/harvester/classify.py:

  • For each clause, apply a deterministic pattern matcher (rules, not learned) to classify the clause's likely rhetorical move:
    • claim: declarative assertion without explicit warrant marker.
    • evidence: clause introduced by "because", "as shown by", "the data indicate", etc.
    • warrant: clause introduced by "therefore", "hence", "this implies", etc.
    • concession: clause introduced by "although", "while", "granted", etc.
    • hedge: clause containing "may", "suggests", "appears", "possibly", etc.
    • definitional_move: clause matching "X is Y" pattern with X being a candidate technical term.
  • Output: typed records (source_id, ..., clause_text, move, confidence).

The classifier is deliberately conservative. Ambiguous clauses output move unknown and are skipped at the proposal stage. No classifier weight is trained — the pattern set is hand-maintained and reviewed like any other ratified discipline.

Stage 3 — Triple extraction

writing_curriculum/harvester/extract.py:

For each clause classified as a known move, attempt to extract a (subject, predicate, object) triple where:

  • subject and object resolve to lemmas in mounted packs (via chat.pack_resolver.resolve_lemma).
  • predicate is one of the existing relation predicates the cognition / relations corpora already use (requires, supports, grounds, precedes, entails, contrasts_with, evidences, causes, implies).
  • A new predicate is allowed ONLY if proposed as a separate candidate with explicit new_predicate: true flag — never silently introduced.

Extraction outputs candidate TeachingProposal records ready for the existing core teaching propose path. Each proposal carries:

{
  "proposal_id": "harvester:scientific_method_canon_v1:popper_1959_ch01:L42",
  "chain_id": "<auto-generated, prefixed with `harvested_`>",
  "subject": "evidence",
  "predicate": "supports",
  "object": "claim",
  "intent_tag": "cause",
  "source_clause": "<the original prose clause, verbatim>",
  "source_id": "popper_logic_of_scientific_discovery_1959_ch01",
  "source_line": 42,
  "rhetorical_move": "evidence",
  "extractor_confidence": "high|medium|low",
  "extracted_at": "<ISO timestamp>",
  "review_state": "pending"
}

The source_clause field is REQUIRED. Every proposal carries the exact prose it came from so a reviewer can verify the extraction.

Stage 4 — Review queue integration

The harvester writes proposals to the existing teaching-proposal pipeline (teaching/store.py). No new review mechanism. No new ratification gate. The proposal is exactly the same shape as a hand-authored one with two extra metadata fields (source_id, source_line) that the existing pipeline preserves but doesn't require.

This is the load-bearing design choice: the harvester adds a proposal producer, not a proposal consumer. Reviewers see harvested proposals in the same queue as hand-authored ones, judge them with the same criteria, accept/reject with the same commands.

Stage 5 — Provenance audit

writing_curriculum/harvester/audit.py:

A diagnostic tool that, given any chain in any teaching corpus, reports whether it was hand-authored or harvested, and if harvested, which source/line. Operators can run this to spot-check the provenance of any surface the system produces.


Determinism + replay

  • Sentence segmentation, classification, and extraction are all deterministic functions of (source_text, harvester_version).
  • Running the harvester twice on the same input manifest produces byte-identical proposals.
  • Harvester version is pinned in the proposal extracted_at metadata.
  • Re-running the harvester against an updated input manifest is additive — it generates new proposals for new sources, never retroactively modifies prior proposals.

This is the same determinism discipline the rest of CORE follows.


What the harvester is NOT

Not Why
A summarizer It extracts triples, not summaries. No prose generation, no paraphrase.
A learned model Pattern rules, hand-maintained. No statistical training.
An auto-ratifier Every proposal goes through human review. The reviewer is the trust boundary.
A source admitter Sources are admitted by core writing sources add <path> (human-initiated) only. No web crawler.
A revision engine Revision (proposing edits to ratified chains) is a separate spec — possibly the next one.
A style detector Source corpus selection encodes the style; the harvester doesn't classify "is this scientific?" — operators decide which corpora to harvest from.

Acceptance criteria for the spec → first implementation PR

When the first implementation PR for this spec opens, it must:

  1. Ship Stage 0 (source manifest schema + loader) and Stage 1 (segmentation) only. Not Stages 2-5.
  2. Include one tiny ratified source corpus (e.g., a public-domain short essay) as the fixture.
  3. Produce a deterministic dry-run report (core writing harvest --dry-run --source <id>) that shows the segmented sentences but does NOT write to the teaching-proposal queue.
  4. Pass a "no surface emission" test: nothing the harvester produces reaches a user surface in this first PR.

This sequencing — substrate, then dry-run, then propose pipeline, then accept-loop integration — mirrors ADR-0084 → 0085's substrate-first discipline.

Stages 2-5 land in follow-up PRs, each with the same gate (no surface emission until human review explicitly accepts the proposals).


Open questions for the implementation phase

These are deliberately left for the implementation PR to resolve, not pre-decided here:

  1. Segmentation library choice. Pure-Python rule-based (zero dependency, matches CORE's no-statistical-tokenizer discipline) vs nltk/spacy sentence segmenters (battle-tested but pull in model weights). Recommendation: pure-Python. Open for reconsideration only if the rule-based output is demonstrably worse on the fixture corpus.
  2. Source-license boundary. What counts as an admissible source? Public-domain only? Fair-use short quotation? Operator- licensed corpora? Worth an explicit policy doc before any non-public-domain source is admitted.
  3. Reviewer UX. Harvested proposals carry source-clause context that hand-authored ones don't. The review tool should display this context. Whether that's a CLI flag or a separate review surface is an implementation choice.
  4. Cross-corpus chain composition. Can a harvested chain whose subject is in en_core_cognition_v1 and whose object is in en_core_relations_v1 be ratified into a third corpus, or must each pack stay in its own corpus? Likely answer: yes, via the existing cross-pack-chain mechanism (ADR-0064), but worth exercising on a real example before committing.

Cross-References

  • ADR-0087 — the axis this harvester ultimately feeds.
  • ADR-0084 / ADR-0085 — substrate- before-consumer sequencing pattern this spec adopts.
  • ADR-0064 — cross- pack teaching corpora mechanism the harvester will reuse.
  • teaching/store.py — the proposal-queue integration point.
  • core teaching propose / review / supersede — the existing review pipeline the harvester producer feeds.