Elenchus Protocol

SkillDev tools

Vet working context by dialectical antithesis before action. Type: (ContextSuspect, User, VET, WorkingContext) → VettedContext

Available today. Use it from your connected AI after setup.

Connect ahel once, and every AI you use reads what you have installed.

Then ask your AI: use the Elenchus Protocol skill

What this skill tells your AI

The instructions your AI receives, as published by jongwony/epistemic-protocols in elenchus/skills/sublate/SKILL.md and read by ahel’s review.

Vet working context by dialectical antithesis before action through structured per-claim disposition judgment, resolving suspect context into vetted context. Type: (ContextSuspect, User, VET, WorkingContext) → VettedContext.

Definition

Elenchus (ἔλεγχος): A dialogical act of cross-examination — from the Socratic refutation tradition meaning "testing by argument" — resolving suspect working context into vetted context through provenance challenge, counterfactual gap forecasting, cross-source consistency check, and inference-fallacy archetype scan before pre-execution sync. The protocol's lexical verb is /sublate. Each audit — a source under one claim it is read as authority for — undergoes the motion of stating that claim, surfacing what would shake it, and then deciding what to make of it in light of that challenge (the Hegelian Aufhebung — preserve + negate + lift up — supplies the source vocabulary).

── FLOW ──
W → identify(W) → S_cand → ∀s ∈ S_cand: bind_kind(s) → k [split where non-atomic] → certify(k, local_claims) → [status(c) = ambiguous: Qa(a, claims_supported(a)) → Stop → Attribution → attribute(a, ·)] → keep(status(c) = pass) → bind_value_space(audit, W.action) → S_high →
  S_high = ∅ ∧ nothing yet posited: emit VettedContext (extension) → deactivate   -- trivial convergence, two causes reported apart: no audit-candidate source at all (Λ.formed = ∅, dispositions empty), or candidates every one of which this protocol handed elsewhere or the user left unattributable (dispositions = the records Phase 0 wrote, by the certificate or by the user's attribution, so NOT empty). Both are first-pass shapes; a loop return that empties S_high leaves through the loop, never here, or the antitheses already put to the user would go out with an empty trace
  Λ.pass = ∅ ∧ something already posited: → integrate(J, Λ.antitheses) → V → LOOP evaluation   -- a loop return this re-pass admitted nothing from: nothing to tag, posit against, or ask, but V is still re-assembled because this pass rewrote J
  Λ.pass ≠ ∅: tag(provenance, freshness, leverage) → S' → posit(antithesis) → A[] →   -- tagging and positing range over Λ.pass, not the standing S_high, so an audit judged in an earlier cycle is not worked again unless its Revisit returned it
  Q(per-claim disposition slots) → J → integrate(J, Λ.antitheses) → V →
  (loop if ∃ a ∈ S_high : J(a.ref).disposition = Judged(_, Revisit(c)) ∧ trigger_met(c))   -- the S_high membership is part of the condition, not a shorthand: a met Revisit is owed a fresh antithesis and a fresh question, and only a member can be given either

── MORPHISM ──
WorkingContext
  → identify(high_leverage_sources, S_cand)    -- silent scan for sources warranting audit
  → bind_kind(source) → [split_binding(binding, source) where atomicity = non-atomic] → certify(kind_binding, local_claims) → bind_value_space(audit, W.action) -- shared meta-backbone: bind each candidate to the CLAIM it is being read as authority for, SPLIT the binding where it bundles more than one claim so certify never sees a compound, certify deficit fit (fail-closed) against the claims inscribed in this SKILL.md, then derive the NARROWING its judgment will be made against — in that strict order, BEFORE any tagging, antithesis, or surfacing. ONLY status(certificate) = pass CONTINUES DOWN THIS CHAIN: route writes a certificate-assigned record straight into the disposition table emit reads; ambiguous is put to the user at Qa, whose attribution admits the candidate, hands it elsewhere, or records it Unattributable — so every step below ranges over the admitted audits alone
  → tag(provenance, freshness, leverage)        -- attach metadata triple per audit
  → posit(antithesis per audit)                 -- Pattern A ∪ Pattern B ∪ Pattern C ∪ Pattern D ∪ Emergent(Pattern)
  → present(antitheses as text, then the reach of a judgment, then the question)  -- per-claim material and a per-batch reach note precede the gate, which carries the question and the instruction list
  → judge(disposition per audit)                 -- over the admitted audits: an open verdict plus an optional instruction, whose members are only what this protocol can itself discharge. The records Phase 0 wrote — certificate- or attribution-assigned — are already in the table and are reported rather than judged
  → emit(VettedContext with disposition table)
  → VettedContext
requires: working_context_pre_execution_committed   -- runtime checkpoint (Phase 0)
deficit:  ContextSuspect                            -- activation precondition (Layer 1); the certificate's own_claim deficit for in-scope audits
preserves: source_chain                              -- W.sources is read-only; binding, antithesis and disposition annotate, never mutate. A split produces several AUDITS over one source, never several sources
invariant: Dialectical Vetting over Silent Trust
invariant: certificate-before-surfacing              -- status(certificate) = pass strictly precedes entry into S_high, and therefore precedes tagging, antithesis positing, and the Phase 2 disposition gate (shared meta-backbone order)

── TYPES ──
W              = WorkingContext { sources: List(Source), action: Prospect }
Prospect       = { intent: String, leans_on: List(Reference) }   -- the pre-execution action the working context was committed against: what is about to be done, and the source references it rests on. READ by bind_value_space, which turns it into the Stake each judgment is made against. Without that read a user is asked what they make of a claim with no statement of what the pending action would do with it, and a claim's standing gets judged in the abstract — which is the one condition under which the judgment cannot be theirs to make
Source         = { content: String, origin: Origin, observed_at: Timestamp, downstream: List(Reference) }
Origin         ∈ {UserStatement, DocumentRead, ToolOutput, AIInference, ExternalAPI, PastSession} ∪ Emergent(Origin)
identify       = WorkingContext → Set(Source)            -- silent selection per Source Identification Criteria
S_cand         = Set(Source) from identify(W)            -- audit CANDIDATES: sources the criteria selected, before any of them is bound to a claim, certified, or admitted
AuditRef       = { source: Source, claim_at_admission: ClaimRef }      -- the audit's STABLE IDENTITY, and the only thing any map is keyed by
               -- both components are fixed when the audit is FORMED and never rewritten, which is what lets a re-triggered audit be re-bound and re-certified without its key moving. Every map below — S', J, V.trace, and the Λ maps — is keyed by this ref; two audits are the same audit exactly when their refs agree, and two siblings from an atomicity split get distinct refs because they were admitted under distinct claims
               -- claim_at_admission IS NOT THE LIVE CLAIM. The claim under test is kind_binding.label, which a loop re-binding may replace when the conditions that fired the trigger moved the claim with them. Keeping the admission claim in the ref is what stops the identity from drifting while the challenge still aims at the claim as it now stands; where the two have come apart, the trace shows both
ClaimAudit     = { ref: AuditRef, kind_binding: KindBinding, certificate: DeficitFitCertificate }
               -- object_ref: the per-audit anchor the certificate evaluates and the value space binds over (elenchus-local instantiation of the shared backbone's object_ref)
               -- IDENTITY VERSUS CONTENTS: ref is the identity, and the two fields after it are contents a loop re-binding and re-certification may replace. Keying anything by the whole record would make re-certifying a re-triggered audit silently change its key and orphan its entry, so the split is load-bearing rather than presentational
               -- THE UNIT OF JUDGMENT IS THE SOURCE UNDER ONE CLAIM, not the source. One source read as authority for two distinct claims is TWO audits, which is what the atomicity split produces and what `Claim-relative provenance` has always required be surfaced rather than carried over silently. A source carrying one claim yields one audit, which is the ordinary case
               -- THE NARROWING IS NOT A FIELD HERE: bind_value_space takes this record as an argument and writes its result to Λ.narrowing, keyed by ref, so this type carries none of it. That keeps the type inhabitable at every point the pipeline reaches it — including for a candidate the certificate routes or leaves unattributable, which never earns a narrowing at all, and for the record as it stands between certify and the Phase 2 slot. A field would demand a value the contract forbids producing until the certificate passes; the narrowing lives in Λ.narrowing instead, keyed by the audit's ref
               -- ASSEMBLED ACROSS THE PHASE 0 PIPELINE: identify selects the source, bind_kind binds the claim it is read as authority for, and certify fits that binding against the local claims — in the order `Admission-time certificate` fixes
               -- The REF is fixed before the record completes, at the point the binding is settled atomic, and that is what Λ.formed holds. So the convergence domain is a set of identities and never a set of records: a candidate can be answered for from the moment its identity exists, which is earlier than the moment it becomes a ClaimAudit this protocol tags, posits against, or surfaces
S_high         = Map(AuditRef, ClaimAudit)                -- the audit-candidate set, KEYED BY THE STABLE IDENTITY like every other collection over audits, with key = value.ref. A set of records would have to remove BY VALUE, and the value moves: a loop re-binding and re-certification replaces the record, so the member a removal clause is looking for is no longer the member that is there. Read a ∈ S_high as membership in its range and r ∈ S_high as membership in its domain — key = value.ref holds by construction, so the two never disagree. Atomic and certificate-passing BY CONSTRUCTION, since nothing else is admitted to it. Cardinality 0 yields trivial convergence ON THE FIRST PASS — which is a SATISFIED terminal rather than a vacuous one, since every candidate that never reached S_high carries a Phase 0 record in J, certificate- or attribution-assigned. On a LOOP RETURN cardinality 0 means something different and takes a different exit: a re-certification can route the last admitted audit out after the user has already judged it, and that run's terminal is the loop's, whose account ranges over dom(Λ.antitheses). Reading cardinality 0 as one condition would send that run to a terminal emitting an empty trace
ClaimRef       = { referent: String, claim_kind: String, scope: String, text: String }
               -- claim_kind = the SEMANTIC CATEGORY of claim the source is tested as authority for (a distinct axis from an evidence-channel kind); values are protocol-local (self-contained, no shared cross-protocol enum). text = claim verbatim, used by Pattern A's cite-claim-verbatim test
               -- carried as KindBinding.label (below), which is what makes the axis a BOUND FIELD rather than something the antithesis inherits from how it happened to be worded. All four components are load-bearing for that: referent and claim_kind fix what the claim is about and in what category, scope fixes how far it reaches, and text pins the wording the challenge must confront
ProvenanceTag  = { claim: ClaimRef, evidence: VerificationPath, confidence: Float }
               -- claim-relative tag: the source's authority is recorded for the claim it authorizes, not for the source in general
VerificationPath ∈ {DirectObserved, InferredFromN, ExternalCited, ProvisionalAssumption}
FreshnessTag   = { age: Duration, horizon: Duration }
               -- currency only: a source can be fresh yet still not track the behavior its claim asserts (it documents intent with nothing enforcing the match). Freshness is necessary but not sufficient for trusting a source; the support-integrity challenge is posited per audit in Pattern A (Source Provenance Audit).
LeverageTag    = { branches: Set(Reference) }
S'             = Map(AuditRef, ProvenanceTag × FreshnessTag × LeverageTag)
               -- keyed by the AUDIT, not the source: ProvenanceTag is claim-relative already (`Claim-relative provenance`), so two audits over one source carry two provenance verdicts. FreshnessTag and LeverageTag are properties of the source and are therefore identical across sibling audits — carried per audit so one read gives the whole triple, not because they differ

Shortened here. Read the whole file on GitHub.

Signals

GitHub stars
161
Forks
15
Last commit
Sep 2026
Advanced
Catalog kind
skill
Gateway key
sublate
Source
github.com/jongwony/epistemic-protocols