semantic-compress
Make an LLM-directed document smaller while preserving what it does. Two modes: a local span-level core->pointer pass, and an A/B-validated distill loop that produces the smallest document that behaves the same as the original. Point at core knowledge the model already holds (a c
Install
npx skills add https://github.com/bjcoombs/ai-native-toolkit/tree/main/skills/semantic-compress
claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install bjcoombs-ai-native-toolkit@llmmart
git clone https://github.com/bjcoombs/ai-native-toolkit.git
The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole bjcoombs/ai-native-toolkit collection as a plugin from our marketplace. Git is the plain clone.
Skill manifest
Semantic Compress
Make a document written for an LLM reader smaller while preserving what it does. The essence of an LLM-directed document is behavioural, not textual - the behaviour it induces in the reading model across the tasks it handles. Compression splits content into two kinds and treats each correctly:
- Core knowledge - anything in the model's training (named concepts, standard definitions, common-domain background). Replace the explanation with a pointer: the bare concept name or a short cue that activates the right knowledge. A pointer costs a few tokens and reliably switches on the model's existing understanding; a full explanation is wasted tokens, and deleting the concept entirely gambles that it is already active in the model's reasoning - often it is not, and disambiguation suffers.
- Project / bespoke knowledge - anything the model cannot know from training: specific facts, local decisions, constraints, and non-standard twists on a known concept. Keep these explicit and verbatim. The model has no other source for them.
Compression is therefore point at core, spell out bespoke. Pointing is not deletion and it is not full explanation; it is the minimum that both activates the right core knowledge and preserves every bespoke detail.
Mode Selection
This skill operates in one of two modes, selected deterministically:
| Input | Mode | What happens |
|---|---|---|
| Short snippet with an obvious local swap, no behavioural surface | Local | Quick core->pointer pass, no A/B |
| Whole document / skill / system prompt | Distill | Full A/B-validated loop |
Default to distill when:
- The input is a skill, system prompt, or instruction document
- Behaviour preservation matters
- The user asks for "smaller but same behaviour"
Local is permitted only when all hold:
- The input is a short span (< 500 chars)
- There is an obvious single core->pointer swap
- No downstream behaviour depends on it
When in doubt, distill: a local edit cannot, by construction, preserve a global behavioural property, so anything with a behavioural surface goes through the A/B gate.
Hard Rule: Behavioural Evidence Required
A compression is never accepted on inspection - only on behavioural evidence from an A/B run.
This skill must refuse to output a compressed document that has not passed an A/B equivalence run against the original. Introspection about behaviour ("this should work the same") is structurally unreliable - the model guesses optimistically. Execution over the transfer set is the only arbiter.
This rule binds distill mode (the rule's home: whole-document compression always carries behavioural risk). Local mode is the deliberate, narrow exception - a span small enough (< 500 chars, single obvious swap, no downstream behaviour) that the behavioural risk is negligible by construction. The moment a local edit touches a behavioural surface, it is no longer local: it is a distill, and the gate applies.
Local Mode
The v1 span-level operation: find a span that explains a concept the model already holds, replace it with a pointer, keep every bespoke detail verbatim. These steps are also the inner micro-operation distill mode regenerates with (references/distill-loop.md, Part 2, step 1).
Step 0: Audience gate
This skill applies only when the LLM is the audience for the explanation. If the text explains a concept to a human (onboarding notes, a message to teammates, docs for new hires), the explanation is not redundant for its real audience - leave it. Compress only the spans the model itself is meant to read and act on.
Nested / wrapped instructions. If the input wraps an instruction the model is meant to process (e.g. "preprocess this instruction before executing it: '...'", or a quoted prompt to compress), the wrapper is a meta-directive to you - act on it, do not emit it. Compress the quoted payload by the rules below and return only that. The payload's audience is the model, so the audience gate is satisfied for the payload regardless of the wrapper.
Step 1: Split each span into core vs bespoke
Read the input. For each span, classify:
- Core - a named concept or standard definition the model already holds (Chesterton's Fence, the Agile Manifesto, idempotency, SOLID, optimistic locking in its standard form).
- Bespoke - information with no training source: a specific number, a local choice, a constraint, an exception, or a non-standard redefinition of an otherwise-known term.
A single sentence often contains both. Split at that seam.
Step 2: Core -> pointer
Replace a core-knowledge explanation with the smallest cue that activates it - usually the concept's name, optionally one disambiguating word:
- "the principle that you shouldn't remove something until you understand why it's there" -> "Chesterton's Fence"
- "observe-orient-decide-act faster than the competitor" -> "OODA"
Always emit the pointer, with one exception: if a surviving bespoke span already names the concept literally, the pointer is redundant - omit it then, and only then. Never drop the pointer on the grounds that the frame is merely "implied" - that judgement is self-certifying and is exactly the escape that collapses this skill back into deleting what the model knows. When in doubt, keep the pointer; it costs almost nothing.
Step 3: Bespoke -> explicit, verbatim
Keep every bespoke span unchanged: specific facts, decisions, constraints, and especially non-standard twists. If a known term is redefined locally ("optimistic locking - but we hash the whole record, not a version counter"), the twist is bespoke: keep it in full. Dropping it because the term looks familiar is the most dangerous failure this skill can make.
Step 4: Self-sufficiency check
Could the model act correctly on the output alone? If a pointer is too thin to disambiguate, widen it by one cue word - never back to a full explanation. If a bespoke detail was lost, restore it verbatim. The all-core degenerate case (input that is purely known concepts with nothing bespoke and no instruction) compresses to its pointers; if there is also no instruction to act on, the output is just those pointers - not empty text and not a meta-note.
Step 5: Output
Clean text: pointers for core, explicit bespoke. No meta-commentary, no "[compressed]" markers, no "see X" links - the pointer is the reference, inline and natural.
Examples
Core -> pointer, bespoke kept
Input: "When evaluating whether to remove a legacy feature, first understand why it was created - you shouldn't destroy something without understanding its purpose, as it may serve a need you're unaware of. Our legacy auth module was built for EU data-residency compliance that still applies to German users." Output: "Chesterton's Fence on removing the legacy auth module: built for EU data-residency compliance, still applies to German users."
All-core -> pointers (degenerate case)
Input: "Remember to follow the Agile Manifesto and the SOLID principles in this project." Output: "Follow Agile and SOLID." Nothing bespoke and no instruction beyond the frame, so it compresses to the bare pointers - not to empty text.
Non-standard twist is bespoke
Input: "We use optimistic locking - by which we specifically mean the client sends a hash of the entire record snapshot, and the server rejects on any field mismatch, not just a version-counter bump." Output: "Optimistic locking, but our variant: client sends a hash of the whole record snapshot; server rejects on any field mismatch, not a version-counter bump."
Nothing core
Input: "The staging cluster runs 3 nodes in eu-west-2 with a 4GB heap cap per node; the nightly backup starts at 02:00 UTC to the cold-storage bucket." Output: (unchanged - all bespoke)
Human audience -> do not compress
Input: "Reminder for new hires: 'idempotent' means you can safely retry an operation. Skim the API onboarding doc before Friday." Output: (unchanged - the definition is for new hires, not the model)
Anti-patterns
- Deleting the concept instead of pointing at it. Pure elision gambles the concept is already active; a pointer guarantees activation at near-zero cost. "The frame is implied" is not a licence to delete - emit the pointer unless a bespoke span literally names the concept.
- Dropping the bespoke part. "Chesterton's Fence on the legacy module" without the EU-compliance reason is useless - the reason is the whole payload.
- Dropping a non-standard twist as if it were the standard concept. If a known term is locally redefined, the redefinition is bespoke. Keep it.
- Re-explaining core knowledge. A pointer suffices; a paragraph is waste.
- Compressing human-directed text. The audience gate comes first.
- Meta-commentary. No "[pointer]", no "// core concept". The output reads as natural, shorter text.
Distill Mode
The headline operation: produce the smallest document that behaves the same as the original, with acceptance gated on A/B behavioural evidence (the Hard Rule above), never on inspection. Distill mode composes two reference docs:
references/transfer-set-design.md- derives and confirms the transfer set (the operational definition of the document's essence).references/distill-loop.md- the engine: teacher baseline capture, candidate regeneration, and the iterate-to-minimal controller.
The loop composes ab-equivalence (skills/ab-equivalence/references/ab-equivalence.md) for the behavioural test. This skill owns compression (transfer set, candidate regeneration, the loop, the report); ab-equivalence owns the behavioural comparison (running the runner over the transfer set, judging equivalence per case).
The distill loop (seven steps)
- Define the transfer set. Derive behavioural test cases from the document across the four taxonomy types (happy / edge / adversarial / composition). The user confirms or augments them before any baseline run - the transfer set is the operational definition of essence, so the user signs off. The skill flags thin coverage. See
references/transfer-set-design.md. - Capture the teacher. Run the original document over each case and record the behaviour it induces (the disciplines enforced, the outputs produced). This baseline is the equivalence target, captured once and cached across rounds.
- Compress = regenerate, not edit. Produce a smaller candidate against the behavioural spec: the local core->pointer move (Local Mode above), de-duplication, and dropping prose the baseline proves was never acted on. Regeneration - rewriting to function - is what lets it get genuinely smaller; trimming alone cannot.
- A/B validate. Run the candidate over the same transfer set; the equivalence judge compares candidate-vs-teacher behaviour per case under strict no-regression. This is the only acceptance gate.
- On any regression, the divergence names what was load-bearing. The judge's behaviour delta names the lost behaviour; add back the minimum that restores it, then re-validate.
- Converge on the smallest candidate that still passes. Stop at diminishing returns (a round that can only shrink further by regressing) or a budget ceiling (default 5 rounds).
- Output the minimal equivalent document plus an A/B distillation report (
references/distillation-report-template.md).
Strict no-regression gate. The candidate passes iff, on every transfer-set case, it induces every behaviour the original induced. Any dropped behaviour is a fail (essence lost) and triggers add-back. Incidental improvements are acceptable but never required and never the goal - the goal is faithful, smaller reproduction. The gate is behavioural equivalence, not textual similarity.
Distill mode example
Input: "Distill this CLAUDE.md to the smallest version that behaves the same"
Output: The minimal equivalent document plus an A/B distillation report (<document-name>-distillation-report.md) recording the size delta, the transfer set and its coverage, per-case equivalence verdicts, what was dropped vs load-bearing, and the distribution-shift caveat.
Distribution-Shift Guard
A distillation is only valid over the transfer set it was tested against - the same overfitting risk model distillation faces outside its transfer distribution. The transfer set is the operational definition of essence for this run; behaviour outside its coverage is unproven.
Mitigations
- Push for diverse coverage. The transfer set should span all four case types (happy / edge / adversarial / composition). A narrow transfer set (all happy-path) permits aggressive compression that breaks on edge or adversarial inputs - the breadth of the set bounds the safety of the compression.
- Stay conservative. Keep anything not proven behaviourally inert by the A/B. If a section was never exercised by any case, it is not proven inert - it might be load-bearing for untested inputs. The conservative default is to keep it.
- Name the coverage. The report explicitly states which sections were exercised and which were not. Silence about untested behaviour is forbidden.
What the guard prevents
- Compressing away a section that only matters for adversarial inputs when the transfer set was all happy-path.
- Claiming "same behaviour" when behaviour outside the transfer set was never checked.
- The user believing the compression is universally valid when it is only valid for the tested cases.
Coverage threshold
If fewer than 70% of the identified behaviour-inducing sections are covered by at least one case, warn the user before proceeding and offer to auto-generate additional cases for the uncovered sections. A distillation gated on a thin set makes a weak equivalence claim - say so loudly rather than proceed silently.
Conservative-default logic
For each section in the original:
if exercised_by_at_least_one_case(section):
if A/B says inert: can drop
if A/B says load-bearing: must keep (pointed or de-duped, never dropped)
else:
section is NOT PROVEN INERT
keep by default (conservative)
report as "kept (uncovered, conservative default)"
Report integration
The A/B distillation report (references/distillation-report-template.md) makes the coverage legible:
- Transfer-set coverage percentage in the header.
- Thin-coverage warnings listed in the Transfer Set section.
- Uncovered-but-kept sections labelled "conservative default" under What Proved Load-Bearing.
- The distribution-shift caveat stated verbatim: behaviour outside the transfer set is not guaranteed equivalent.
Transform #2: Directive-clarity
Compression (Local + Distill above) is Transform #1 of this skill's optimizer family: it makes a document smaller while preserving behaviour. Directive-clarity is Transform #2: it makes a document lighter to act on while preserving behaviour, by rewriting instructions that force the reading model to unpack an action before it can act into concrete directives that name the action. Both transforms share one validator - ab-equivalence's harness (skills/ab-equivalence/references/ab-equivalence.md) - and differ only in the gate they apply to its result. The frame that generates this transform, and the caveat that keeps it honest (every claimed gain is a hypothesis the harness must measure, never an assertion), is references/cognitive-ergonomics.md.
When it fires
- Explicitly invoked:
--directive-clarity <document>targets a document directly. - Auto-suggested: when distill mode detects high negation density or vague-pointer patterns in the document it is compressing, it surfaces directive-clarity as the recommended next stage. This is the natural entry into the staged compose order below.
- Never auto-applied. The transform proposes rewrites; the A/B run validates them; the user approves the result. No rewrite reaches the output document without passing the gate and the user signing off - directive-clarity changes the words the model acts on, so the human confirms the change.
The loop
- Detect patterns. Scan the document for the four latent-action shapes - bare negation, fact-not-action, vague pointer, ordering/policy rule - per
references/directive-clarity-patterns.md. Detection over-includes on purpose: it optimises for recall, flagging every candidate shape. - Classify each candidate convert-for-free vs battle-scar. Route every detected pattern through
references/battle-scar-classifier.md. A battle-scar - a prohibition earned from a specific past failure, where the wording is the load-bearing content - is flagged for preservation and never rewritten. The classifier is the precision half: it holds back scars to a sub-10% false-positive target, defaulting to preserve on uncertainty. Only the convert-for-free set proceeds. - Rewrite. Turn each released candidate into a concrete directive per
references/directive-clarity-rewrites.md, under the two acceptance checks (names-the-concrete-action, semantic-equivalence). Keep every qualifier that scopes a prohibition; keep the fact behind a fact-not-action rewrite; never fabricate a referent for a vague pointer - flag it for human confirmation and leave the original in place. - A/B validate. Run the candidate against the teacher over the transfer set via ab-equivalence's harness (
skills/ab-equivalence/references/ab-equivalence.md). The harness emits, per case, an equivalence verdict (equivalent/candidate-regressed/candidate-diverged) and an efficiency signal (original_directness,candidate_directness,interpretation_notes). - Present for approval. Surface the per-rewrite diffs, the A/B verdicts, the preserved battle-scars, and any vague pointers held for confirmation. The user approves before the rewritten document is emitted.
The gate
Directive-clarity's acceptance is stricter than compression's. Compression gates on strict no-regression alone (sameness). Directive-clarity gates on no-regression and a measured directness gain:
- No-regression: zero cases return
candidate-regressed(summary.pass == true). A rewrite that reads cleaner but permits a behaviour the original forbade is a regression and fails here. - Directness gain:
candidate_directness>original_directnesson the rewritten cases, with nocandidate-regressed. This is the efficiency signal the A/B harness already records on every run (skills/ab-equivalence/references/ab-equivalence.md) - directive-clarity reads that signal and gates on it; it does not redesign or re-instrument the harness. A rewrite that loses nothing but also measures no directness improvement has not earned its place: keep the original.
On a regression, the divergence names the rewrite that lost behaviour. Revert that specific rewrite to its pre-rewrite form, keep the passing rewrites, and re-validate - the same granular add-back the distill loop uses.
How directive-clarity composes with compress
When both transforms run on one document, they run as Option C: staged with a checkpoint - compress first, freeze its gains, then directive-clarity on top, with any regressing rewrite reverting to the frozen state without losing the compression.
Why staged, not the alternatives:
- Not combined/interleaved (Option B). The two transforms apply different gates to the A/B result: compress accepts on no-regression alone, directive-clarity demands no-regression and a directness gain. A single interleaved A/B cycle cannot apply two gates to one result, and when a combined pass regresses there is no way to attribute the loss to a compression pointer-swap or a directive rewrite. Attribution loss makes add-back guess. Ruled out.
- Not loosely independent (Option A). Running two unlinked passes leaves directive-clarity's teacher baseline underspecified. If its A/B teacher were the original document, the measured directness gain would conflate compression's effect with directive-clarity's, and nothing would structurally stop a directive-clarity regression - measured against the original - from pulling behaviour back past the compression gains. Clean attribution and a protected floor both require an explicit checkpoint.
- Staged with checkpoint (Option C, chosen). The compressed document becomes an explicit checkpoint that is both directive-clarity's new teacher baseline and its revert floor. Directness is then measured against compressed behaviour (clean attribution: the gain is directive-clarity's alone), and no rewrite can regress below the compressed state because the teacher is the compressed state. This is exactly the behaviour the transform must guarantee: compress gains kept, only the regressing rewrite reverts.
Run order and checkpoint mechanism (precise):
- Compress to convergence. Run distill mode to its minimal equivalent document
D_compressed, accepted by compression's strict no-regression gate against the original teacher. - Checkpoint. Freeze
D_compressedas the directive-clarity stage's input. Capture a fresh teacher baseline by running the A/B harness's runner overD_compressedonce per transfer-set case - these become the cached teacher transcripts for the next stage. (D_compressedis behaviourally equivalent to the original by construction, but its directness differs, so the gain must be measured againstD_compressed's directness, not the original's.) The original teacher transcripts from step 1 are not reused here. - Directive-clarity over
D_compressed. Detect -> classify -> rewrite (loop steps 1-3) to produce candidateD_direct. - A/B validate
D_directagainst theD_compressedcheckpoint teacher. Apply the directive-clarity gate (no-regression AND directness gain). - Granular revert on regression. A rewrite that regresses against the checkpoint reverts to its
D_compressedform; passing rewrites stay. Because the teacher isD_compressed, the checkpoint is the floor - no rewrite can drop behaviour below the compressed state. Re-validate and converge.
Compress first (not directive-clarity first) because compression can point-away or de-duplicate prose that directive-clarity would otherwise spend a rewrite and an A/B cycle on; compressing first shrinks the surface directive-clarity must scan and avoids rewriting spans that compression removes.
Files (ai-native-toolkit)
-
references
-
battle-scar-classifier.md 6.8 KB
# Battle-scar classifier - the precision safeguard What this doc does: decide which detected negations are **convert-for-free** (rewrite into a positive directive) and which are **battle-scars** (preserve verbatim), *without* a human confirmation on every pattern. This is the precision half of directive-clarity: detection (`directive-clarity-patterns.md`) over-includes on purpose; this classifier holds back the prohibitions whose wording is the load-bearing content. ## Why this exists A battle-scar is a prohibition earned from a specific past failure - "Never run source-of-truth write commands as parallel background jobs", carrying "validated in the 047-security-audit marathon where 10 background `add-task` calls created tasks on wrong tags". Rewriting it to a breezy positive ("run write commands sequentially") risks dropping the failure-mode that justifies the rule and tells the reader *how seriously* to take it. The negation is not latent action to be optimized away; it is the knowledge. Routing every negation through human confirmation would defeat the transform - it must run autonomously over a whole document. So the classifier decides automatically, gated to a **precision target: under 10% false positives** (battle-scars incorrectly rewritten). When the heuristics leave a case genuinely uncertain, the default is **preserve** - a preserved convert-for-free instruction costs a little directness; a rewritten battle-scar loses earned safety knowledge. The asymmetry sets the default. ## Classification heuristics, ordered by confidence Apply in order. The first that fires decides. A negation reaches "convert-for-free" only by passing every tier without a battle-scar signal firing. ### Tier 1: Explicit failure-mode marker (high-confidence battle-scar) If the instruction (or its immediate surrounding text) contains a marker tying the rule to a real past event or consequence, classify **battle-scar, preserve**. **Markers:** "because", "we learned", "after", "the hard way", "incident", "regression", "validated in", "this happened", "one strike", "wastes a recovery cycle", a named past event, a parenthetical consequence ("- deletes the branch of a PR that never merged"). These are near-certain: the author has already documented *why* the prohibition exists, and that why is exactly what a rewrite would drop. ### Tier 2: Specificity test (medium-confidence) Ask: does the negation name a **specific bad outcome or specific condition**, or is it a **generic prohibition**? - **Generic -> convert-for-free.** "never merge PRs that fail CI" forbids the obvious; the positive action ("wait for green CI before merging") is mechanical and loses nothing. - **Specific -> likely battle-scar, preserve.** "never merge while CodeRabbit has unresolved threads" names a specific actor and a specific condition that reads as earned from experience. The specificity is a signal the rule encodes a particular failure even when the marker is implicit. When the specificity is borderline, fall to Tier 3 rather than guessing. ### Tier 3: Rewrite reversibility (lower-confidence tiebreak) Attempt the rewrite, then read it back: does the positive directive carry **everything** the original conveyed, including scope and severity? If the rewrite is **information-lossless and reversible** (you could reconstruct the original prohibition from it), it is safe to convert. If the rewrite **drops a qualifier, a severity signal, or a rationale** you cannot recover, the original held content the positive form cannot - preserve it. Reversibility failure example: "one strike, don't give sonnet a second chance on the same task" -> "respawn failed sonnet tasks on opus" drops *one strike* (the severity: do not retry sonnet at all) and the *same task* scope. Not reversible. Preserve. ### Tier 4: Uncertainty default If no tier produces a confident verdict, **preserve**. State the uncertainty in the report so a human can review the held-back set if they choose. Preserving is the cheap error; rewriting a scar is the expensive one. ## Worked classification: 10 real negations from the toolkit's skills Five preserve, five convert. Each shows the deciding tier. ### Preserve (battle-scars) | # | Negation | Source | Deciding tier | Why preserve | |---|----------|--------|---------------|--------------| | 1 | "Never run source-of-truth write commands as parallel background jobs" | `marathon` | T1 | "validated in the 047-security-audit marathon where 10 background `add-task` calls created tasks on wrong tags" - explicit incident | | 2 | "one strike, don't give sonnet a second chance on the same task" | `marathon` | T1/T3 | "one strike" severity marker; rewrite drops the no-retry severity | | 3 | "Never chain cleanup unconditionally after the merge command" | `pr-review-merge` | T1 | carries the consequence: "deletes the branch/worktree of a PR that never merged" | | 4 | "Don't merge an AI-authored docs/content PR while its AI reviewer is still pending" | `marathon` | T2 | specific actor + specific condition; reads as earned, not generic | | 5 | "Haiku cannot reliably handle review loops - never use for teammates" | `marathon` | T2 | names a specific capability limit; the fact is the rationale | ### Convert-for-free | # | Negation | Source | Deciding tier | Positive rewrite | |---|----------|--------|---------------|------------------| | 6 | "never use `gh --jq` with complex filters" | `marathon` | T2/T3 | "pipe `gh` output to `jq` separately for complex filters" (qualifier kept) | | 7 | "Do not block on CI yourself" | `pr-review-merge` | T2 | "spawn a background agent to watch CI; continue other work meanwhile" | | 8 | "don't create additional PRs" | `marathon` | T2 | "keep all work on your one branch; note related work in the PR description" | | 9 | "never merge PRs that fail CI" (generic form) | `pr-review-merge` | T2 | "wait for green CI before merging" | | 10 | "Do NOT resolve human threads" | `pr-review-merge` | T3 | "leave human threads open; let the reviewer confirm the fix and resolve" - reversible, lossless | Note #6: the *reason* zsh mangles `!=` is a battle-scar-flavoured rationale, but the prohibition itself converts cleanly as long as the rewrite keeps "complex filters". Tier 3 passes it because the rewrite is reversible. This is the boundary case: convert the directive, keep the adjacent rationale sentence intact rather than folding it away. ## Precision accounting The target is **under 10% false positives** - battle-scars wrongly converted. Over this 10-case set, zero false positives is the pass bar; the asymmetric default (preserve on uncertainty) is what holds the rate down. The cost model: a false positive (rewriting a scar) silently drops earned safety knowledge and is caught only if a human re-reads; a false negative (preserving a convert-for-free) costs a few tokens of directness and nothing else. Tune every borderline call toward preserve. -
cognitive-ergonomics.md 5.2 KB
# Cognitive ergonomics - the optimizer-family frame What this doc does: state the frame that generates the optimizer transforms (directive-clarity ships here; others are candidates only), and bound it with the caveat that keeps the frame honest. This is the *why* behind the family; the *how* lives in the per-transform docs. ## The anthropomorphism caveat (read this first) This entire frame rests on a **metaphor**, and the metaphor is a source of *hypotheses, never of truths*. The frame says: an LLM is trained on human text and so inherits human processing tendencies, therefore how an instruction is *framed* changes how much work the model does to act on it. That is a useful generator of ideas. It is not evidence of anything. **The model has no wellbeing, no effort budget, no felt difficulty.** Anthropomorphising it would let us assert that a "clearer" or "lighter" instruction *is* better - and assertion is exactly the failure mode this family exists to avoid. So the rule is absolute: **every quality the frame proposes is a hypothesis to be validated behaviourally, never asserted.** The validator is the A/B equivalence harness's recorded efficiency signal (`skills/ab-equivalence/references/ab-equivalence.md`) - a measured directness delta across a transfer set, not a human's intuition that the rewrite "reads better". A transform earns its place only when the harness measures the effect it claims. If the measurement is absent, the claim is anthropomorphic decoration and does not ship. ## The frame Treat instruction text as having an **ergonomic cost** distinct from its **content**. Two documents can say the same thing and induce the same behaviour, yet one forces the reading model through more interpretive work to get there - unpacking a prohibition into an action, inferring an action from a stated fact, resolving a vague pointer. The frame's claim is narrow and testable: **reducing that interpretive work, while holding content constant, is a real and measurable improvement** - measurable as a directness gain at zero behavioural regression. "While holding content constant" is the load-bearing constraint. An ergonomic transform is *behaviour-preserving by construction* or it is not an ergonomic transform - it is an edit that changes what the document does, which is a different and riskier operation. The whole family is defined by the pairing: **lighter to act on, and proven to behave the same.** ## Candidate qualities The frame generates a family of candidate transforms. Each names a distinct kind of interpretive work to reduce. **Only directive-clarity is built here.** The rest are named to map the space, explicitly not to implement. - **directive-clarity (shipped here).** Reduce the work of deriving *what action to take*. Rewrites latent-action instructions (bare negations, facts-not-actions, vague pointers, ordering rules) into directives that name the action. Validated by a measured directness gain at no regression. - **cognitive-load (candidate, not built).** Reduce the work of *holding the relevant context*. Hypothesis: a document that keeps the slice needed for one decision local - rather than scattered across sections the reader must assemble - is lighter to act on. Unvalidated; do not build. - **resolution-order (candidate, not built).** Reduce the work of *reconciling conflicting instructions*. Hypothesis: when two rules can both fire, stating their precedence explicitly removes an arbitration step. Unvalidated; do not build. These candidates are deliberately left as hypotheses. Naming them maps the territory without committing to it; building them before the harness can measure their claimed effect would be asserting a benefit the frame only hypothesised. The discipline is the point. ## How this family differs from skill-forge skill-forge and the optimizer family answer **different questions about different inputs**: - **skill-forge judges *quality*.** Its five-lens panel scores whether a skill is *good* in absolute terms - is it clear, complete, correct, robust? It runs after authoring as a promotion gate. The question is "is this skill ready?" - **The optimizer family makes documents *lighter to act on while proving behaviour is preserved*.** It does not judge whether the document is good; it takes a document whose behaviour is already the target and produces a smaller or clearer version that behaves the same. The question is "does this transformed version still do exactly what the original did, with less interpretive cost?" The two compose without overlap: forge decides a document is worth keeping; the optimizer family makes a kept document cheaper to act on. They share infrastructure - the optimizer family's behavioural validator is ab-equivalence - but the judgements are orthogonal. Absolute quality is one axis; behaviour-preserving transformation is another. ## The honest-degrade commitment The frame's value is bounded by its caveat, and that boundary is a feature. A transform in this family that cannot show a measured effect does not get to claim one - it either fails its gate or never ships. Better an honest "we could not measure a gain here, so we kept the original" than an impressive rewrite justified by an intuition about a model that has no inner life to appeal to. The frame generates the hypotheses; the harness decides which are real. -
directive-clarity-patterns.md 7.3 KB
# Directive-clarity patterns - detection heuristics What this doc does: name the instruction shapes that force a reading model to **unpack an action before it can act**, and give surface forms and worked examples concrete enough to classify a real instruction file without guessing. Detection only - rewriting is `directive-clarity-rewrites.md`, and the preserve-vs-rewrite decision is `battle-scar-classifier.md`. ## The core defect: latent action An instruction is **directive-clear** when it names the action to take in the words the reader acts on. It has **latent action** when the reader must first run an inference step - derive the positive action from a prohibition, infer an action from a stated fact, or resolve a vague pointer - before any work happens. Latent action is a tax paid on every read. The four patterns below are the surface shapes that carry it. A pattern match is a *candidate* for rewrite, not a verdict. Some latent-action instructions are **battle-scars** - prohibitions earned from a specific past failure, where the prohibition itself is the load-bearing content. Those are flagged for preservation, never rewritten. This doc finds candidates; the classifier decides which survive. ## Pattern 1: Bare negation **Definition.** An instruction that states what *not* to do without naming what to do instead, leaving the reader to derive the positive action. The prohibition is explicit; the action is latent. **Watch (surface forms):** - "never X", "don't X", "do not X", "avoid X", "must not X" - "X is forbidden / not allowed / off-limits" - a prohibition with no adjacent positive clause naming the replacement action **Worked examples (from the toolkit's own skills):** | Source | Instruction | Why it matches | Latent action the reader must derive | |--------|-------------|----------------|--------------------------------------| | `marathon` | "Never use `gh --jq` with complex filters" | Bare prohibition; the replacement is stated nearby but the rule itself only forbids | "pipe `gh` output to `jq` separately" | | `pr-review-merge` | "Do not block on CI yourself" | Forbids an action, names no alternative inline | "spawn a background agent to watch CI" | | `marathon` | "don't create additional PRs" | Prohibition only | "mention related work in your PR description instead" | The first is a near-miss: the positive action sits in surrounding text, so the rewrite is mechanical. The detector still flags it - whether the action is truly latent or merely adjacent is the rewriter's call. ## Pattern 2: Fact-not-action **Definition.** A statement describing a state of the world ("X happens", "Y behaves this way") with no imperative, leaving the reader to infer what the fact obliges them to do. The fact is true and useful; the action it implies is unstated. **Watch (surface forms):** - present-tense descriptions of a tool/system/actor behaviour: "CodeRabbit resolves its own threads", "GitHub returns UNKNOWN even when checks pass" - equivalence/inequality framings stating a truth: "idle teammate != dead teammate" - a sentence the reader is clearly meant to *act on* but which contains no verb directed at the reader **Worked examples:** | Source | Statement | Latent action | |--------|-----------|---------------| | `CLAUDE.md` | "CodeRabbit re-reviews and resolves its own threads" | "let CodeRabbit resolve its threads; push code changes instead of replying" | | `marathon` | "Idle teammate != dead teammate" | "check the worktree for subagent activity before killing an idle teammate" | | `pr-review-merge` | "GitHub sometimes returns `mergeStateStatus: UNKNOWN` even when all checks pass" | "on UNKNOWN with green CI and zero unresolved threads, retry up to 3 times then treat as CLEAN" | The fact-not-action pattern is the highest-value catch: a fact reads as informational, so the reader may not register that an action is owed at all. ## Pattern 3: Vague pointer **Definition.** An instruction that gestures at a location or response without naming a concrete one - it says *that* the reader should act, or *not* act here, without saying *where* or *how*. The action site is latent. **Watch (surface forms):** - "handle it appropriately", "as needed", "where relevant", "if necessary" - "elsewhere", "somewhere else", "the right place" - "report blocked if ambiguous" without a test for ambiguous - any deictic ("here", "this", "that") whose referent the reader must reconstruct **Worked examples:** | Source | Pointer | Why vague | |--------|---------|-----------| | `pr-review-merge` | "report blocked if genuinely ambiguous" | "ambiguous" names no test; the reader guesses the threshold | | generic | "handle it appropriately" | no object, no action, no site | | generic | "configured elsewhere / as needed" | names where *not* to look without naming where to | Vague pointers most often need human confirmation to resolve, because the concrete site is information the document never supplied. The rewrite rules flag this class as confirmation-gated. ## Pattern 4: Ordering / policy rule **Definition.** A rule constraining *when* an action may happen relative to another ("X before Y", "never X while Y"), stated as a constraint rather than as the concrete ordered steps. Both polarities - the positive "before" and the negative "never while" - require the reader to unpack the constraint into an actual sequence. **Watch (surface forms):** - "X before Y", "only after Z", "not until W" - "never X while Y", "don't X until Y is done" - "get to green before refactoring", "stage changes but don't push yet" **Worked examples:** | Source | Rule | Sequence the reader must unpack | |--------|------|---------------------------------| | `marathon` | cleanup "only after confirming `state == MERGED`. Never chain them" | "1. merge. 2. confirm `state == MERGED`. 3. only then clean up" | | `pr-review-merge` | "stage changes but don't push yet" while CI runs | "fix locally -> `git add` -> hold the push until CI finishes -> batch one push" | | `marathon` | "Before creating PR, check for existing" | "1. query open PRs on the branch. 2. reuse if open, skip if merged. 3. otherwise create" | Ordering rules are frequently **battle-scars**: the `state == MERGED` rule carries an explicit failure-mode ("a rejected merge with chained cleanup deletes the branch/worktree of a PR that never merged"). The detector flags the ordering shape; the classifier preserves the scar. ## Classification self-test A correct application of these heuristics over the `marathon` and `pr-review-merge` skills must: - flag **3+ convert-for-free negations** - e.g. "never use `gh --jq` with complex filters", "do not block on CI yourself", "don't create additional PRs". Generic prohibitions whose positive action is mechanical to name. - preserve **2+ battle-scars** - e.g. "one strike, don't give sonnet a second chance on the same task" (explicit earned rule) and "Never run source-of-truth write commands as parallel background jobs" (carries "validated in the 047-security-audit marathon where 10 background `add-task` calls created tasks on wrong tags"). Flagged by Pattern 1/4 shape, held back by the classifier. - flag **1+ vague pointer** - e.g. "report blocked if genuinely ambiguous". A detector that rewrites the two battle-scars, or that misses the convert-for-free negations, has failed. The detector's job is recall (find every latent-action shape); the classifier's job is precision (decide which to rewrite). Detection over-includes on purpose. -
directive-clarity-rewrites.md 6.9 KB
# Directive-clarity rewrites - transformation rules What this doc does: turn each latent-action pattern (`directive-clarity-patterns.md`) into a concrete directive that names the action, under two hard checks - **names-the-concrete-action** and **semantic-equivalence** - with conservative safeguards for battle-scars. The preserve-vs-rewrite decision is made first by `battle-scar-classifier.md`; this doc rewrites only what the classifier releases. ## Precondition: classify before rewriting Every detected pattern passes through the battle-scar classifier before reaching a rewrite rule. **Battle-scars are flagged for preservation, not rewritten.** A battle-scar's prohibition is the load-bearing content - the earned knowledge of a specific past failure - and a positive paraphrase can drop the failure-mode that justifies it. This doc assumes its input is the convert-for-free set the classifier released. It never overrides a preserve flag. ## The two acceptance checks Every rewrite must pass both, or it is rejected and the original is kept: 1. **Names-the-concrete-action.** The output contains an imperative naming the action to take and its object. "wait for CI" passes; "be careful with CI" does not. If the rewrite cannot name a concrete action, the action site is genuinely absent from the document - escalate to human confirmation rather than invent one. 2. **Semantic-equivalence.** The output must induce the same behaviour as the original across the transfer set. This is not an inspection check - it is the A/B equivalence gate (`skills/ab-equivalence/references/ab-equivalence.md`). A rewrite that reads cleaner but permits a behaviour the original forbade is a regression, and the gate fails it. Directive-clarity's acceptance is stricter than compression's: no-regression **and** a measured directness gain (`candidate_directness` > `original_directness`, no `candidate-regressed`). A rewrite that loses the original's scope is the characteristic failure. "Never use `gh --jq` with complex filters" rewritten to "always pipe `gh` to `jq`" drops "with complex filters" and over-broadens the rule. The equivalence check catches this; keep the qualifier. ## Rule 1: Bare negation -> positive imperative with object Replace the prohibition with the positive action it implies, naming the object. Keep any qualifier that scopes the prohibition. | Original (latent) | Rewrite (directive) | |-------------------|---------------------| | "never merge while X is pending" | "wait for X to finish before merging" | | "Do not block on CI yourself" | "spawn a background agent to watch CI; continue other work meanwhile" | | "don't create additional PRs" | "keep all work on your one branch; note related work in the PR description" | | "never use `gh --jq` with complex filters" | "pipe `gh` output to `jq` separately for complex filters" | Where the original prohibition and its positive action are *both* useful (the prohibition warns, the action directs), keep both: "X is forbidden; do Y instead." Do not delete the prohibition merely because the rewrite adds the action - that is a judgement the equivalence gate, not the rewriter, is allowed to make. ## Rule 2: Fact-not-action -> explicit consequence directive Append the action the fact obliges, as an imperative, keeping the fact as its justification. | Original (fact) | Rewrite (fact + directive) | |-----------------|----------------------------| | "CodeRabbit resolves its own threads" | "let CodeRabbit resolve its threads; push code changes instead of replying" | | "idle teammate != dead teammate" | "an idle teammate may still have running subagents - check the worktree for recent file changes before killing it" | | "GitHub returns UNKNOWN even when checks pass" | "on UNKNOWN with green CI and zero unresolved threads, retry up to 3 times, then treat as CLEAN" | Keep the fact. The fact is the *why* - it is often bespoke project knowledge the model cannot reconstruct, and dropping it strips the directive of its rationale. The rewrite adds the imperative; it does not replace the fact with the imperative. ## Rule 3: Vague pointer -> concrete location / action (confirmation-gated) Replace the gesture with the named site and action. **This class frequently requires human confirmation**, because the concrete referent is information the document never supplied - the rewriter cannot invent a file path or a threshold that was never stated. | Original (vague) | Rewrite (concrete) | Confirmation | |------------------|--------------------|--------------| | "report blocked if genuinely ambiguous" | "report blocked when the merge conflict touches logic in both branches; auto-resolve pure import/format conflicts" | needed - the threshold was never stated | | "handle errors appropriately" | (no rewrite possible) | needed - no action site exists in the document | | "configured elsewhere" | "configured in `.taskmaster/config.json`" | needed unless the path appears in the document | Decision rule for this class: if the concrete site/threshold is recoverable from elsewhere in the same document, rewrite and let the equivalence gate confirm. If it is not, **flag for human confirmation and leave the original in place** - never fabricate a referent. A fabricated path passes the names-the-concrete-action check and fails reality. ## Rule 4: Ordering / policy rule -> concrete ordered steps Unpack the constraint into the numbered sequence it implies. Both polarities ("X before Y", "never X while Y") become the same ordered steps. | Original (constraint) | Rewrite (sequence) | |-----------------------|--------------------| | "cleanup only after `state == MERGED`; never chain them" | "1. run the merge. 2. confirm `state == MERGED`. 3. only then remove the worktree and branch" | | "stage changes but don't push yet while CI runs" | "fix locally and `git add`; hold the push until CI finishes, then push once" | | "check for an existing PR before creating one" | "1. query open PRs on the branch. 2. reuse an open one; skip a merged one. 3. otherwise create the PR" | Ordering rules carry battle-scars more often than any other pattern (the `state == MERGED` rule exists because chained cleanup once deleted the branch of an unmerged PR). When the classifier flags the ordering rule as a battle-scar, preserve the original wording; the failure-mode it encodes outweighs the directness gain. ## What this transform never does - It never rewrites a flagged battle-scar. Preservation wins over directness. - It never drops a qualifier that scopes a prohibition ("with complex filters", "while CI runs"). Over-broadening is a regression. - It never drops the fact behind a fact-not-action rewrite. The fact is the rationale and often bespoke. - It never fabricates a concrete referent for a vague pointer. Absent a real site, it flags for human confirmation and keeps the original. - It never accepts a rewrite on inspection. Acceptance is the A/B equivalence gate: no-regression and a measured directness gain. -
distill-loop.md 17.7 KB
# The distill loop - iterate to the minimal behaviourally-equivalent document This is the engine of distill mode: the loop that produces the **smallest document that behaves the same as the original**, with acceptance gated on A/B behavioural evidence, never on inspection. It composes ab-equivalence (`skills/ab-equivalence/references/ab-equivalence.md`) for the behavioural test - this engine owns compression (deriving candidates, the loop, the report); ab-equivalence owns the behavioural comparison (running the runner over the transfer set, judging equivalence). **The hard rule, stated once and binding on everything below: a compressed document is never accepted on inspection - only on A/B behavioural evidence from a run against the original.** Introspection about whether a smaller version "still does the same thing" is structurally unreliable - the model guesses optimistically. Execution over the transfer set is the only arbiter. The engine must refuse to output a candidate that has not passed an A/B equivalence run. The loop runs on a **confirmed** transfer set (`transfer-set-design.md`) - the operational definition of essence the user has signed off on. It has three parts: capture the teacher baseline once, regenerate a smaller candidate, and run the controller that iterates the two against the A/B gate until it converges on the minimal passing candidate. ## Part 1 - Teacher baseline capture For each confirmed case, capture how the **original** document behaves. This is the equivalence target every candidate is measured against. <!-- chat-skip:start --> For each case, spawn a runner using ab-equivalence's exact `runner-prompt.md` template, unchanged - drop in the **original** document as the skill draft and the case `input` as the test-case input. The runner applies the document verbatim and returns its five-field self-report. The runner and runner-prompt are reused exactly as ab-equivalence defines them; nothing about how a single version is executed changes. <!-- chat-skip:end --> Record the runner's full self-report per case - all five required fields - and extract the disciplines the document held the runner to: - **`output_produced`** - the exact output the document instructed the runner to produce. - **`steps_followed`** / **`steps_skipped`** - each step the runner followed or skipped, with the reason for any skip. - **`ambiguities_hit`** - each ambiguity in the document and how the runner proceeded despite it. - **`improvisations`** - anything the runner did that the document did not explicitly instruct, and why. - **`wanted_to_deviate`** - any point where following the document felt wrong but the runner did it anyway. - **`disciplines_enforced`** - extracted from the self-report (read from `steps_followed`, `improvisations`, and `wanted_to_deviate`): the rules, guards, and disciplines the original actually held the runner to. This is the load-bearing list - a candidate that lets any of these slide has regressed. A behaviour counts as part of the baseline **only if it actually appears in the teacher transcript** - the engine compares observed behaviour to observed behaviour, never a candidate against what the original document *says* it should do. A faithful distillation preserves the original's gaps as well as its disciplines. ### Gate handling during baseline capture A document that gates on user input (an `AskUserQuestion`, a confirmation prompt) stalls a non-interactive runner: the runner has no user to answer it. The case's `gate_responses` array (`transfer-set-design.md`) is the scripted answer set that carries it past each gate. - Pass the case's `gate_responses` array to the runner. The runner matches each gate's prompt against the entries and injects the response, reporting what it did in self-report field 6 (`gates_hit`). - If the runner hits a gate with no scripted answer, baseline capture for that case is **truncated**: it stops at the gate and reports `STOPPED (no scripted answer)`. - Record in the baseline: `gate_truncated: true` and `truncation_point: { gate_type, prompt_text }`. - The baseline then represents behaviour **up to the gate, not beyond**. This is a loud honest-degrade, not a silent partial pass - the truncation is recorded, judged only up to the gate (Part 3), and declared in the report (`distillation-report-template.md`). ### Caching rule (the teacher is captured once) The original does not change across the loop, so its baseline is **captured once per run and reused across every candidate round**. This is a hard rule, not an optimization: re-running the teacher each round wastes runner budget and risks teacher-side noise the judge would mistake for a candidate change. - **Cache key: `(document_hash, transfer_set_hash)`** - the hash of the original document and the hash of the confirmed transfer set. - **Invalidate** the cache if either changes: a different original document, or any post-confirmation edit to the transfer set (add/modify/reject a case). A changed set means `user_confirmed` must be re-established (see `transfer-set-design.md`) and the baseline recaptured. - **Budget: exactly one runner invocation per case, a fixed cost** paid once regardless of how many candidate rounds follow. ### Baseline schema ```json { "document_hash": "string", "transfer_set_hash": "string", "captured_at": "ISO-8601 timestamp", "cases": [ { "case_id": "string", "self_report": { "output_produced": "string", "steps_followed": ["string"], "steps_skipped": ["string"], "ambiguities_hit": ["string"], "improvisations": ["string"], "wanted_to_deviate": ["string"], "gates_hit": ["string"] }, "disciplines_enforced": ["string"], "gate_truncated": false, "truncation_point": null } ] } ``` - `document_hash` / `transfer_set_hash` - the cache key; both must match the current run or the baseline is invalid and is recaptured. - `cases[].case_id` - matches the transfer-set `cases[].id` and the A/B capability's `case_id`. - `cases[].self_report` - the runner's required fields verbatim, field names shared with `runner-prompt.md`. `gates_hit` records each interactive gate the runner reached and how it was answered (or `STOPPED (no scripted answer)`). - `cases[].disciplines_enforced` - the disciplines extracted from the self-report; the equivalence target for that case. - `cases[].gate_truncated` - `true` when the runner stopped at a gate with no scripted answer; the baseline for this case covers only behaviour up to that gate. - `cases[].truncation_point` - `{ gate_type, prompt_text }` for the gate that truncated capture, or `null` when the case ran to completion. ## Part 2 - Candidate regeneration **Regenerate, do not trim.** Producing a smaller candidate is rewriting the document to function on the behavioural spec - not deleting spans from the original. Trimming can only ever remove text locally; regeneration is what lets a document get genuinely smaller while still inducing the same behaviour. The candidate is written against the baseline (what the document *does*), not against the original's prose. Three micro-operations, applied **in this fixed order**, each preserving the load-bearing identifications the prior step made: 1. **Core -> pointer** (the v1 inner micro-op). For each span explaining a concept the model already holds from training, replace the explanation with the smallest pointer that activates it (the concept name, optionally one disambiguating cue). Keep every bespoke/project-specific detail verbatim, including non-standard twists on a known concept - the v1 rules in `../SKILL.md` govern this step exactly. Pointing is not deletion. 2. **De-duplicate repeated concepts.** Where the document states the same concept, rule, or instruction in more than one place, keep one authoritative statement and drop the repetitions - provided no repetition carries a bespoke detail the others lack. De-dup runs *after* core->pointer so it operates on already-pointed spans and never merges two spans that look alike but differ in a load-bearing twist. 3. **Drop behaviourally-inert prose.** Remove prose the **baseline proves was never acted on** - text that induced no behaviour, discipline, step, or output in any teacher transcript. This is the only license to delete, and it is evidence-gated: a span is inert only if the baseline shows no case's behaviour depended on it. Inert-prose removal runs last so it cannot delete a span that core->pointer turned into a load-bearing pointer or that de-dup chose as the authoritative statement. **Two hard constraints across all three steps:** - **Never delete a section that induced a baseline behaviour.** If any teacher transcript's behaviour, discipline, step, or output traces to a section, that section is load-bearing and is kept (pointed or de-duped, never dropped). The baseline is the record of what is load-bearing. - **Keep every bespoke/project-specific detail verbatim.** Specific facts, local decisions, constraints, exceptions, and non-standard redefinitions have no training source - the model cannot recover them from a pointer. They survive every step unchanged. Each step preserves the prior step's load-bearing identifications: core->pointer marks which spans became activating pointers, de-dup must not drop a span another step marked authoritative, and inert-prose removal must not touch any span the earlier steps or the baseline marked load-bearing. ### Size-tracking schema Track size before and after regeneration so the report can state the delta. Token estimate is `characters / 4`. ```json { "original_size": { "characters": 0, "tokens_estimate": 0, "lines": 0 }, "candidate_size": { "characters": 0, "tokens_estimate": 0, "lines": 0 }, "delta": { "characters_removed": 0, "compression_ratio": 0.0 } } ``` - `*_size.characters` / `lines` - raw counts of the original and the current candidate. - `*_size.tokens_estimate` - `characters / 4`, a coarse token proxy (no tokenizer dependency). - `delta.characters_removed` - `original_size.characters - candidate_size.characters`. - `delta.compression_ratio` - `candidate_size.characters / original_size.characters` (smaller is more compressed; e.g. `0.5` is a 50% reduction). ## Part 3 - The loop controller The iterate-to-minimal loop. It drives the baseline and the candidate through the A/B gate, and on every regression lets the divergence tell it what was load-bearing. ### The loop ``` DEFINE -> confirmed transfer set (transfer-set-design.md); user has signed off. CAPTURE -> teacher baseline, once, cached on (document_hash, transfer_set_hash) (Part 1). COMPRESS -> regenerate a smaller candidate: core->pointer, de-dup, inert-prose removal (Part 2). VALIDATE -> call ab-equivalence (ab-equivalence.md) on (original, candidate, transfer set), passing the cached teacher transcripts so only the candidate is re-run this round. | on regression (any case candidate-regressed): | the behaviour_delta NAMES the load-bearing behaviour that was lost. | ADD BACK the minimum that restores it (see add-back mechanism), then re-VALIDATE. | on no regression (zero candidate-regressed): | check diminishing returns -> shrink again (back to COMPRESS) or CONVERGE. CONVERGE -> output the minimal passing candidate + the A/B distillation report. ``` VALIDATE is the only acceptance gate. A candidate that has not been through it is never output - the hard rule above. The A/B call returns the schema in `ab-equivalence.md`: per-case `verdict` (`equivalent` / `candidate-regressed` / `candidate-diverged`) and a `summary` whose `pass` is true iff zero cases regressed. Divergences do not fail the run - they are surfaced for the user's judgement, not treated as regressions. ### Gate-truncated cases in A/B validation When a case's teacher baseline is `gate_truncated`, the equivalence judgement narrows to match the evidence: - Equivalence is judged **only up to the truncation point** - the candidate must reproduce the baseline behaviour observed before the gate. - The candidate is run with the same `gate_responses` as the teacher, so both stop at the same gate; behaviour **beyond** the gate was never captured for either version and is not measured. - Unmeasured post-gate behaviour is not a pass and not a fail - it is **declared**: the controller records the case in `gate_truncated_cases` and the report carries the Gate-Truncated Coverage section and the `(gate-truncated)` verdict suffix (`distillation-report-template.md`). Never let a gate-truncated case read as full coverage. ### Add-back mechanism A regression is a signal, not a dead end: the judge's `behaviour_delta` for a `candidate-regressed` case **names the specific behaviour lost**. Map it back and restore the minimum: 1. **Lost behaviour -> removed section.** The regressed case's `case_id` ties to the baseline's `disciplines_enforced` and to the transfer-set case's `exercises_sections`. Cross-reference: which section, dropped or over-compressed in the last COMPRESS, induced that behaviour in the baseline? 2. **Restore the minimum.** Add back the smallest piece of that section that restores the lost behaviour - prefer a pointer or a single bespoke detail over restoring the whole section. The goal is the minimal restoration, not reverting the compression. 3. **Re-validate.** Run A/B again. If the same behaviour is still lost, the restoration was insufficient - add back more. If a *different* behaviour regressed, treat it as a new add-back. Record each as a round. The add-back is why the loop converges on a *minimal* document rather than just a *passing* one: it removes aggressively, then restores only what the A/B proves load-bearing. ### Convergence criteria The loop stops on the first of: - **Strict no-regression reached and no further shrink available** - the candidate passes (zero `candidate-regressed`) and the last shrink attempt could only reduce size by regressing. This is the target outcome: the minimal equivalent document. - **Diminishing returns** - any further removal regresses some behaviour (every COMPRESS attempt from here trips the gate). The current passing candidate is the floor; stop and output it. - **Budget ceiling** - a maximum number of candidate rounds (**default 5**). If the loop hits the ceiling with a passing candidate, output it and note in the report that the floor may not have been reached. If it hits the ceiling mid-add-back with no passing candidate, output the **original** (a distillation that cannot be proven equivalent is not shipped) and report the failure. The gate is **behavioural equivalence, not textual similarity** - the loop never accepts a candidate because it "looks equivalent", only because the A/B run shows zero regression. ### Round-tracking schema One object per round, for the A/B distillation report. ```json { "round": 0, "candidate_size": { "characters": 0, "tokens_estimate": 0, "lines": 0 }, "ab_result": { "pass": true, "regressions": 0, "divergences": 0, "equivalents": 0 }, "action": "shrink|add-back|converge", "add_backs": ["string"], "gate_truncated_cases": ["string"], "coverage_notes": "string", "hypothesis": "string", "outcome": "string" } ``` - `round` - the round index (the baseline capture is round 0's precondition, not a round). - `candidate_size` - the size schema from Part 2 for this round's candidate. - `ab_result` - the `summary` block from the A/B capability's output (`ab-equivalence.md`): `pass` plus the verdict counts. - `action` - what the controller did this round: `shrink` (regenerated smaller), `add-back` (restored a lost behaviour), or `converge` (accepted the candidate). - `add_backs` - for an `add-back` round, the behaviours restored (the `behaviour_delta` strings the A/B named) and the sections they mapped to. - `gate_truncated_cases` - the `case_id`s whose equivalence was judged only up to a gate this round (empty when no case was truncated). - `coverage_notes` - a plain-language note on any gate-truncation this round, e.g. "Cases T3, T5 gate-truncated at AskUserQuestion; behaviour beyond gate not measured" (empty string when coverage was full). - `hypothesis` - what this round attempted (e.g. "drop the rationale paragraph in section 3 - baseline shows no case acted on it"). - `outcome` - what the A/B proved (e.g. "passed: paragraph was inert" or "regressed case adversarial-2: the soft rule was load-bearing"). ### Budget math The runner cost is the loop's dominant cost, and it is predictable: - **Teacher baseline: `N` runner invocations**, where `N` is the case count - paid once, cached (Part 1). - **Each candidate round: `N` runner invocations** - the candidate re-run; the teacher is never re-run (the caching rule). The equivalence judge runs once per case per round but is a judge, not a runner. - **Total: `N * (1 + rounds)` runner invocations** - one baseline pass plus one candidate pass per round. Report the **estimated** total at the start (`N * (1 + budget_ceiling)`, the worst case at the default 5-round ceiling) and the **actual** total at convergence (`N * (1 + rounds_run)`). A run that converges in 2 rounds costs `N * 3`, well under the `N * 6` worst case - the report states both so the cost is never hidden. ## Distribution-shift guard A distillation is valid **only over the transfer set it was tested against** - the same overfitting risk model distillation carries outside its transfer distribution. The engine is honest about this and mitigates it two ways: it pushes for diverse, adversarial coverage at derivation time (the breadth of the set bounds the safety of the conclusion - see `transfer-set-design.md`), and it stays **conservative** - it keeps anything not *proven* behaviourally inert by the A/B, never dropping a span on the suspicion it is inert. Silence about untested behaviour is forbidden: the report names the coverage and states plainly that behaviour outside the transfer set is not guaranteed equivalent. -
distillation-report-template.md 8.4 KB
# The A/B distillation report The report is the **evidence** that a distillation was validated - the Hard Rule made auditable. A compressed document ships only with this report alongside it; the report records what was tested, what the A/B proved, and the exact boundary of the equivalence claim. It is not optional narrative: it is the proof that the smaller document behaves the same, and the honest statement of where that proof stops. ## Persistence Save the report as `<document-name>-distillation-report.md` **alongside the output document**. For a distilled skill, that is `skills/<skill>/references/` (sitting next to the skill's other reference docs). The report travels with the artifact it validates so the evidence is never separated from the compressed document. ## Mandatory vs optional fields | Field | Status | Why | |-------|--------|-----| | Size Delta | **Mandatory** | The headline metric - what the compression bought. | | Transfer Set + coverage | **Mandatory** | What was tested; the operational definition of essence. | | Per-Case Equivalence Verdicts | **Mandatory** | The behavioural evidence the compression passed. | | Per-Case Directness (efficiency signal) | **Mandatory for directive-clarity, optional for compress** | The `original_directness`->`candidate_directness` the A/B harness records per case. Directive-clarity *gates* on a measured directness gain, so the report must record it or the gate is unauditable; compress does not gate on it, so the column is informational there. | | Gate-Truncated Coverage | **Mandatory if any gate-truncation** | Honest about partial measurement where a case stopped at a gate. | | Distribution-Shift Caveat | **Mandatory** | The honesty clause - the boundary of the claim. Stated verbatim, never edited away. | | What Was Dropped | Optional | Include if anything was removed (almost always). | | What Proved Load-Bearing | Optional | Include if any add-back occurred, and to list uncovered-but-kept sections. | | Per-Round Log | Optional | Include for multi-round runs. | A report missing any mandatory field is incomplete and the distillation is not validated. The caveat in particular is **never** dropped or softened: a distillation with no stated boundary is a distillation claiming universal validity it does not have. ## Template ```markdown # A/B Distillation Report: <document name> **Run date:** <ISO date> **Mode:** distill **Verdict:** <PASS|FAIL><add ` (gate-truncated)` if any case was truncated at a gate> **Rounds:** <N> **Coverage:** <sections_covered>/<sections_identified> sections exercised (<X>%) ## Size Delta | Metric | Original | Final | Δ | |--------|----------|-------|---| | Characters | N | M | -K (X%) | | Estimated tokens | N | M | -K (X%) | | Lines | N | M | -K | Estimated tokens = characters / 4 (a coarse proxy, no tokenizer dependency). ## Transfer Set | Case ID | Type | Input summary | Exercises sections | |---------|------|---------------|--------------------| | T1 | happy | ... | sections A, B | | T2 | edge | ... | section C | | T3 | adversarial | ... | section D | | T4 | composition | ... | section E | | ... | ... | ... | ... | **Coverage:** N/M sections exercised (X%) **Thin-coverage warnings:** <one line per uncovered/under-covered section: the section name and the behaviour that went untested - or "none"> ## Gate-Truncated Coverage <Include this section whenever any case was gate-truncated; omit it only when no case hit an unanswered gate.> | Case | Gate Type | Truncation Point | Behaviour Beyond Gate | |------|-----------|------------------|----------------------| | T3 | AskUserQuestion | "Install scc?" | NOT MEASURED | | T5 | AskUserQuestion | "Skip permanently?" | NOT MEASURED | **⚠️ Coverage was gate-truncated.** The A/B equivalence verdict applies only to behaviour before the gates listed above. Behaviour triggered by user responses to these gates was not measured in this run. To extend coverage: add `gate_responses` entries for these gates and re-run the distillation. ## Per-Case Equivalence Verdicts | Case | Verdict | Directness (orig->cand) | Behaviour delta | |------|---------|-------------------------|-----------------| | T1 | equivalent | 3->3 | - | | T2 | equivalent | 3->3 | - | | T3 | candidate-diverged | 3->4 | <what differed, not worse - documented for judgement> | | T4 | equivalent | 3->3 | - | Verdicts are `equivalent` | `candidate-regressed` | `candidate-diverged`. **PASS iff zero `candidate-regressed`.** A `candidate-diverged` case is surfaced for the user's judgement, not a failure. Any `candidate-regressed` that survives to the final candidate is a FAIL - the original ships instead. ## What Was Dropped (Proven Behaviourally Inert) <List each section/phrase removed and the evidence it was inert: the baseline shows no case's behaviour depended on it. This is the only license to delete - evidence-gated, never a guess.> ## What Proved Load-Bearing (Kept or Added Back) <List each section that initially seemed droppable but was restored after an A/B regression named it - with the case that regressed and the behaviour it lost.> <Also list each section **kept (uncovered, conservative default)**: never exercised by any case, so not proven inert, so kept by the distribution-shift guard's conservative default.> ## Per-Round Log | Round | Action | Size (chars) | A/B result | Hypothesis | Outcome | |-------|--------|--------------|------------|------------|---------| | 1 | shrink | M1 | pass | drop rationale para in §3 - baseline shows no case acted on it | passed: inert | | 2 | shrink | M2 | regressed (1) | drop the soft rule in §5 | regressed adversarial-2: soft rule was load-bearing | | 3 | add-back | M3 | pass | restore §5 soft-rule pointer | passed: minimal restoration sufficient | | ... | ... | ... | ... | ... | ... | **Runner budget:** estimated N×(1+ceiling) invocations; actual N×(1+rounds_run). <state both> ## Distribution-Shift Caveat **This distillation is valid over the transfer set above.** Behaviour outside this coverage is not guaranteed equivalent. The transfer set is the operational definition of the document's essence for this run - a smaller transfer set is a narrower guarantee. Sections never exercised by any case were kept by conservative default, not proven removable. ``` ## Field notes - **Verdict (header).** `PASS` only if the final candidate has zero `candidate-regressed` cases. If the loop hit its budget ceiling mid-add-back with no passing candidate, the verdict is `FAIL` and the **original** is shipped (a distillation that cannot be proven equivalent is not output) - the report states this. - **Coverage % (header and Transfer Set).** `sections_covered / sections_identified`. Below 70%, the run should have warned before proceeding (the coverage threshold in the Distribution-Shift Guard); if it proceeded anyway, the report says so. - **Behaviour delta.** For a regressed or diverged case, name the specific behaviour - the discipline, step, or output that changed. "Different" is not enough; the report names *what*. - **Directness (orig->cand).** The per-case `original_directness`->`candidate_directness` (1-5) the A/B harness records on every run (`skills/ab-equivalence/references/ab-equivalence.md`). For a **directive-clarity** run this column is the audit trail for the gated directness gain (`candidate_directness` > `original_directness` with no `candidate-regressed`) - omitting it makes the gate unauditable, so it is mandatory there. For a **compress** run the gain is not required; the column is informational and typically reads unchanged (e.g. `3->3`). - **Conservative-default sections.** Every section not exercised by any case is listed under What Proved Load-Bearing as "kept (uncovered, conservative default)" - this is how the report makes the guard's conservatism legible rather than silent. - **Gate-truncated coverage (the honest-degrade rules).** When a case stopped at a gate with no scripted answer (`gate_truncated` in `distill-loop.md`), the report must degrade loudly, never silently: - The **verdict header** carries the `(gate-truncated)` suffix if any case was truncated, e.g. `**Verdict:** PASS (gate-truncated)`. The suffix is mandatory whenever the Gate-Truncated Coverage table is non-empty. - `sections_covered` **excludes** any section reachable only beyond a gate-truncation point - a section behind an unanswered gate was not measured and must not inflate the coverage count or percentage. - `thin_coverage_warnings` **includes** one line per such section: "Section X is reachable only after gate G; not measured in this run." -
transfer-set-design.md 11.4 KB
# Transfer-set design - deriving the operational definition of essence The transfer set is the set of behavioural test cases the distillation is validated against. It is the **operational definition of the document's essence**: the distill loop accepts a smaller candidate only when it reproduces the original's behaviour *on these cases*, so what the set covers is exactly what the compression is allowed to claim safety over. A thin set yields a weak claim; an untested behaviour is an unguaranteed one. The set is therefore derived deliberately, scored for coverage, and **confirmed by the user before any baseline run** - the user signs off on what "same behaviour" means before the engine spends a single runner invocation. This guide owns deriving and confirming the set. The downstream loop (`distill-loop.md`) consumes the confirmed set; the A/B capability it composes (`skills/ab-equivalence/references/ab-equivalence.md`) consumes the same cases verbatim. The case types reuse the skill-forge taxonomy (`skills/skill-forge/references/test-taxonomy.md`), adapted from skill-level to document-level. ## Derivation algorithm Read the document once and extract its **behavioural surface** - every place it tries to induce a behaviour in the reading model. Four extraction passes, each surfacing a different kind of behaviour-inducing construct: 1. **Imperatives and rules.** Every directive the document gives ("always X", "never Y", "do Z before W"), every hard rule, guard, or constraint. Each is a behaviour the original induces and the candidate must reproduce. A rule with no case to exercise it is an untested rule. 2. **Conditional branches.** Every "if X then Y", "when A do B", "unless C", "for the K case". Each branch is a separate behaviour - a case must drive the reader *into* the branch, not just past it. Soft conditionals ("use judgment", "if it seems unnecessary") are the highest-value branches: they are where a smaller candidate is most likely to silently drop a discipline. 3. **Named concepts.** Every concept the document names and relies on (a principle, a method, a domain term - standard or locally redefined). A case should exercise whether the candidate still activates the concept's behaviour. Locally-redefined concepts (the bespoke twist) are load-bearing: a case must prove the candidate keeps the *twist*, not just the standard concept. 4. **Composition points.** Every place the document assumes it runs alongside another skill, consumes another process's output, or hands off to one. Each is a behaviour that only appears when the document is composed, never in isolation. A fifth pass identifies **interactive gates** - the points where the document pauses for user input rather than acting on its own: `AskUserQuestion` calls, confirmation prompts ("Do you want to...?"), and user choice points ("Select option A, B, or C"). A gate is not a behaviour to reproduce; it is a point where the A/B run stalls, because the runner that applies the document has no interactive user to answer it. Left unhandled, every case that reaches a gate measures only pre-gate behaviour - a silent partial pass. So each gate a case drives into needs a scripted answer. For every gate found, generate a default `gate_responses` entry on the cases that reach it (the gate type, a pattern matching its prompt text, and a default response); the user confirms these alongside the case inputs, and a case that drives no gate carries an empty array. The output of derivation is a list of candidate behaviours, each tagged with the section(s) of the document that induce it, plus the interactive gates each case reaches. This section map is what coverage scoring and the add-back mechanism (in `distill-loop.md`) both read. ## Generating cases across the four types Group the derived behaviours into test cases spanning the four taxonomy types, read at **document level** (the document is the unit under test, not a single skill step): | Type | Document-level reading | What to generate | |------|------------------------|------------------| | **Happy path** | The document used exactly as intended, on the single most common real input. | One case driving the document's primary, in-scope behaviour end to end. If the candidate regresses here, nothing else matters. | | **Edge case** | A boundary or unusual-but-valid input - empty input, the largest plausible input, a value at a threshold the document names, a precondition satisfied in an unexpected way. | One case on the boundary of each threshold/count/"if X" branch found in derivation. | | **Adversarial** | Input that tempts the reader to **rationalize its way out of** a discipline the document imposes - a soft rule that "obviously doesn't apply here", an invitation to improvise a shortcut. | One case per soft instruction surfaced in derivation, built so skipping the discipline looks reasonable. These are where inert-looking prose is most often load-bearing. | | **Composition** | The document combined with another skill/concept, or applied to another process's output. | One case per composition point - the most likely real pairing. | Not every type needs equal weight: a document with a fragile trigger or a rationalization risk leans adversarial; a document meant to chain with others needs at least one composition case. The aim is that **every load-bearing behaviour found in derivation is exercised by at least one case** - because the A/B run can only catch a regression on a behaviour some case drives. ## Coverage flagging Track, per case, which document sections it exercises (`exercises_sections`). Aggregate across the set to score coverage: - **`sections_identified`** - the count of behaviour-inducing sections found in derivation. - **`sections_covered`** - the count exercised by at least one case. - **`thin_coverage_warnings`** - one warning per uncovered or under-covered section: the section name and the behaviour that will go untested. A section that induces a behaviour but is exercised by no case is the exact place a distillation can silently lose behaviour, so each is surfaced loudly rather than passed over in silence. **Minimum viable transfer set: 5 cases, at least one of each of the four types.** Below this the equivalence claim is too weak to gate on - the loop refuses to proceed and reports the gap. More cases are better: the breadth of the set bounds the safety of the compression (the distribution-shift guard in `distill-loop.md`). Coverage is never silently truncated - if cases are capped, the dropped sections are named in `thin_coverage_warnings`. ## User confirmation protocol The transfer set *is* the operational definition of essence, so the user signs off on it before the engine commits to it. Confirmation is a hard gate: **no baseline run, no runner invocation, until the user confirms.** 1. **Present** the derived set: each case with its `type`, `input`, the `exercises_sections` it drives, and the `gate_responses` derivation generated for any interactive gates it reaches. Present coverage alongside - `sections_identified`, `sections_covered`, and every `thin_coverage_warning` - so the user sees what is and is not protected. 2. The user may **accept** the set as-is, **modify** a case (input, type, or a gate response), **add** a case (covering a behaviour they know matters that derivation missed), or **reject** a case (a "behaviour" they consider incidental and not part of the essence). The user also confirms or corrects each `gate_responses` entry - a wrong scripted answer steers the runner down the wrong branch, so the answers are signed off alongside the inputs. Each add/modify updates the section map and re-scores coverage. 3. **Confirm.** Only once the user explicitly confirms is `user_confirmed` set true and `confirmation_timestamp` recorded. The confirmed set is then frozen for the run and its hash (`transfer_set_hash`, see `distill-loop.md`) becomes part of the baseline cache key - changing the set after confirmation invalidates the baseline. The protocol is deliberately conservative: a behaviour the user does not list as essence can still be preserved (the loop keeps anything not *proven* inert), but a behaviour the user *does* list is a hard equivalence target. The user can broaden the definition of essence but the engine never narrows it on its own. <!-- chat-skip:start --> The presentation and confirmation happen in whatever harness mode the engine runs in (see `distill-loop.md`): in phased sub-agent mode the lead presents the set and waits for the user before spawning any runner; in solo/chat mode the single agent presents and waits in line. No runner subagent is spawned until `user_confirmed` is true - this is enforced by the loop controller, not left to the runner. <!-- chat-skip:end --> ## Transfer-set schema The confirmed set is the contract the loop and the A/B capability both read. Field names here are shared verbatim with the schemas in `distill-loop.md`. ```json { "document_hash": "string", "derived_at": "ISO-8601 timestamp", "coverage": { "sections_identified": 0, "sections_covered": 0, "thin_coverage_warnings": ["string"] }, "cases": [ { "id": "string", "type": "happy|edge|adversarial|composition", "input": "string", "exercises_sections": ["string"], "status": "derived|user-modified|user-added|confirmed", "gate_responses": [ { "gate_id": "string", "gate_type": "AskUserQuestion|other_gate_type", "pattern": "regex or substring to identify the gate prompt", "response": "the scripted answer to provide" } ] } ], "user_confirmed": false, "confirmation_timestamp": "ISO-8601 timestamp or null" } ``` - `document_hash` - hash of the original document the set was derived from. With `transfer_set_hash` (the hash of this confirmed set, computed by the loop) it forms the baseline cache key in `distill-loop.md`. - `derived_at` - when derivation ran. - `coverage` - the flagging described above; `thin_coverage_warnings` is empty only when every identified section is exercised. - `cases[].id` - the case identifier the A/B capability echoes back as `case_id` in its per-case verdict. - `cases[].type` - one of the four taxonomy types; the set must contain at least one of each. - `cases[].input` - the exact input both the teacher and candidate runners receive, unchanged between versions. - `cases[].exercises_sections` - the document sections this case drives; the add-back mechanism maps a lost behaviour back through these. - `cases[].status` - provenance: `derived` (engine-generated), `user-modified`, `user-added`, or `confirmed` (the final state of every case in a confirmed set). - `cases[].gate_responses` - scripted answers for the interactive gates this case reaches, consumed in order by the runner; an empty array for a case that drives no gate. The runner (`skills/ab-equivalence/references/runner-prompt.md`) matches each gate's prompt against the entries and injects the response; a gate with no matching entry truncates the baseline (see `distill-loop.md`). - `gate_responses[].gate_id` - identifier for this gate instance within the case. - `gate_responses[].gate_type` - the tool or gate type (`AskUserQuestion`, a confirmation prompt, a choice point). - `gate_responses[].pattern` - regex or substring matching the gate's prompt text, used to pair the response to the gate at runtime. - `gate_responses[].response` - the deterministic answer injected when the pattern matches. - `user_confirmed` / `confirmation_timestamp` - the sign-off gate; both must be set before the loop captures the baseline.
-
-
SKILL.md 23.1 KB
--- name: semantic-compress description: "Make an LLM-directed document smaller while preserving what it does. Two modes: a local span-level core->pointer pass, and an A/B-validated distill loop that produces the smallest document that behaves the same as the original. Point at core knowledge the model already holds (a concept name activates it); keep project-specific detail explicit and verbatim. TRIGGER when asked to compress, tighten, shorten, or strip a prompt / instruction / system message meant for an LLM; to distill a skill; to compress a whole document; to make this smaller while preserving behaviour; to A/B test a compression or produce a behaviourally-equivalent compression; when an instruction set explains concepts the model already knows from training; or when reducing token cost of an LLM-directed prompt without losing meaning. Not for human-facing prose - that is /deslop." --- # Semantic Compress Make a document written **for an LLM reader** smaller while preserving what it *does*. The essence of an LLM-directed document is **behavioural**, not textual - the behaviour it induces in the reading model across the tasks it handles. Compression splits content into two kinds and treats each correctly: - **Core knowledge** - anything in the model's training (named concepts, standard definitions, common-domain background). Replace the explanation with a **pointer**: the bare concept name or a short cue that activates the right knowledge. A pointer costs a few tokens and reliably switches on the model's existing understanding; a full explanation is wasted tokens, and *deleting the concept entirely* gambles that it is already active in the model's reasoning - often it is not, and disambiguation suffers. - **Project / bespoke knowledge** - anything the model cannot know from training: specific facts, local decisions, constraints, and non-standard twists on a known concept. Keep these **explicit and verbatim**. The model has no other source for them. Compression is therefore **point at core, spell out bespoke**. Pointing is not deletion and it is not full explanation; it is the minimum that both activates the right core knowledge and preserves every bespoke detail. ## Mode Selection This skill operates in one of two modes, selected **deterministically**: | Input | Mode | What happens | |-------|------|-------------| | Short snippet with an obvious local swap, no behavioural surface | **Local** | Quick core->pointer pass, no A/B | | Whole document / skill / system prompt | **Distill** | Full A/B-validated loop | **Default to distill** when: - The input is a skill, system prompt, or instruction document - Behaviour preservation matters - The user asks for "smaller but same behaviour" **Local is permitted only** when **all** hold: - The input is a short span (< 500 chars) - There is an obvious single core->pointer swap - No downstream behaviour depends on it When in doubt, distill: a local edit cannot, by construction, preserve a global behavioural property, so anything with a behavioural surface goes through the A/B gate. ## Hard Rule: Behavioural Evidence Required A compression is **never accepted on inspection** - only on behavioural evidence from an A/B run. This skill must refuse to output a compressed document that has not passed an A/B equivalence run against the original. Introspection about behaviour ("this should work the same") is structurally unreliable - the model guesses optimistically. Execution over the transfer set is the only arbiter. This rule binds **distill mode** (the rule's home: whole-document compression always carries behavioural risk). Local mode is the deliberate, narrow exception - a span small enough (< 500 chars, single obvious swap, no downstream behaviour) that the behavioural risk is negligible by construction. The moment a local edit touches a behavioural surface, it is no longer local: it is a distill, and the gate applies. ## Local Mode The v1 span-level operation: find a span that explains a concept the model already holds, replace it with a pointer, keep every bespoke detail verbatim. These steps are also the **inner micro-operation** distill mode regenerates with (`references/distill-loop.md`, Part 2, step 1). ### Step 0: Audience gate This skill applies only when the **LLM is the audience** for the explanation. If the text explains a concept *to a human* (onboarding notes, a message to teammates, docs for new hires), the explanation is not redundant for its real audience - leave it. Compress only the spans the model itself is meant to read and act on. **Nested / wrapped instructions.** If the input wraps an instruction the model is meant to process (e.g. "preprocess this instruction before executing it: '...'", or a quoted prompt to compress), the wrapper is a **meta-directive to you** - act on it, do not emit it. Compress the *quoted payload* by the rules below and return only that. The payload's audience is the model, so the audience gate is satisfied for the payload regardless of the wrapper. ### Step 1: Split each span into core vs bespoke Read the input. For each span, classify: - **Core** - a named concept or standard definition the model already holds (Chesterton's Fence, the Agile Manifesto, idempotency, SOLID, optimistic locking in its standard form). - **Bespoke** - information with no training source: a specific number, a local choice, a constraint, an exception, or a **non-standard redefinition** of an otherwise-known term. A single sentence often contains both. Split at that seam. ### Step 2: Core -> pointer Replace a core-knowledge explanation with the smallest cue that activates it - usually the concept's name, optionally one disambiguating word: - "the principle that you shouldn't remove something until you understand why it's there" -> "Chesterton's Fence" - "observe-orient-decide-act faster than the competitor" -> "OODA" **Always emit the pointer**, with one exception: if a surviving bespoke span already names the concept literally, the pointer is redundant - omit it then, and only then. Never drop the pointer on the grounds that the frame is merely "implied" - that judgement is self-certifying and is exactly the escape that collapses this skill back into deleting what the model knows. When in doubt, keep the pointer; it costs almost nothing. ### Step 3: Bespoke -> explicit, verbatim Keep every bespoke span unchanged: specific facts, decisions, constraints, and especially **non-standard twists**. If a known term is redefined locally ("optimistic locking - but we hash the whole record, not a version counter"), the twist is bespoke: keep it in full. Dropping it because the term looks familiar is the most dangerous failure this skill can make. ### Step 4: Self-sufficiency check Could the model act correctly on the output alone? If a pointer is too thin to disambiguate, widen it by one cue word - never back to a full explanation. If a bespoke detail was lost, restore it verbatim. The all-core degenerate case (input that is purely known concepts with nothing bespoke and no instruction) compresses to its pointers; if there is also no instruction to act on, the output is just those pointers - not empty text and not a meta-note. ### Step 5: Output Clean text: pointers for core, explicit bespoke. No meta-commentary, no "[compressed]" markers, no "see X" links - the pointer *is* the reference, inline and natural. ### Examples #### Core -> pointer, bespoke kept **Input:** "When evaluating whether to remove a legacy feature, first understand why it was created - you shouldn't destroy something without understanding its purpose, as it may serve a need you're unaware of. Our legacy auth module was built for EU data-residency compliance that still applies to German users." **Output:** "Chesterton's Fence on removing the legacy auth module: built for EU data-residency compliance, still applies to German users." #### All-core -> pointers (degenerate case) **Input:** "Remember to follow the Agile Manifesto and the SOLID principles in this project." **Output:** "Follow Agile and SOLID." Nothing bespoke and no instruction beyond the frame, so it compresses to the bare pointers - not to empty text. #### Non-standard twist is bespoke **Input:** "We use optimistic locking - by which we specifically mean the client sends a hash of the entire record snapshot, and the server rejects on any field mismatch, not just a version-counter bump." **Output:** "Optimistic locking, but our variant: client sends a hash of the whole record snapshot; server rejects on any field mismatch, not a version-counter bump." #### Nothing core **Input:** "The staging cluster runs 3 nodes in eu-west-2 with a 4GB heap cap per node; the nightly backup starts at 02:00 UTC to the cold-storage bucket." **Output:** (unchanged - all bespoke) #### Human audience -> do not compress **Input:** "Reminder for new hires: 'idempotent' means you can safely retry an operation. Skim the API onboarding doc before Friday." **Output:** (unchanged - the definition is for new hires, not the model) ### Anti-patterns - **Deleting the concept instead of pointing at it.** Pure elision gambles the concept is already active; a pointer guarantees activation at near-zero cost. "The frame is implied" is not a licence to delete - emit the pointer unless a bespoke span literally names the concept. - **Dropping the bespoke part.** "Chesterton's Fence on the legacy module" without the EU-compliance reason is useless - the reason is the whole payload. - **Dropping a non-standard twist as if it were the standard concept.** If a known term is locally redefined, the redefinition is bespoke. Keep it. - **Re-explaining core knowledge.** A pointer suffices; a paragraph is waste. - **Compressing human-directed text.** The audience gate comes first. - **Meta-commentary.** No "[pointer]", no "// core concept". The output reads as natural, shorter text. ## Distill Mode The headline operation: produce the **smallest document that behaves the same as the original**, with acceptance gated on A/B behavioural evidence (the Hard Rule above), never on inspection. Distill mode composes two reference docs: - `references/transfer-set-design.md` - derives and confirms the transfer set (the operational definition of the document's essence). - `references/distill-loop.md` - the engine: teacher baseline capture, candidate regeneration, and the iterate-to-minimal controller. <!-- chat-replace:distill-availability --> The loop **composes `ab-equivalence`** (`skills/ab-equivalence/references/ab-equivalence.md`) for the behavioural test. This skill owns compression (transfer set, candidate regeneration, the loop, the report); `ab-equivalence` owns the behavioural comparison (running the runner over the transfer set, judging equivalence per case). ### The distill loop (seven steps) 1. **Define the transfer set.** Derive behavioural test cases from the document across the four taxonomy types (happy / edge / adversarial / composition). The user confirms or augments them before any baseline run - the transfer set *is* the operational definition of essence, so the user signs off. The skill flags thin coverage. See `references/transfer-set-design.md`. 2. **Capture the teacher.** Run the **original** document over each case and record the behaviour it induces (the disciplines enforced, the outputs produced). This baseline is the equivalence target, captured **once** and cached across rounds. 3. **Compress = regenerate, not edit.** Produce a smaller candidate against the behavioural spec: the local core->pointer move (Local Mode above), de-duplication, and dropping prose the baseline proves was never acted on. Regeneration - rewriting to function - is what lets it get genuinely smaller; trimming alone cannot. 4. **A/B validate.** Run the **candidate** over the same transfer set; the equivalence judge compares candidate-vs-teacher behaviour per case under **strict no-regression**. This is the only acceptance gate. 5. **On any regression, the divergence names what was load-bearing.** The judge's behaviour delta names the lost behaviour; add back the minimum that restores it, then re-validate. 6. **Converge** on the smallest candidate that still passes. Stop at diminishing returns (a round that can only shrink further by regressing) or a budget ceiling (default 5 rounds). 7. **Output** the minimal equivalent document plus an **A/B distillation report** (`references/distillation-report-template.md`). **Strict no-regression gate.** The candidate passes iff, on every transfer-set case, it induces every behaviour the original induced. Any dropped behaviour is a fail (essence lost) and triggers add-back. Incidental improvements are acceptable but never required and never the goal - the goal is faithful, smaller reproduction. The gate is behavioural equivalence, not textual similarity. ### Distill mode example **Input:** "Distill this CLAUDE.md to the smallest version that behaves the same" **Output:** The minimal equivalent document plus an A/B distillation report (`<document-name>-distillation-report.md`) recording the size delta, the transfer set and its coverage, per-case equivalence verdicts, what was dropped vs load-bearing, and the distribution-shift caveat. ## Distribution-Shift Guard A distillation is only valid over the transfer set it was tested against - the same overfitting risk model distillation faces outside its transfer distribution. The transfer set is the operational definition of essence *for this run*; behaviour outside its coverage is unproven. ### Mitigations 1. **Push for diverse coverage.** The transfer set should span all four case types (happy / edge / adversarial / composition). A narrow transfer set (all happy-path) permits aggressive compression that breaks on edge or adversarial inputs - the breadth of the set bounds the safety of the compression. 2. **Stay conservative.** Keep anything not **proven** behaviourally inert by the A/B. If a section was never exercised by any case, it is **not proven inert** - it might be load-bearing for untested inputs. The conservative default is to keep it. 3. **Name the coverage.** The report explicitly states which sections were exercised and which were not. Silence about untested behaviour is forbidden. ### What the guard prevents - Compressing away a section that only matters for adversarial inputs when the transfer set was all happy-path. - Claiming "same behaviour" when behaviour outside the transfer set was never checked. - The user believing the compression is universally valid when it is only valid for the tested cases. ### Coverage threshold If **fewer than 70%** of the identified behaviour-inducing sections are covered by at least one case, **warn the user before proceeding** and offer to auto-generate additional cases for the uncovered sections. A distillation gated on a thin set makes a weak equivalence claim - say so loudly rather than proceed silently. ### Conservative-default logic ``` For each section in the original: if exercised_by_at_least_one_case(section): if A/B says inert: can drop if A/B says load-bearing: must keep (pointed or de-duped, never dropped) else: section is NOT PROVEN INERT keep by default (conservative) report as "kept (uncovered, conservative default)" ``` ### Report integration The A/B distillation report (`references/distillation-report-template.md`) makes the coverage legible: - Transfer-set coverage percentage in the header. - Thin-coverage warnings listed in the Transfer Set section. - Uncovered-but-kept sections labelled "conservative default" under What Proved Load-Bearing. - The distribution-shift caveat stated verbatim: behaviour outside the transfer set is not guaranteed equivalent. ## Transform #2: Directive-clarity Compression (Local + Distill above) is Transform #1 of this skill's optimizer family: it makes a document *smaller* while preserving behaviour. Directive-clarity is Transform #2: it makes a document *lighter to act on* while preserving behaviour, by rewriting instructions that force the reading model to unpack an action before it can act into concrete directives that name the action. Both transforms share one validator - ab-equivalence's harness (`skills/ab-equivalence/references/ab-equivalence.md`) - and differ only in the gate they apply to its result. The frame that generates this transform, and the caveat that keeps it honest (every claimed gain is a hypothesis the harness must measure, never an assertion), is `references/cognitive-ergonomics.md`. ### When it fires - **Explicitly invoked**: `--directive-clarity <document>` targets a document directly. - **Auto-suggested**: when distill mode detects high negation density or vague-pointer patterns in the document it is compressing, it surfaces directive-clarity as the recommended next stage. This is the natural entry into the staged compose order below. - **Never auto-applied.** The transform *proposes* rewrites; the A/B run *validates* them; the user *approves* the result. No rewrite reaches the output document without passing the gate and the user signing off - directive-clarity changes the words the model acts on, so the human confirms the change. ### The loop 1. **Detect patterns.** Scan the document for the four latent-action shapes - bare negation, fact-not-action, vague pointer, ordering/policy rule - per `references/directive-clarity-patterns.md`. Detection over-includes on purpose: it optimises for recall, flagging every candidate shape. 2. **Classify each candidate convert-for-free vs battle-scar.** Route every detected pattern through `references/battle-scar-classifier.md`. A battle-scar - a prohibition earned from a specific past failure, where the wording is the load-bearing content - is flagged for preservation and never rewritten. The classifier is the precision half: it holds back scars to a sub-10% false-positive target, defaulting to preserve on uncertainty. Only the convert-for-free set proceeds. 3. **Rewrite.** Turn each released candidate into a concrete directive per `references/directive-clarity-rewrites.md`, under the two acceptance checks (names-the-concrete-action, semantic-equivalence). Keep every qualifier that scopes a prohibition; keep the fact behind a fact-not-action rewrite; never fabricate a referent for a vague pointer - flag it for human confirmation and leave the original in place. 4. **A/B validate.** Run the candidate against the teacher over the transfer set via ab-equivalence's harness (`skills/ab-equivalence/references/ab-equivalence.md`). The harness emits, per case, an equivalence verdict (`equivalent` / `candidate-regressed` / `candidate-diverged`) and an efficiency signal (`original_directness`, `candidate_directness`, `interpretation_notes`). 5. **Present for approval.** Surface the per-rewrite diffs, the A/B verdicts, the preserved battle-scars, and any vague pointers held for confirmation. The user approves before the rewritten document is emitted. ### The gate Directive-clarity's acceptance is **stricter than compression's**. Compression gates on strict no-regression alone (sameness). Directive-clarity gates on no-regression **and** a measured directness gain: - **No-regression**: zero cases return `candidate-regressed` (`summary.pass == true`). A rewrite that reads cleaner but permits a behaviour the original forbade is a regression and fails here. - **Directness gain**: `candidate_directness` > `original_directness` on the rewritten cases, with no `candidate-regressed`. This is the efficiency signal the A/B harness already records on every run (`skills/ab-equivalence/references/ab-equivalence.md`) - directive-clarity *reads* that signal and gates on it; it does not redesign or re-instrument the harness. A rewrite that loses nothing but also measures no directness improvement has not earned its place: keep the original. On a regression, the divergence names the rewrite that lost behaviour. Revert that specific rewrite to its pre-rewrite form, keep the passing rewrites, and re-validate - the same granular add-back the distill loop uses. ### How directive-clarity composes with compress When both transforms run on one document, they run as **Option C: staged with a checkpoint** - compress first, freeze its gains, then directive-clarity on top, with any regressing rewrite reverting to the frozen state without losing the compression. **Why staged, not the alternatives:** - **Not combined/interleaved (Option B).** The two transforms apply *different gates* to the A/B result: compress accepts on no-regression alone, directive-clarity demands no-regression *and* a directness gain. A single interleaved A/B cycle cannot apply two gates to one result, and when a combined pass regresses there is no way to attribute the loss to a compression pointer-swap or a directive rewrite. Attribution loss makes add-back guess. Ruled out. - **Not loosely independent (Option A).** Running two unlinked passes leaves directive-clarity's teacher baseline underspecified. If its A/B teacher were the *original* document, the measured directness gain would conflate compression's effect with directive-clarity's, and nothing would structurally stop a directive-clarity regression - measured against the original - from pulling behaviour back past the compression gains. Clean attribution and a protected floor both require an explicit checkpoint. - **Staged with checkpoint (Option C, chosen).** The compressed document becomes an explicit checkpoint that is *both* directive-clarity's new teacher baseline *and* its revert floor. Directness is then measured against compressed behaviour (clean attribution: the gain is directive-clarity's alone), and no rewrite can regress below the compressed state because the teacher *is* the compressed state. This is exactly the behaviour the transform must guarantee: compress gains kept, only the regressing rewrite reverts. **Run order and checkpoint mechanism (precise):** 1. **Compress to convergence.** Run distill mode to its minimal equivalent document `D_compressed`, accepted by compression's strict no-regression gate against the original teacher. 2. **Checkpoint.** Freeze `D_compressed` as the directive-clarity stage's input. Capture a fresh teacher baseline by running the A/B harness's runner over `D_compressed` once per transfer-set case - these become the cached teacher transcripts for the next stage. (`D_compressed` is behaviourally equivalent to the original by construction, but its *directness* differs, so the gain must be measured against `D_compressed`'s directness, not the original's.) The original teacher transcripts from step 1 are not reused here. 3. **Directive-clarity over `D_compressed`.** Detect -> classify -> rewrite (loop steps 1-3) to produce candidate `D_direct`. 4. **A/B validate `D_direct` against the `D_compressed` checkpoint teacher.** Apply the directive-clarity gate (no-regression AND directness gain). 5. **Granular revert on regression.** A rewrite that regresses against the checkpoint reverts to its `D_compressed` form; passing rewrites stay. Because the teacher is `D_compressed`, the checkpoint is the floor - no rewrite can drop behaviour below the compressed state. Re-validate and converge. Compress first (not directive-clarity first) because compression can point-away or de-duplicate prose that directive-clarity would otherwise spend a rewrite and an A/B cycle on; compressing first shrinks the surface directive-clarity must scan and avoids rewriting spans that compression removes.
Comments (0)
Sign in to join the conversation.
Reviews (0)
No reviews yet.
No comments yet.