Source profileQuality 93/100

jongwony/epistemic-protocols/heuresis/skills/ideate/SKILL.md

ideate

Divergent candidate-field generation before any selection is made. When the object-level candidate field is empty or has prematurely converged, infers seed vs. blank entry from the invocation utterance alone — zero entry questions. A blank entry opens an abstract frame map for user selection before any concrete idea is shown, mitigating early-example fixation and preserving ownership; a seeded entry expands directly from the user's own fragments. Generates candidates in parallel across open fram

Source repository stars
160
Declared platforms
0
Static risk flags
0
Last source update
2026-08-25
Source checked
2026-08-25

Decision brief

What it does: where it fits

Resolve an underexpanded candidate field through frame-parallel divergent generation, without ever eliminating, ranking, or selecting among the candidates it produces. Type: (CandidateFieldUnderexpanded, User, DIVERGE, IdeationRequest) → DiverseCandidateField.

Best for

    Not for

    • Unexplored-frame honesty is bounded by derivation breadth: heuresis declares unexploredframes from what it actually derived, not from every conceivable angle — it cannot prove the declared set is exhaustive, only that n…
    • Chain fixation is a stated trade-off, not a mitigated one: material folded in via a ChainRef was read by the user before the invocation utterance was written, so the ownership/diversity benefit of ideating before seeing…

    Compatibility matrix

    Platform support, with evidence labels

    PlatformStatusEvidenceWhat to check
    CodexNot declaredNo explicit evidencePortability before use
    Claude CodeNot declaredNo explicit evidencePortability before use
    CursorNot declaredNo explicit evidencePortability before use
    Gemini CLINot declaredNo explicit evidencePortability before use
    Open the compatibility checker

    Installation

    Inspect first. Install second.

    The source command is displayed only when detected. A safe inspection prompt is always available so your agent can explain every action before execution.

    Source-detected install commandSource
    npx skills add https://github.com/jongwony/epistemic-protocols --skill "heuresis/skills/ideate"
    Safe inspection promptEditorial

    Inspect the Agent Skill "ideate" from https://github.com/jongwony/epistemic-protocols/blob/fbb9ab65a7d5a7658b237e6decd22cdeb17162c8/heuresis/skills/ideate/SKILL.md at commit fbb9ab65a7d5a7658b237e6decd22cdeb17162c8. List every install step, command, network request, credential, file read/write, external action, and rollback step. Explain whether it fits my task. Do not install or execute anything until I approve.

    Workflow

    What the source asks the agent to do

    1. 01

      Phase 0: Entry Classification (Silent)

      Bind the invocation utterance per U-BINDING. Determine Entry by scanning the utterance for idea fragments: any fragment present → Seeded, extracted as seeds tagged origin=User (pre-frame fragments — each gains a frame when generation first runs); material carried by a ChainRef t…

      Bind the invocation utterance per U-BINDING. Determine Entry by scanning the utterance for idea fragments: any fragment present → Seeded, extracted as seeds tagged origin=User (pre-frame fragments — each gains a frame w…Classify ExpansionWitness alongside Entry — Empty (Blank: nothing yet), NarrowAcrossFrames (Seeded, but every seed clusters within one implied frame), PrematurelyConverged (the utterance signals settling on a single opt…Present the classification as a short relay before proceeding: what was read as Entry, its basis (quoting the utterance fragment), the ExpansionWitness, and the signals extracted from the bound input — each with its sou…
    2. 02

      Phase 1: Frame Derivation (+ Frame Map on Blank)

      Derive GenerationFrame candidates — distinct angles the candidate production will parallelize over, registered once here and extended later only for a user-named new angle. On Blank, frames are purely novel and stay abstract (no concrete example folded in yet — showing one this…

      Derive GenerationFrame candidates — distinct angles the candidate production will parallelize over, registered once here and extended later only for a user-named new angle. On Blank, frames are purely novel and stay abs…Blank path — before presenting anything, read references/blank-entry.md. It carries the frame-map presentation template, the Stop-before-generation branch, and the differential-future rationale that keeps Stop a peer op…Seeded path — no gate. Read references/seeded-entry.md before proceeding to Phase 2: it carries the expand-first rationale and why the Blank-path gate does not apply here.
    3. 03

      Phase 2: Parallel Generation (Silent)

      Generate candidates across every open frame in this round — logically parallel, no mandatory subagent dispatch (a host MAY realize this via isolated parallel agents when available; heuresis's meaning does not depend on it). No elimination, no ranking, no scoring: every candidate…

      Generate candidates across every open frame in this round — logically parallel, no mandatory subagent dispatch (a host MAY realize this via isolated parallel agents when available; heuresis's meaning does not depend on…
    4. 04

      Phase 3: Round Presentation + Continuation (Constitution)

      Present the round as text before the gate: candidates grouped by the frame they came from, each tagged with its origin; the round's newly opened frames alongside the cumulative explored set (exploredframes — every frame opened so far, this round's included) and the frames derive…

      Present the round as text before the gate: candidates grouped by the frame they came from, each tagged with its origin; the round's newly opened frames alongside the cumulative explored set (exploredframes — every frame…Every round, ahead of the gate, heuresis evaluates four things: which signals remain unaddressed, how the explored and unexplored frames differ in direction, what continuing would cost to review, and what stopping would…Then present Qround, Continue always first and Stop always second:
    5. 05

      Definition

      Heuresis (εὕρεσις): the act of finding or discovering[^1] — a dialogical act of widening a candidate field that is empty or has prematurely converged, before any selection is made. Divergent and convergent thinking are distinct cognitive operations (Runco & Acar, 2012), and no p…

      Heuresis (εὕρεσις): the act of finding or discovering[^1] — a dialogical act of widening a candidate field that is empty or has prematurely converged, before any selection is made. Divergent and convergent thinking are…[^1]: Greek εὕρεσις denotes finding, discovery, or invention/conception broadly; its later specialization as a term of rhetorical inventio is one historical narrowing among several, not the word's primary or governing s…

    Permission review

    Static risk signals and limitations

    No configured static risk pattern was detected

    This is not proof of safety. Runtime behavior, indirect dependencies, and hidden external systems are outside the static scan.

    Evidence record

    Why each signal appears

    EvidenceSourceComputedTestedEditorial
    SignalValueEvidence typeMeaning
    Quality score93/100ComputedDocumentation, specificity, maintenance, and trust rules
    Repository stars160SourceRepository attention, not individual Skill quality
    Compatibility0 platformsSourceDeclared in the catalog source record
    Usage guideautomated source guideEditorialGenerated or reviewed according to the visible evidence level

    Pinned source

    Provenance and original SKILL.md

    Repository
    jongwony/epistemic-protocols
    Skill path
    heuresis/skills/ideate/SKILL.md
    Commit
    fbb9ab65a7d5a7658b237e6decd22cdeb17162c8
    License
    MIT
    Collected
    2026-08-25
    Default branch
    main
    View the original SKILL.md

    Heuresis Protocol

    Resolve an underexpanded candidate field through frame-parallel divergent generation, without ever eliminating, ranking, or selecting among the candidates it produces. Type: (CandidateFieldUnderexpanded, User, DIVERGE, IdeationRequest) → DiverseCandidateField.

    Definition

    Heuresis (εὕρεσις): the act of finding or discovering1 — a dialogical act of widening a candidate field that is empty or has prematurely converged, before any selection is made. Divergent and convergent thinking are distinct cognitive operations (Runco & Acar, 2012), and no protocol in this catalog carries a typed guarantee to generate the object-level alternative set itself — heuresis sits at the point where the candidate field itself is thin. On a blank entry, heuresis opens an abstract frame map before showing any concrete idea — early concrete examples measurably narrow independent generation (Wadinambiarachchi et al., 2024), and ideating before seeing material preserves ownership and diversity that seeing it first does not (Qin et al., 2025). Generation is frame-parallel and never eliminates, ranks, or scores: selection is a downstream act, out of this protocol's scope.

    ── FLOW ──
    Heuresis(U) → bind(U) → classify_entry(U) → Entry →
      [Entry = Blank]         derive_frames(topic) → Qframes(frame_map) → Stop → FrameSelection →
                                 [FrameSelection = Open(F_selected)] generate(∥ F_selected) → Round₁
                                 [FrameSelection = Stop] → EarlyExit
      [Entry = Seeded(seeds)] derive_frames(seeds, topic) → generate(∥ frames; seeds fold in under their origin — utterance fragments as User) → Round₁
      present(Round) → Qround → Stop → D →
        [D = Continue(frames)] shape_frames(frames)? → generate(∥ frames) → Round → present → Qround (loop)
        [D = Stop]             assemble(Λ) → DiverseCandidateField (Λ.candidates ≠ ∅) | EarlyExit (Λ.candidates = ∅)
    -- chain note: when U names a ChainRef (a prior protocol's output), its material folds in as seeds at classify_entry —
    --   origin preserved when already tagged (a chained DiverseCandidateField), origin=User when untagged (collection
    --   output, utterance fragments); a deliberate substrate-first choice the user made, documented as a trade-off (Rule 8)
    -- park note: a deepen request at any Qround (wanting more on an already-open frame) parks into Λ.parked — it never
    --   re-opens a frame mid-loop; parked follow-ups are declared at either terminal for post-protocol chaining
    -- signal note: Phase 0 also extracts Signal(s) — concerns, weaknesses, or requirements present in the same bound
    --   utterance + named ChainRef material — tagged by source (Utterance or Chain); coverage (unaddressed(Λ)) is
    --   recomputed each round from Λ.candidates, never separately stored, and never a score, elimination reason, or
    --   ranking signal
    
    ── MORPHISM ──
    IdeationRequest
      → bind(utterance)                     -- read only the invocation utterance + any explicitly named chain reference
      → classify_entry(utterance)           -- Blank | Seeded(seeds); zero entry questions — inferred, never asked; a named
                                            --   ChainRef's material folds in here as seeds (origin preserved when already
                                            --   tagged, origin=User otherwise), so a ChainRef-bearing
                                            --   entry classifies Seeded even on a bare-topic utterance
      → derive_frames(Entry)                -- candidate GenerationFrames — the registry later rounds progressively open
      → select_frames(frames)               -- Blank only: Cognitive Partnership Move (Constitution) — frame map BEFORE any concrete candidate
      → generate(∥ open frames)             -- parallel candidate production; no elimination, no ranking, no scoring
      → present(Round)                      -- relay: candidates by frame + explored/unexplored declaration + unaddressed
                                            --   signals when any exist
      → continue_or_stop(Round)             -- user constitutive judgment; Stop available at every round AND before the first one;
                                            --   a deepen request parks (relay) rather than continuing — the loop only widens
      → assemble(field)                     -- the surviving field entire under the bound topic: every candidate origin- and frame-tagged, parked
                                            --   follow-ups and unaddressed signals declared, nothing dropped
      → DiverseCandidateField               -- happy-path terminal; the zero-candidate stop path exits as EarlyExit (FLOW)
    requires: candidate_field_underexpanded(U)   -- runtime checkpoint (Phase 0); direct /ideate invocation satisfies it
    deficit:  CandidateFieldUnderexpanded         -- activation precondition (Layer 1)
    preserves: seed_provenance(U)                 -- every seed keeps the origin it folded in with (User for the user's own
                                                  --   fragments, an already-tagged chain origin as-is) through every later
                                                  --   round; never relabeled
    invariant: Divergence over Selection          -- generation never eliminates, ranks, or converges; selection is outside this protocol
    
    ── TYPES ──
    U      = IdeationRequest: the invocation utterance, read as-is — a natural-language topic, optional idea fragments, and an
             optional explicit reference to a prior collection protocol's output
             -- Input type: the morphism reads only the bound utterance plus an explicitly named prior-output reference — a
             --   bare invocation binds the immediately preceding user message AS the utterance (U-BINDING: a one-turn
             --   binding rule, still the user's own words); beyond that binding it never scans the wider session, codebase,
             --   or rules (Euporia's reverse-trace territory, not this protocol's)
    Entry  = Blank                                -- utterance carries a topic only — no idea fragments, no named ChainRef
           | Seeded(Set(Seed))                    -- utterance carries idea fragments and/or names a ChainRef; both sources
             --   fold in as seeds at classify_entry — utterance fragments as origin=User, chained material under its
             --   existing origin tag when it carries one (origin=User only when untagged)
             -- coproduct: Blank and Seeded take DIFFERENT phase paths (frame-first gate vs. expand-first) — behavioral
             --   branching per the structural convention (natural-language definitions are for uniform-processing inputs only)
    Seed   = { content: String, origin ∈ {User, AI} }   -- a pre-frame fragment; origin=User for utterance-borne fragments and
             --   untagged chained material, while chained material already carrying an origin tag keeps it (a chained
             --   DiverseCandidateField's candidates re-seed under their existing tags — an origin=AI candidate is never
             --   relabeled User by re-chaining). Carries no frame yet — frames do not exist until Phase 1 derives them
             --   (GenerationFrames are invocation-local, so a chained candidate's old frame is dropped, not preserved).
             --   Promoted to Candidate on the first generation pass, gaining the frame it lands under (origin travels unchanged)
    ExpansionWitness ∈ {Empty, NarrowAcrossFrames, PrematurelyConverged} ∪ Emergent(T)
             -- classification of WHY the candidate field is underexpanded; cited as Phase 0 relay basis. Sub-level to
             --   CandidateFieldUnderexpanded — NOT itself a top-level deficit, and never gates activation (User-initiated:
             --   direct /ideate invocation IS the activation)
    ChainRef = an explicit reference the utterance itself names to a prior collection protocol's output (e.g. "using what
             --   /inquire just gathered") -- the ONLY substrate-adjacent material this protocol reads; absent a named
             --   reference, no external material enters (Euporia boundary: no substrate scan, no reverse-traced coordinates)
    Signal = { content: String, source ∈ {Utterance, Chain} }
             -- a concern, weakness, or requirement present in the bound input only — extracted at Phase 0 alongside Entry,
             --   from the same bound utterance plus any explicitly named ChainRef material (never a wider scan); source
             --   records which of the two it came from. Never a quality score, elimination reason, or ranking signal
             --   (coverage — unaddressed(Λ), defined in CONVERGENCE — is a binary observation, not a judgment on any candidate)
    Candidate = { content: String, frame: Frame, origin ∈ {User, AI} }
             -- a generated idea item (raw material for divergence) — NOT a selection-ready alternative; carries no score,
             --   rank, or preference signal
    Frame  = GenerationFrame { label: String, angle: String }
             -- a partition for parallel candidate production; distinct from Prothesis's
             --   FramedInquiry lens — GenerationFrame carries no substrate_need, no per-perspective directive, no
             --   epistemic-perspective machinery. It organizes divergent generation only — derived at Phase 1, extended
             --   only for a user-named new angle, opened progressively across rounds (never re-derived by a bare
             --   continue) — and is never handed off as a framed inquiry object
    Round  = { frames_opened: Set(Frame), candidates: Set(Candidate) }   -- one generation pass; frames_opened is exact —
             --   every pass opens only not-yet-open frames (no mid-loop re-entry; a deepen wish parks instead)
    Qframes = frame-map presentation (Blank path only) — abstract frames, no concrete candidate yet [Tool: Constitution interaction]
    FrameSelection ∈ {Open(frames: Set(Frame)), Stop}   -- Qframes answer type, Open/Stop differential-future rationale: read references/blank-entry.md
    Qround = per-round presentation: candidates by frame + explored/unexplored (direction contrast) + parked-so-far + unaddressed-signal declaration, then continuation ask, Continue always first and Stop always second [Tool: Constitution interaction]
    D      = ContinuationAnswer ∈ {Continue(frames: Set(Frame)), Stop}
             -- Continue's frames are drawn from the declared unexplored set, or are user-named frames outside the declared
             --   set (free response — type-preserving materialization of Continue, not a new constructor). An already-open
             --   frame is NOT a Continue target: wanting more on it is a park request (→ ParkedFollowUp) — mid-loop
             --   deepening narrows the very field the loop exists to widen, so depth belongs to a later invocation chaining
             --   on the assembled field. A Continue naming no frames defaults to the declared unexplored set; when none
             --   remain, Continue has no default target — it materializes only with a user-named new angle (the gate's
             --   option text renders that state), and a Continue that materializes no target re-presents the round
             --   question rather than entering generation — an empty pass never runs. Stop is available at every Qround exactly as at
             --   Qframes — the user's stop is the constitutive act that bounds the field, not a pre-convergence abandonment
    ParkedFollowUp = { frame: Frame, note: Optional(String) }
             -- a mid-loop wish to go deeper on an already-open frame, captured as a typed follow-up instead of narrowing
             --   the live loop; held in Λ.parked and declared at either terminal — its durable externalization (a task
             --   record, an issue) is a host-side handoff after the protocol ends, so the loop itself stays side-effect-free
    DiverseCandidateField = {
      topic: String,
      candidates: Set(Candidate { content, frame, origin }),
      explored_frames: Set(Frame),
      unexplored_frames: Set(Frame),
      parked: Set(ParkedFollowUp),
      unaddressed_signals: Set(Signal)
    }
             -- "diverse" = frame-distributed, never scored/ranked/optimized; candidates ≠ ∅ always holds here (see EarlyExit
             --   for the empty case). unaddressed_signals holds only signals with no responding candidate at Stop — an
             --   addressed signal needs no field of its own: the candidates heuresis read as answering it are already in
             --   the field, and judging that fit is the consumer's own act at selection time.
             --   The field is the propagation contract: endpoint-neutral and complete for unfolding what it carries
             --   (an issue tracker, /preview, a chained /ideate) — topic carries the bound request; the one declared
             --   boundary is the full signal take/discard ledger, which stays in session text (see Known Limitations);
             --   endpoint-specific unfolding is downstream scope, not heuresis's
    EarlyExit = { frames_offered: Set(Frame), parked: Set(ParkedFollowUp), unaddressed_signals: Set(Signal), reason: Optional(String) }
             -- the typed terminal for a stop (Qframes Stop, a Qround Stop after passes that produced nothing)
             --   that fires while no candidate exists — an empty
             --   field is never mislabeled DiverseCandidateField; frames_offered declares what was on the table even
             --   though nothing was generated, unaddressed_signals holds every signal extracted (none had a candidate to
             --   respond to it), and any parked follow-ups are declared alongside — nothing is silently dropped
    
    ── U-BINDING ──
    bind(U) = explicit_arg ∪ colocated_expr ∪ prev_user_turn ∪ chain_ref
    Priority: explicit_arg > colocated_expr > prev_user_turn > chain_ref
             -- prev_user_turn binds only on a bare invocation: the immediately preceding user message BECOMES the utterance
             --   (one turn, the user's own words) — a binding rule for U, not a license to scan session history
             -- chain_ref, when named, folds its material in as seeds (origin preserved when tagged, User otherwise) — it never substitutes for the topic itself
    
    /ideate "topic or fragments"               → U = "topic or fragments"
    /ideate (alone)                            → U = previous user message
    "give me some ideas ... /ideate"           → U = text before trigger
    "using what /inquire just found, /ideate"  → U also carries the named ChainRef; its material folds in as origin=User seeds (untagged collection output; a chained DiverseCandidateField would keep its candidates' own origin tags)
    
    ── PHASE TRANSITIONS ──
    Phase 0: U → bind(U) → classify_entry(U) → Entry ⊗ ExpansionWitness; Λ.topic := topic(U); Λ.signals := extract_signals(U)   -- silent; zero entry questions; topic(U) records the bound request in mode state, so assemble(Λ) has a formal source for the field's topic; extract_signals(U) reads the same bound utterance + named ChainRef only (Rule 7 boundary) and tags each Signal's source
           classify_relay(Entry, ExpansionWitness) → TextPresent+Proceed          -- states the inferred entry + its basis + the extracted signals (source-tagged) before proceeding; relay, not a gate — every signal enters session text once, at extraction
    Phase 1: Entry → Λ.frames_candidate := derive_frames(Entry)   -- registered into mode state at derivation, so the frames_open ⊆ frames_candidate invariant is checkable from the first pass
           [Entry = Blank] present(frame map) → Qframes → Stop → FrameSelection   [Tool]   -- read references/blank-entry.md
             [FrameSelection = Open(F_selected)] → Phase 2 with F_selected
             [FrameSelection = Stop] → EarlyExit(frames_offered := Λ.frames_candidate, parked := Λ.parked, unaddressed_signals := Λ.signals)   -- stop before any candidate exists; Λ.parked is necessarily ∅ here (parks originate at Qround) — passed explicitly for uniform construction; every extracted signal is unaddressed since no candidate exists yet
           [Entry = Seeded(seeds)] → Phase 2 directly with Λ.frames_candidate (no gate — expand-first)   -- read references/seeded-entry.md
    Phase 2: F_open → generate(∥ over F_open) → Round(candidates, frames_opened := F_open)   -- no elimination, no ranking; on the first pass of a Seeded entry, seeds promote to Candidates under the frame each lands in, each keeping its seed origin (utterance fragments User); generated candidates carry origin=AI; F_open ∩ Λ.frames_open = ∅ (Phase 3 admits only unexplored or newly registered frames), so frames_opened records genuinely new openings
           Λ.candidates := Λ.candidates ∪ Round.candidates, Λ.frames_open := Λ.frames_open ∪ F_open, Λ.rounds := append(Λ.rounds, Round)   -- state absorbed BEFORE Phase 3 presents: every Qround guard (the Stop branches' Λ.candidates test) reads post-round state, never stale
    Phase 3: Round → present(Round: candidates by frame, explored_frames, unexplored_frames, parked so far, unaddressed(Λ)) → Qround → Stop → D   [Tool]
           [park request — the response asks for more on an already-open frame] Λ.parked := Λ.parked ∪ {ParkedFollowUp(frame, note)} — relay the parking (extension); a response carrying only a park leaves the continuation question open, so Qround is re-presented with the park acknowledged
           [D = Continue(frames: F'), F' ≠ ∅, F' ⊆ Λ.frames_unexplored] → Phase 2 with F' (open unexplored — no new derivation; an already-open frame is never a Continue target)
           [D = Continue(frames: F'), F' ⊄ Λ.frames_candidate] Fₙ := shape_frames(F' \ Λ.frames_candidate); Λ.frames_candidate := Λ.frames_candidate ∪ Fₙ → Phase 2 with (F' ∩ Λ.frames_unexplored) ∪ Fₙ (user-named new angle — shape_frames shapes it into registered frames before generation, a distinct operation from Phase 1's derive_frames: its domain is Set(Frame), not Entry; type-preserving materialization of Continue; any already-open frame the response also named routes to the park branch, never back into F_open)
             -- a Continue naming no frames defaults F' := Λ.frames_unexplored; when none remain, Continue has no default target — it materializes only with a user-named new angle (the gate's option text renders that state), and a bare Continue never derives new frames
           [D = Continue(frames: ∅) — the default resolved to an empty set: no unexplored frame remains and no new angle was named] → re-present Qround with that state rendered — Continue materialized no target, so no generation pass runs (F' ≠ ∅ guards Phase 2 entry; an empty round never exists)
           [D = Stop, Λ.candidates ≠ ∅] → assemble(Λ) → DiverseCandidateField(topic := Λ.topic, candidates := Λ.candidates, explored_frames := Λ.frames_open, unexplored_frames := Λ.frames_unexplored, parked := Λ.parked, unaddressed_signals := unaddressed(Λ))   -- every field sourced from Λ; explored_frames is the chain-contract name of Λ.frames_open
           [D = Stop, Λ.candidates = ∅] → EarlyExit(frames_offered := Λ.frames_candidate, parked := Λ.parked, unaddressed_signals := Λ.signals)   -- a completed pass can yield nothing; honest stop typing routes an empty field to EarlyExit, never DiverseCandidateField; with no candidates, every extracted signal is unaddressed
    
    ── LOOP ──
    Round cadence: Phase 2 (generate, ∥ over open frames) → Phase 3 (present + Qround). Before every Qround, heuresis
      evaluates four things: which signals remain unaddressed, how the explored and unexplored frames differ in direction
      (not just their labels), what continuing would cost to review, and what stopping would finalize and preserve — the
      evaluation is fixed every round, but what renders adapts to what carries decision-relevant content this round
      (empty or unchanged detail may compress), except unaddressed signals, which are always surfaced whenever any exist.
      A Continue answer triggers
      shape_frames only when the user names a wholly new angle (shaped into registered frames before generation); opening
      declared-unexplored frames returns directly to Phase 2 — then back to Phase 3. A deepen request never re-enters
      Phase 2 mid-loop: it parks (Λ.parked) for a later invocation chaining on the assembled field — depth is downstream,
      width is this loop's whole business.
      No fixed round count and no quota: heuresis tracks no target to converge toward; the loop continues until the user's own Stop.
    Novelty relay (optional, extension): at any Qround, heuresis MAY note as basis-cited context whether recent rounds read
      as producing candidates closer to earlier ones (novelty has not yet declined further, or has) — informational only,
      sits in the pre-gate text, describes state only, and never reorders the Continue/Stop options or blocks or discourages Stop.
    Continue until: DiverseCandidateField (user Stop with ≥1 candidate already generated) OR EarlyExit (Stop
      while no candidate exists — the Blank frame map declined, or a Stop
      after passes that produced nothing).
    Convergence evidence: at DiverseCandidateField, present the transformation trace — the bound topic, then for each
      opened frame, the candidates it produced with their origin tags, plus the declared unexplored frames, parked
      follow-ups, and unaddressed signals. At
      EarlyExit, present the frames that were derived and offered, none of which yielded a candidate, plus any parked
      follow-ups and unaddressed signals (every extracted signal, since none was addressed). Demonstrated, not asserted —
      and the trace materializes the full result type, nothing more, nothing less.
    
    ── CONVERGENCE ──
    resolved(Λ) = user_stops(Λ)   -- the user's own Stop IS the completion predicate — not a separate judgment layered on
      top of an already-built object; this is what keeps termination-at-any-time from reading as pre-convergence abandonment
    unaddressed(Λ) = { s ∈ Λ.signals | no c ∈ Λ.candidates responds to s }   -- derived, binary, source-traceable coverage
      observation, recomputed every round from current Λ.candidates — never a stored partition, and never a quality score,
      elimination reason, or ranking signal; a candidate "responding" to a signal is a semantic judgment heuresis makes at
      presentation time — a session-local reading remade from scratch each round (Λ.signals never shrinks, so a reading
      can reverse; nothing is sticky), never stored as a signal-to-candidate mapping, and never itself a score or rank on
      the candidate. The contract carries only the derived set as it stands at Stop — whether a candidate truly answers a
      signal is the consumer's own judgment at selection time, downstream of this protocol
    result equations:
      DiverseCandidateField ⇔ resolved(Λ) ∧ Λ.candidates ≠ ∅
      EarlyExit             ⇔ resolved(Λ) ∧ Λ.candidates = ∅
                              -- a typed terminal, never a DiverseCandidateField mislabeled empty; frames_offered and
                              --   unaddressed_signals (every extracted signal, since none was addressed) declare what was
                              --   on the table so nothing is silently dropped
    cleanup: not applicable — heuresis holds no side-effect state (no team spawned, no file artifact); the
      user_withdraw cleanup tier does not apply here — only Normal convergence (the user's own Stop) does.
      Parked follow-ups live in Λ and are declared at the terminal — their
      durable externalization (a task record, an issue) is a host-side handoff AFTER convergence, per the substrate
      boundary, so no cleanup obligation ever arises mid-loop
    framing readout: the surfaced state names the work in play (which frames are open, how many candidates so far, what
      remains unexplored, what is parked, what signals remain unaddressed) — never a completion tally against a target
      round count, since heuresis tracks no such target
    
    ── TOOL GROUNDING ──
    -- Realization: Constitution → TextPresent+Stop; Extension → TextPresent+Proceed
    Phase 0 bind           (sense)        → Internal analysis (parse the invocation utterance + any explicitly named chain reference; no external tool, no substrate scan)
    Phase 0 classify_relay  (extension)   → TextPresent+Proceed (states the inferred Entry — Blank or Seeded — and its basis, quoting the utterance fragment, plus the extracted signals with their source tags; relay, not a gate — zero entry questions)
    Phase 0 extract_signals (sense)       → Internal analysis (Signals — concerns, weaknesses, requirements — read from the same bound utterance + named ChainRef only, same Rule 7 boundary as bind; tagged Utterance or Chain by source; surfaced once via the classify_relay emission; feeds unaddressed(Λ) each round, never scored or ranked)
    Phase 1 derive_frames   (sense)       → Internal analysis (candidate GenerationFrames, registered as Λ.frames_candidate; seed-anchored + novel on Seeded, purely novel and abstract on Blank)
    Phase 3 shape_frames    (sense)       → Internal analysis (a user-named new angle shaped into registered GenerationFrames, extending Λ.frames_candidate — distinct from Phase 1's derive_frames: domain Set(Frame), not Entry)
    Phase 1 Qframes         (constitution) → present (Blank path only; conditional: fires when Entry = Blank; multi-select frame map presented BEFORE any concrete candidate, plus the Stop path; read references/blank-entry.md before presenting — it carries the template and the Stop-branch rationale)
    Phase 2 generate        (sense)       → Internal generation (logical topology: parallel over open frames — no mandatory subagent dispatch; a host MAY realize this via isolated parallel agents when available, but heuresis's meaning is independent of that realization; no elimination, no ranking, no scoring)
    Phase 3 present         (extension)   → TextPresent+Proceed (round relay: candidates grouped by frame with origin tags, explored/unexplored frame declaration with direction contrast, unaddressed signals when any exist, plus the fixed four-part decision-delta evaluation; precedes the gate)
    Phase 3 Qround          (constitution) → present (mandatory every round; fixed order Continue=1, Stop=2 at every presentation and re-presentation; Continue — open more unexplored or name a new frame — or Stop; a deepen request parks rather than continuing)
    Phase 3 park            (extension)   → Internal state update + TextPresent+Proceed (a request for more on an already-open frame parks as ParkedFollowUp — relay, basis: the user's own request quoted; declared at either terminal; durable externalization is a host-side handoff after the protocol ends)
    Λ                       (track)       → Internal state update (topic records at Phase 0 bind; signals extracted at Phase 0 and fixed thereafter — never re-scanned, never removed; frames_candidate registers at Phase 1 and extends only on a user-named new angle; candidates, rounds, frames_open, parked accumulate; frames_unexplored and unaddressed(Λ) are both derived — frames_candidate minus frames_open, and signals with no responding candidate, respectively — recomputed each round, never stored as a separate partition; a candidate is never removed or relabeled once tagged with origin)
    converge                (extension)   → TextPresent+Proceed (DiverseCandidateField: transformation trace — the bound topic + per opened frame, its candidates + declared unexplored frames + parked follow-ups + unaddressed signals; EarlyExit: the frames offered, none of which yielded a candidate, + parked follow-ups + unaddressed signals; either way the parked set's durable externalization hands off to the host after the trace)
    
    ── MODE STATE ──
    Λ = { phase: Phase, entry: Option(Entry), witness: Option(ExpansionWitness),
          topic: Option(String),             -- the bound request, recorded at Phase 0; assemble(Λ) carries it into
                                             --   DiverseCandidateField so the chain contract needs no source outside Λ
          chain_ref: Option(ChainRef),
          signals: Set(Signal),              -- extracted at Phase 0 from the same bound utterance + named ChainRef only;
                                             --   fixed thereafter — never re-scanned from a wider source, never removed; unaddressed(Λ)
                                             --   (defined in CONVERGENCE) derives from this set and Λ.candidates, not
                                             --   stored separately
          frames_candidate: Set(Frame),
          frames_open: Set(Frame),           -- invariant: frames_open ⊆ frames_candidate — every opened frame is registered
                                             --   before generation (the Phase 3 new-angle branch registers Fₙ first);
                                             --   assembled as explored_frames in DiverseCandidateField
          frames_unexplored: Set(Frame),     -- derived, not accumulated: frames_candidate \ frames_open — opening a frame
                                             --   removes it here, so no frame is ever reported explored AND unexplored
          candidates: Set(Candidate),        -- accumulate-only across rounds; origin never relabeled once tagged
          rounds: List(Round),
          parked: Set(ParkedFollowUp),       -- accumulate-only; deepen wishes captured mid-loop, declared at either terminal
          active: Bool, cause_tag: String }
    Phase ∈ {0, 1, 2, 3}
    
    ── COMPOSITION ──
    *: product — (D₁ × D₂) → (R₁ × R₂). Candidate-field resolution emergent via session context.
    

    Core Principle

    Divergence over Selection: a candidate is raw material, not a selection-ready alternative — heuresis never discards, scores, ranks, or converges on one. This is deliberate: AI assistance can raise inter-output similarity (Doshi & Hauser, 2024), and premature narrowing during generation is exactly the failure mode a divergence protocol exists to avoid. Later candidates in a session tend to be more original than earlier ones — the serial-order effect (Beaty & Silvia, 2012) — a reason heuresis carries no fixed round count or quota and lets the user's own stop bound the field, rather than the protocol declaring itself done. "No elimination" governs candidates, not conversation: a stance of no criticism at all is not universally optimal for idea quality (Nemeth et al., 2004), so heuresis does not suppress generative challenge or clarifying pushback during a round — it only forbids discarding or ranking the candidates a round produces. Depth is likewise out of the live loop: a wish to go deeper on an open frame parks as a typed follow-up rather than turning the loop inward — mid-loop deepening narrows the very field the loop exists to widen, and the parked follow-up chains into a later invocation on the assembled field.

    Mode Activation

    Activation

    Command invocation activates the protocol. heuresis is User-initiated only — whether a candidate field needs widening is the user's own judgment about their own creative process, not something the AI infers and auto-activates on.

    Activation layers:

    • Layer 1 (User-invocable): /ideate slash command or description-matching input. Always available.
    • Layer 2: No AI-guided activation. heuresis MAY observe in passing, in prose, that a candidate field looks narrow — that observation is never an activation.

    Priority

    Supersedes: Direct-answer patterns that jump straight to a single recommended idea or a pre-narrowed shortlist (Divergent generation must run — and the user's own Stop must fire — before any selection or recommendation)

    Retained: Safety boundaries, tool restrictions, user explicit instructions

    Action: On a Blank entry, present the frame map via Cognitive Partnership Move (Constitution) before any concrete candidate is generated. Every round, present the round's candidates, the explored/unexplored frame declaration, and any signals still unaddressed, then present Qround (Continue always first, Stop always second) via Cognitive Partnership Move (Constitution). Stop is a first-class answer at every gate, available from the very first one.

    • Heuresis completes (converges to DiverseCandidateField or EarlyExit) before downstream selection work begins
    • Loaded instructions resume after convergence.

    Trigger Signals

    SignalDetection
    Pure topic, no attached ideasUtterance names a subject with no idea fragments — a Blank entry
    One idea offered, more requestedUtterance carries exactly one fragment and asks for "more," "other angles," "what else"
    Fixation languageUtterance names a single option as settled while the underlying goal stays open ("I think X is it, but let me see other angles")
    Direct request for options"give me some ideas / directions / angles" with no existing candidate field in view

    Skip:

    • Two or more candidates already exist and their futures just need to be seen before choosing
    • The material to ideate from lives outside this conversation (codebase, rules, past sessions) — heuresis itself never scans; run a collection pass first and name its output in the /ideate invocation
    • The ask is analytical lenses on a fixed inquiry, not idea generation
    • The ask is verifying understanding of completed work

    Mode Deactivation

    TriggerEffect
    Stop at Qframes (Blank, before any candidate)EarlyExit — frames_offered + every extracted signal (all unaddressed) declared, nothing generated
    Stop at QroundDiverseCandidateField when ≥1 candidate exists — candidates + explored/unexplored frames + parked follow-ups + unaddressed signals; EarlyExit when completed passes produced none

    Protocol

    Phase 0: Entry Classification (Silent)

    Bind the invocation utterance per U-BINDING. Determine Entry by scanning the utterance for idea fragments: any fragment present → Seeded, extracted as seeds tagged origin=User (pre-frame fragments — each gains a frame when generation first runs); material carried by a ChainRef the utterance names folds in the same way at this step — keeping any origin tag it already carries (a chained candidate field's candidates re-seed under their existing tags), typing origin=User only when untagged — so a ChainRef-bearing invocation is Seeded even when its utterance is a bare topic; a bare topic with no fragments and no named ChainRefBlank. This is a relay, not a gate — Entry is inferred, never asked (zero entry questions).

    Classify ExpansionWitness alongside EntryEmpty (Blank: nothing yet), NarrowAcrossFrames (Seeded, but every seed clusters within one implied frame), PrematurelyConverged (the utterance signals settling on a single option while the underlying goal stays open), or an emergent case outside the named three (Emergent(T): name the observed pattern ad hoc — the named types are working hypotheses, not an exhaustive set) — cited as basis in the relay text that follows, and used to shape frame derivation (a PrematurelyConverged witness pushes frame derivation toward angles distant from the existing single idea).

    Present the classification as a short relay before proceeding: what was read as Entry, its basis (quoting the utterance fragment), the ExpansionWitness, and the signals extracted from the bound input — each with its source tag. No user response is requested here — proceed directly to Phase 1.

    Alongside Entry, extract Signals from the same bound input — concerns, weaknesses, or requirements the utterance or a named chain reference already carries, each tagged by which of the two it came from. Signals enter mode state once, at this step, and stay fixed for the rest of the session — never re-scanned from anywhere wider; whether a generated candidate later responds to one is a derived observation recomputed each round, not something Phase 0 assigns. The extraction itself is internal, but its result is not withheld: the entry relay above states the extracted signals alongside the classification, so every signal enters session text once, at extraction — later Qrounds then re-state only those still unaddressed.

    Scope restriction: Phase 0 reads only the invocation utterance plus a chain reference the utterance explicitly names (ChainRef — e.g. "using what /inquire just gathered") — the same boundary governs Signal extraction. A bare /ideate binds the immediately preceding user message as that utterance (U-BINDING) — a one-turn binding, not a session scan. Beyond the bound utterance it never scans the wider session, codebase, or rules — that is Euporia's territory, not this protocol's.

    Phase 1: Frame Derivation (+ Frame Map on Blank)

    Derive GenerationFrame candidates — distinct angles the candidate production will parallelize over, registered once here and extended later only for a user-named new angle. On Blank, frames are purely novel and stay abstract (no concrete example folded in yet — showing one this early risks anchoring the user's own generation before it starts). On Seeded, frames are derived from the seeds' implied angle(s) plus genuinely novel ones, so the round doesn't just restate what the user already has.

    Blank path — before presenting anything, read references/blank-entry.md. It carries the frame-map presentation template, the Stop-before-generation branch, and the differential-future rationale that keeps Stop a peer option rather than a meta-action. This reference binds only on a Blank entry — on Seeded, it does not bind and is not read; the Seeded path instead reads references/seeded-entry.md.

    Seeded path — no gate. Read references/seeded-entry.md before proceeding to Phase 2: it carries the expand-first rationale and why the Blank-path gate does not apply here.

    Phase 2: Parallel Generation (Silent)

    Generate candidates across every open frame in this round — logically parallel, no mandatory subagent dispatch (a host MAY realize this via isolated parallel agents when available; heuresis's meaning does not depend on it). No elimination, no ranking, no scoring: every candidate produced survives into the round. On a Seeded entry, read references/seeded-entry.md before this first pass runs — it carries the seed-promotion and origin-tagging law for that pass.

    Phase 3: Round Presentation + Continuation (Constitution)

    Present the round as text before the gate: candidates grouped by the frame they came from, each tagged with its origin; the round's newly opened frames alongside the cumulative explored set (explored_frames — every frame opened so far, this round's included) and the frames derived but not yet opened (unexplored_frames) — described by the direction each side actually covers, not just their labels, so the contrast between what's already open and what would open next is visible; any follow-ups parked so far; and, whenever any exist, the signals still unaddressed — a concern, weakness, or requirement from the bound input that no generated candidate yet responds to, stated plainly ("no candidate yet responds to X").

    Every round, ahead of the gate, heuresis evaluates four things: which signals remain unaddressed, how the explored and unexplored frames differ in direction, what continuing would cost to review, and what stopping would finalize and preserve. This evaluation runs every round; what actually renders adapts to what carries decision-relevant content this round — unchanged or empty detail may compress — except unaddressed signals, which are always stated whenever any exist, since their omission is the exact gap this readout closes. heuresis MAY also note, as basis-cited context only, whether recent rounds read as producing candidates closer to earlier ones — this describes the field's state, never ranks or reorders the options below, and never blocks or discourages Stop.

    Then present Qround, Continue always first and Stop always second:

    Options:
    1. **Continue** — open [the named unexplored frames, or a new angle you name]; [when the widening aims at a signal still unaddressed] this pass aims at [the signal] — no candidate responds to it yet, and generation aiming there doesn't guarantee one lands; another pass follows — whatever it produces is added to the field you review, and a completed pass may produce none; wanting more on an already-open frame is a park, not a continue
    2. **Stop** — [when candidates exist] the field finalizes as-is: the topic, the candidates under the frames explored, unexplored frames, parked follow-ups, and any signals still unaddressed all travel into the result; selection and prioritization begin downstream, outside this loop
       [when completed passes produced none] end here with nothing generated — a typed early exit; the frames offered, any parked follow-ups, and every signal extracted are declared, not dropped
    

    Continue may name specific unexplored frames or introduce a frame the user names outright (a free response — type-preserving materialization of Continue, not a new constructor); a Continue naming nothing defaults to the declared unexplored frames — when none remain, Continue has no default target and materializes only with a user-named new angle, and the option text renders that state; a continue that materializes no target re-presents the question instead of running an empty pass. A request for more on an already-open frame is a park, not a continuation — stated in the Continue line itself, not as a postscript: it is recorded as a typed follow-up (ParkedFollowUp — a relay quoting the user's own request), acknowledged in the next presentation, and declared at either terminal; the deepening itself belongs to a later invocation chaining on the assembled field, because turning the live loop inward narrows the very field it exists to widen. A response carrying only a park leaves the continuation question open — Qround is re-presented with the park acknowledged and the field status recomputed from current state, never a bare contextless choice. Loop back through shape_frames (only when a user-named new angle needs shaping into a registered frame) or directly to Phase 2 (opening declared-unexplored frames). Stop is available here exactly as it is at Qframes — the user's own Stop, at any round, is what bounds the field: assemble the mode state into DiverseCandidateField when at least one candidate exists, with the topic, explored_frames, unexplored_frames, parked, and unaddressed_signals all declared; a Stop after passes that produced nothing returns EarlyExit — honest stop typing, an empty field is never dressed up as the resolution type, and every extracted signal counts as unaddressed on that path since no candidate exists to respond to any of them. The Stop option's description renders whichever of these two futures is live at gate time — the gate never promises a candidate field that does not exist.

    Continue is always option 1 and Stop is always option 2, at this presentation and at every re-presentation — the order never changes and never encodes an AI recommendation. Any leaning heuresis forms (from a novelty trend, or from how the field's coverage sits) surfaces in the pre-gate text only as the basis-cited state that grounds it — what recent rounds produced, which signals still lack responses — never as the direction it points: no wording that calls continuing or stopping the favored move, and no reordering.

    Adversarial Guards

    • substrate-creep: heuresis starts reading the codebase, rules, or past sessions to seed generation instead of the invocation utterance alone. Guard: bind() is scoped to the utterance plus an explicitly named ChainRef only (Rule 7) — any wider read belongs to a collection protocol run first and chained in, never to heuresis itself.
    • lens-conflation: a GenerationFrame gets treated as an analytical perspective — carrying a substrate need, a per-perspective directive, or getting handed off as a framed inquiry object. Guard: GenerationFrame is a partition for parallel production only (Rule 9); that machinery belongs to /frame, not here.
    • selection-creep: candidates start getting scored, ranked, starred, or silently dropped between rounds. Guard: generation never eliminates (Rule 3); a candidate that exists stays in Λ.candidates until the user stops — selection is downstream, out of scope.
    • depth-creep: the loop starts generating "a few more" inside an already-open frame instead of parking the wish. Guard: an already-open frame is never a Continue target (Rule 17) — the wish parks as a typed follow-up and the loop stays wide; depth chains downstream on the assembled field.
    • coverage-creep: unaddressed-signal coverage starts getting used to justify discarding, deprioritizing, or ranking a candidate, or a signal's addressed/unaddressed status gets read as a quality judgment on the candidates that do or don't respond to it. Guard: unaddressed(Λ) is a derived, binary, source-traceable observation only (Rule 19) — it says whether a candidate exists, never whether it's good; the same no-scoring guard that governs generation itself (Rule 3) covers this side door too.

    Known Limitations

    Unexplored-frame honesty is bounded by derivation breadth: heuresis declares unexplored_frames from what it actually derived, not from every conceivable angle — it cannot prove the declared set is exhaustive, only that nothing derived was silently withheld.

    Chain fixation is a stated trade-off, not a mitigated one: material folded in via a ChainRef was read by the user before the invocation utterance was written, so the ownership/diversity benefit of ideating before seeing material does not hold on that path. heuresis documents this; it adds no countermeasure.

    Signal extraction is bounded by what heuresis reads, not a formal completeness guarantee: heuresis surfaces the concerns, weaknesses, and requirements it identifies within the bound utterance and named chain material — it cannot prove every signal present in that input was found, only that nothing it did find was silently dropped from the presentation or the trace.

    Signal extraction is an entry-time act, not a session-long listener: signals enter the field once, at Phase 0, from the bound input. A concern first voiced in a later turn of the loop is not folded into Λ.signals and will not appear in unaddressed_signals — it stays visible in session text, and carrying it forward is the consumer's own move: re-voice it where it should land, a later invocation chaining on the assembled field, or a collection protocol run first whose output the next invocation names. The contract carries what entered at the boundary; what arises mid-loop routes at the consumer's discretion.

    The contract carries the final coverage snapshot, not the full signal ledger: unaddressed_signals is heuresis's last session-local coverage reading, frozen at Stop — a signal read as answered travels nowhere in the result, so a misread one leaves no trace in the contract (an utterance-sourced signal stays re-derivable from the bound request in topic; chained material folds in as seeds rather than traveling as text, so a chain-sourced signal read as answered is not recoverable from the field). This is a leanness trade-off, not an omission: the brainstorming contract stays light, and take/discard clarity over the full extracted set is downstream scope — every extraction and per-round readout remains visible in session text (the full extracted set at the entry relay, the still-unaddressed each round), from which a consumer who wants the full take/discard record can assemble it.

    UX Safeguards

    RuleStructureEffect
    Zero entry questionsEntry inferred from the utterance alone (classify_entry, extension)No interrogation gate before generation begins
    Frame-first on BlankQframes presents abstract frames before any concrete candidateMitigates early-example fixation; preserves ownership
    No elimination, no rankinggenerate() never discards, scores, or ranks a candidateSelection stays out of scope; downstream protocols own it
    Provenance preservedEvery Candidate carries origin ∈ {User, AI}, never relabeledFixation and ownership stay observable after the fact
    Stop at any timeStop is a peer answer at Qframes and every QroundThe user's own stop bounds the field; not a target-count completion
    Depth parks, never hijacksA deepen wish on an open frame parks as ParkedFollowUp, declared at either terminalMid-loop divergence stays wide; depth chains downstream on the assembled field
    Honest stop typingΛ.candidates = ∅ at Stop → EarlyExit, never DiverseCandidateFieldAn empty field is never dressed up as the resolution type
    Euporia boundarybind() reads only the utterance + a named ChainRefNo substrate scan, no reverse-traced coordinates
    Chain trade-off documentedChained material folds in as seeds with no mitigation — origin preserved when tagged, origin=User when untaggedThe user's deliberate substrate-first choice stays honest, undisguised — and AI-generated material never masquerades as the user's own
    Vendor-neutral parallelismgenerate()'s topology names no required toolMeaning independent of host realization; provenance stays {User, AI}
    Coverage observed, never scoredunaddressed(Λ) is derived and binary, never a quality/elimination/ranking signalCoverage cannot become covert candidate scoring
    Decision-delta before every gateFixed four-part evaluation, adaptively emitted, placement-split between pre-gate text and option linesThe differential future between Continue and Stop is visible before the choice, not reconstructed after
    Fixed option orderContinue = 1, Stop = 2 at every presentation and re-presentationAI leaning never becomes a positional nudge

    Rules

    1. User-initiated, zero entry questions: /ideate activates only on direct invocation (Layer 1); Entry (Blank vs. Seeded) is inferred from the utterance alone — never asked. Phase 0's classify_entry is Extension/relay, not a gate.
    2. Recognition over Recall, frame-first before concrete: On a Blank entry, read references/blank-entry.md before Phase 1 presents anything — it carries the frame-first requirement and the ownership/fixation rationale behind it.
    3. No elimination, no ranking: Generation never discards, scores, ranks, or optimizes a candidate. A "candidate" in this type is a generated idea item (raw material) — never a selection-ready alternative. Selection is out of scope; it belongs downstream — seeing candidate futures before choosing (/preview) or the user's own direct judgment, with the resulting decision auditable for unnoticed gaps (/gap — an audit of the decision made, not a selector among candidates).
    4. Provenance preserved: Every candidate carries origin ∈ {User, AI}, assigned once and never relabeled. User-supplied seeds keep origin=User through every subsequent round, and chained material that arrives already origin-tagged keeps its tag — re-chaining a candidate field never converts origin=AI into origin=User.
    5. Termination is the user's constitutive act, at any time: Stop is available at every gate, including before any candidate is ever generated. The user's stop bounds the field — it is not a pre-convergence abandonment; the completion predicate for DiverseCandidateField IS the user's own stop (with candidates ≠ ∅ at that moment). heuresis MAY relay that recent rounds show declining novelty as basis-cited context; this NEVER blocks or discourages Stop.
    6. Honest stop typing: A stop while ≥1 candidate exists converges to DiverseCandidateField. A stop while none exist — declining the Blank frame map, or a Stop after completed passes that produced nothing — returns EarlyExit — the frames that were offered are declared, never silently dropped, and an empty field is never mislabeled DiverseCandidateField.
    7. Euporia boundary — utterance-only input: heuresis reads only what the invocation carries — the utterance itself, plus a prior protocol's output the user explicitly names (a chain reference). A bare invocation binds the immediately preceding user message as the utterance (a one-turn U-BINDING rule, not a session scan). Beyond the bound utterance it never scans the wider session, codebase, or rules, and it never reverse-traces hidden decision coordinates from externalized substrate — that is Euporia's territory (/elicit), not this protocol's.
    8. Chain semantics — a documented trade-off, not a mitigation: When the invocation names a chain reference, the chained material folds in as seeds — keeping any origin tag it already carries (a chained DiverseCandidateField's candidates re-seed under their existing tags, so an origin=AI candidate is never relabeled User by re-chaining), typed origin=User only when untagged (utterance-borne fragments, a collection protocol's output). On a named ChainRef — not Entry = Seeded generally — read references/chain-reference.md: it carries what happens next — first-pass promotion and frame handling. The trade-off this path carries is stated under Known Limitations.
    9. GenerationFrame ≠ analytical lens: A GenerationFrame is a partition for parallel candidate production — a divergence angle, not an analytical perspective. It carries no substrate need, no per-perspective directive, and is never handed off as a framed inquiry object; that machinery belongs to Prothesis (/frame).
    10. Vendor-neutral parallelism: "Generate over open frames" names a logical topology, not a required tool. A host MAY realize it via isolated parallel agents when available; heuresis's meaning does not depend on that realization, and provenance stays {User, AI} — never a host- or vendor-specific tag.
    11. Round composition: Compose each round so the reader can act on it without reassembling it — everyday language rather than this file's formal vocabulary, the judgment set beside the evidence it rests on together with the differential implication that matters for the next move, and analytical context laid out before a gate rather than inside it, so the gate carries the question and each option's differential implication. Read references/round-composition.md before composing when a term's rendering has to hold across the session or wording has to be carried through unchanged, when some of what is in view belongs to a later round or a trace rather than this one, or when this protocol's own phases bear on where a sentence sits relative to a gate.
    12. Option-set relay test (Extension classification): If AI analysis converges on a single dominant next-frame choice (option-level entropy → 0), present it directly as relay rather than as a gate. Continue/Stop remain genuinely viable under different user judgments about whether the field is wide enough.
    13. Gate integrity (Safeguard tier): The defined option sets (FrameSelection, ContinuationAnswer) are presented intact — option injection, deletion, and substitution each violate this invariant. Type-preserving materialization (naming the actual candidate frames inside Open/Continue while preserving the coproduct) is distinct from mutation.
    14. Formal blocks are runtime-normative: This protocol's formal blocks — those defined in its Definition code block above — are LLM-facing and constitutive of protocol identity: they type the prose and carry the operational contract executed at runtime. A reduced or single-shot realization carries every one of them through as runtime contract, since each block is the type that constitutes the protocol — preserving the blocks keeps the protocol intact. How its symbols render to the user is a separate emit-layer concern (see Round composition).
    15. Depth parks, width continues: An already-open frame is never a Continue target. A mid-loop wish to go deeper parks as a ParkedFollowUp — an extension relay citing the user's own request — held in mode state, declared at either terminal, and durably externalized by the host after the protocol ends (a task record, an issue: the protocol records the handoff, not the enforcement). The deepening itself is a later invocation chaining on the assembled field.
    16. The result type is the chain contract: DiverseCandidateField is endpoint-neutral and complete for unfolding what it carries — topic, candidates (frame- and origin-tagged), explored/unexplored frames, parked follow-ups, and the final unaddressed-signal snapshot all travel in the field, so unfolding the assembled field needs nothing outside it; the one declared boundary is the full signal take/discard ledger, which the contract deliberately does not carry (a Known Limitation — session text, which a consumer who wants that record can assemble it from, covers it). The convergence presentation materializes the full type: nothing in the type missing from the trace, nothing in the trace outside the type. Endpoint-specific unfolding — an issue tracker, a task list, a next protocol — is downstream scope.
    17. Signals are extracted, coverage is observed, never scored: At Phase 0, alongside Entry, heuresis extracts Signals — concerns, weaknesses, or requirements present in the bound utterance and any named chain reference, tagged by which of those two it came from. The same input boundary as Rule 7 governs extraction — never the wider session, codebase, or rules. The extracted set is stated once in the Phase 0 entry relay (source-tagged, no response requested — a relay, not an entry question), so every signal enters session text at extraction; each later Qround re-states only the signals still unaddressed. Coverage — which signals a generated candidate already responds to, and which remain unaddressed — is a derived, binary, source-traceable observation recomputed every round, never a stored partition, and never a quality score, elimination reason, or ranking signal over any candidate; it says only whether a signal has a response yet, not whether that response is good.
    18. Per-round decision-delta, placement split: Before every Qround, heuresis evaluates four things: which signals remain unaddressed, how the explored and unexplored frames differ in the direction they'd take the field (not just their labels), what continuing would cost to review, and what stopping would finalize and preserve. The evaluation runs every round; what renders adapts to what carries decision-relevant content this round, and unchanged or empty detail may compress — except unaddressed signals, which are always surfaced whenever any exist. Placement follows Round composition (Rule 11): the field status, unaddressed signals, and frame-direction contrast sit in the pre-gate text; the exact widening target, which still-unaddressed signal that widening aims at, added review load, and the depth-parks-not-continues distinction sit in the Continue option's own line; what's finalized and retained sits in the Stop option's own line.
    19. Fixed option order, neutral readout: Continue is always option 1 and Stop is always option 2, at every Qround presentation and every re-presentation, without exception. Any leaning heuresis forms — from a novelty trend, or from how the field's coverage sits — may surface only as the basis-cited state that grounds it in the pre-gate text (what recent rounds produced, which signals still lack responses), never as the direction it points: no wording that calls continuing or stopping the favored move, and no reordering. The unaddressed signals themselves are field state, not a leaning: they carry the placement Rule 20 assigns them. Both option lines carry symmetric specificity, and the field-status paragraph precedes every presentation and re-presentation alike — a re-presentation after a park acknowledgment or an unresolved Continue never poses a contextless choice.
    20. Form feedback: Silence about form is not evidence about form. Too dense fails quietly — the reader skims, answers past it, stops — while too plain fails out loud, so the complaints that arrive come from one side only. Density therefore does not carry over from the previous round: each round takes it from what this request asked for, while a statement about form does carry over until it is countermanded. Read an instruction about form for the parts of a round it reaches, not for what kind of reaction it is — a complaint, a request, a symptom report and a bare preference are one input here, and sorting them by kind yields nothing the reach reading does not already give while costing a clause per kind. Change the form rather than asking which form they want; naming one is the recall this discipline exists to remove. What such an instruction reaches is whatever the active protocol leaves open in how a round is composed — its density, its ordering, its length. What it does not reach is whatever is already fixed for this round elsewhere: content the protocol requires, wording carried verbatim, an order it presents in, a cadence it caps, a turn boundary it sets. Those stay in place, and the layer that fixed them is what states why. Say in one line what changed; where the instruction overlapped something that stays, say in one line that it stays and why — that second line is owed by the overlap, not by how the instruction was worded.

    Footnotes

    1. Greek εὕρεσις denotes finding, discovery, or invention/conception broadly; its later specialization as a term of rhetorical inventio is one historical narrowing among several, not the word's primary or governing sense here.

    Frequently asked questions

    What to verify before installation and use

    What does the ideate source document cover?

    Resolve an underexpanded candidate field through frame-parallel divergent generation, without ever eliminating, ranking, or selecting among the candidates it produces. Type: (CandidateFieldUnderexpanded, User, DIVERGE, IdeationRequest) → DiverseCandidateField.

    How do I install ideate?

    The source record exposes this install command: npx skills add https://github.com/jongwony/epistemic-protocols --skill "heuresis/skills/ideate". Inspect the command and pinned source before running it.

    Alternatives

    Compare before choosing