---
name: Recollect
slug: recollect
category: AI Engineering
description: Recollect resolves vague recall by scanning contextual clues and presenting grounded excerpts for the user to recognize. Use it when you remember a discussion, topic, or concept but cannot name it yet.
github: "https://github.com/jongwony/epistemic-protocols/tree/main/anamnesis/skills/recollect"
language: JavaScript
stars: 161
forks: 15
install: "npx degit https://github.com/jongwony/epistemic-protocols/tree/main/anamnesis/skills/recollect ~/.claude/skills/recollect"
installs_to: ~/.claude/skills/recollect
source_path: anamnesis/skills/recollect/SKILL.md
collection_size: 25
category_size: 3670
collection_url: "https://dirskills.com/collections/jongwony/epistemic-protocols"
added: 2026-09-08T05:36:03.712Z
last_synced: 2026-09-08T05:36:03.712Z
canonical_url: "https://dirskills.com/skills/recollect"
---

# Recollect

Recollect resolves vague recall by scanning contextual clues and presenting grounded excerpts for the user to recognize. Use it when you remember a discussion, topic, or concept but cannot name it yet.

**Install:**

```bash
npx degit https://github.com/jongwony/epistemic-protocols/tree/main/anamnesis/skills/recollect ~/.claude/skills/recollect
```

## README

# Anamnesis Protocol

Resolve vague recall into recognized context through AI-guided contextual scan and user-validated recognition. Type: `(RecallAmbiguous, AI, RECOGNIZE, VagueRecall) → RecalledContext`.

## Definition

**Anamnesis** (ἀνάμνησις): A dialogical act of resolving vague recall into recognized context. The user holds an empty intention toward a past discussion they cannot name; AI reads the cue out of the utterance and the accumulated context, finds the candidates the hypomnesis INDEX and the record spines suggest (`memory/` is a non-scanned, user-curated realization-layer adjunct — see STORE TOPOLOGY), opens each member's own record for the excerpt the cue reaches, and presents that excerpt with its source so the user can identify it. The INDEX narrative is a cue that wakes recall; the excerpt and its locator are the evidence; the act that makes a RecalledContext is the user's identification. Recognition over retrieval, grounded in the source rather than in the index.

```
── FLOW ──
Anamnesis(V) → Detect(V) →
  not-empty_intention(V): relay(finding) → proceed (no activation)
  empty_intention(V): Cue(V, Σ) → V.trace, V.unit → set(scan_scope = spine, attempts = 0) →
    Find(Store ⊕ (scan_scope = full_text ? SSOT_body : ∅), V) → O[ranked] →
    |O[]| = 0 ∧ attempts = 0: Ask(V) → Stop → H → recue(V, H) → set(attempts = attempts + 1) → Find
    |O[]| = 0 ∧ attempts > 0 ∧ fulltext_unscanned: Qx(StoreExpansion) → Stop → X →
      ExpandFullText: set(scan_scope = full_text) → Find
      StopAtSpine: NullMatch → inform(V, Σ) → deactivate
    |O[]| = 0 ∧ attempts > 0 ∧ fulltext_scanned: NullMatch → inform(V, Σ) → deactivate
    |O[]| > 0: O[top] := Ground(O[top]) →
      ¬grounded(O[top]): O[] := O[] \ {O[top]} → Ground the new head, or fall to the |O[]| = 0 branches above when none is left   -- no member's record opened: nothing to present from
      grounded(O[top]): set(presented = O[top]) → Present(Λ.presented) → Stop → U →
      Identified: emit(RecalledContext(Λ.presented)) → converge
      Corrected(c) ∧ attempts < max: recue(V, c) → set(attempts = attempts + 1) → Find
      Corrected(c) ∧ attempts = max: surface(Λ.presented) → deactivate   -- AttemptsExhausted: the closest grounded candidate, no identification claimed
      Withdrawn: deactivate                                         -- the user moved on; no terminal claimed

── MORPHISM ──
VagueRecall
  → detect(empty_intention)              -- recognize vague recall state
  → cue(trace, unit)                     -- what is meant, and at which whole: one session, or the line, topic, or concept above it
  → find(Store, cue)                     -- INDEX gist and record spines as cues (see STORE TOPOLOGY); above session scope the candidates are joined by read-time inferred edges (── FIND ABOVE SESSION SCOPE ──)
  → ask(user)?                           -- one open question when the first find returns nothing; a zero result expands or terminates only after at least one user round-trip — that question's answer or a correction, either of which is new cue information
  → expand(SSOT_body, user)?             -- only after a round-trip and a spine-scope miss, at the user's election
  → ground(recognizable)                 -- open each member's own record: the excerpt the cue reaches, its locator, its handle; the narrative is composed from the excerpts
  → present(recognizable)                -- one shape at every scope; the turn yields
  → identify(recognizable, user)         -- synthesis of identification (Husserl CM §18) fulfilling the empty horizon (CM §19): the user's observable act, never inferred from silence
  → emit(RecalledContext)                -- the identified recognizable with its excerpts and locators, as session text
  → RecalledContext
requires: empty_intention(V)              -- phenomenological trigger
deficit:  RecallAmbiguous                 -- activation precondition (Layer 1/2)
preserves: Store                          -- SSOT ⊕ INDEX are read-only; V is recued during the protocol
invariant: Recognition over Retrieval

── TYPES ──
V                = VagueRecall { trace: RecallTrace, unit: Unit, cues: List(Cue) }   -- the cue: what the user gave, at the whole they mean; cues accumulate what each answer and correction added
RecallTrace      = { keywords: Set(String), temporal: Optional(String),
                     associations: Set(String), identifiers: Set(IdentifierTuple) }
Cue              = String   -- what the user adds: the answer to the open question, or a correction; recue folds it into the trace
Unit             ∈ {session, line, topic, concept}   -- the whole the intention names, read from V + Σ at Phase 0 and re-read by recue when a cue names a different whole. Closed on a stated premise: the three values above session are the three ways candidates join — succession, shared topic, a settled concept — and a recall matching none of them is at session scope
InputType        ∈ {StructuredIdentifier, NaturalRecall, Mixed}      -- Find's own dispatch, read from V + Σ; not a phase
Track            ∈ {entropy, salience, hybrid}                       -- dispatched from InputType inside Find (dispatch bindings in ── ENTROPY EXTRACTION ── and ── SALIENCE MARKERS ──)
Source           = String   -- opaque: store location identifier (substrate-agnostic)
IdentifierTuple  = { literal: String, source: Source, source_namespace: String, precision: ℝ[0,1] } -- entropy-track anchor
                  -- source_namespace determines which claim kinds it can authorize (via the registry); a literal anchors ranking only when its namespace authorizes the recall trace's claim kind
                  -- claim_kind is NOT a tuple field — it is determined by source_namespace at scan time, so the writer materializes only source_namespace (the extractor's namespace), not a per-literal claim_kind
MarkerProfile    = { coinage: Set(Token), actor: Set(Entity),
                     temporal: Set(TimeRef), emotional: Set(Marker),
                     cognitive: Set(Marker), singularity: Set(Event) }  -- salience-track profile
DateAnchor       = String   -- ISO 8601 date; reference point for salience-track temporal normalization (e.g., session start). Optionality is carried at the use site (DateAnchor?)
EvidenceMode     = {user_constituted, attested, observed, inferred}   -- totally ordered tier of the content's evidential STANDING (who stands behind it: user-authored verbatim > party-asserted with verbatim witness > mechanically present without assertion > LLM-synthesized) — NOT extractor reliability (that is extraction_method's concern). Assigned at write time by construction of each artifact's production path (deterministic metadata, never LLM-judged). Ranks recall weight only — NEVER suppresses/excludes; introduces no automatic effectivity (any downstream status change stays user-gated).
SourceScan       = { skipped_lines: Nat, unverified_user_turns: Nat, omitted_chars: Nat }   -- how completely the record's own source reached the extractor: transcript lines that failed to parse, human turns absent from the runtime's own cross-check channel, and characters a bounded extraction dropped from the middle of a long session. Any count non-zero ⇒ the record was built from less than its whole source, so the emit says so (`Recalled context currency is not fidelity`)
Store            = SSOT ⊕ INDEX               -- see ── STORE TOPOLOGY ── block
Candidate        = { session_id: Optional(SessionId),
                     runtime: Source,
                     record: Optional(Source),                    -- the path to THIS candidate's own record, as the spine read declared it (── STORE TOPOLOGY ──: SSOT_spine yields "whatever handle the realization needs to reopen the record"). Ground opens this and never a path re-resolved at grounding time: the spine scan is not partition-scoped, so the active session's partition does not locate a candidate found in another one. Null ⇒ no record can be opened for this member — Ground yields an empty excerpt and the prose says so
                     recency: Optional(DateAnchor),               -- the record's own last-activity time, as the spine read declares it (Claude: the file's modification time; Codex: the rollout's session_meta timestamp). Where the record is gone but its INDEX entry survives, the entry's own timestamp supplies it; Null only where neither has one — NEUTRAL in ranking exactly as evidence_mode and source_scan are, never a penalty. Optional because a candidate whose record no longer exists must still be constructible: it is precisely the candidate the Ungroundable path exists to receive, and a mandatory field sourced from the missing record would make that path unreachable. Read by Rank for ordering, and above session scope by edge inference — which is what makes references/supra-session.md's "reads only these fields" true of a field Candidate actually has
                     cwd: Optional(String),
                     topic: String,
                     keywords: Set(String),
                     fingerprint: Prose,                          -- the INDEX gist: a cue, never evidence
                     cross_refs: List(Anchor),
                     evidence_mode: Optional(EvidenceMode),       -- highest tier among the signals that matched this candidate at scan time; Null ⇒ INDEX entry predates evidence-mode capture — Null is NEUTRAL in ranking (no contribution), never a penalty
                     source_scan: Optional(SourceScan),           -- capture-time integrity of the record's own source, published by the writer; Null ⇒ the entry predates integrity capture. NEUTRAL in ranking exactly as evidence_mode is: it qualifies what the emit says about a candidate, never what the candidate scores
                     fork_marker: Bool,                          -- true ⇒ the id is a sidechain/fork with no top-level SSOT (SidechainNoSSOT); its own id is not a valid resume handle. Invariants: fork_marker = false ⇒ parent_pointer = Null ∧ parent_cwd = Null ; parent_pointer = Null ⇒ parent_cwd = Null (parent_cwd requires parent_pointer; parent_pointer present with parent_cwd = Null is valid — parent identified but its cwd is unknown)
                     parent_pointer: Optional(SessionId),        -- orchestrating parent session for a fork candidate, read directly from the fork's own record; the resumable handle when the parent's top-level SSOT still exists (Null ⇒ parent record absent → non-resumable)
                     parent_cwd: Optional(String) }              -- parent session's working directory, paired with parent_pointer to build the parent resume handle (Null ⇒ parent record absent, OR parent identified but its cwd metadata is unknown — parent transcript predates cwd capture)
Anchor           = StructuredAnchor | LegacyAnchor      -- Candidate.cross_refs element; extends-edge sediment (context-adding annotation)
StructuredAnchor = { kind: ∈ {memory, github_issue, github_pr}, ref: String, channel: ∈ {user, transcript} }
                  -- the stored form tells a later reader WHAT kind of reference and from WHICH utterance channel; it confers NO effectivity — a structured anchor is never authoritative by form, it remains a recall clue requiring the same re-verification as any recalled context
                  -- kind reuses the entropy source_namespace vocabulary where overlapping (github_issue, github_pr); memory is sediment-local. StructuredAnchor ≠ IdentifierTuple: no precision, no compatible_anchor authorization — shared value vocabulary, disjoint machinery (annotation vs anchoring)
                  -- ref stores the canonicalized literal (issue/PR numbers normalized to "#N", memory paths prefixed "memory/"); canonical-form grep over INDEX is form-invariant (a search for "#309" hits ref: "#309") — the canonical form is the dedup key, so raw surface variants ("PR 309") collapse into it
LegacyAnchor     = String   -- opaque: memory path, URL, session ID, doc path — entries written before structured anchors; read as kind-unknown extends edges, never rejected, no migration
Prose            = String   -- source-agnostic NL description
Recognizable     = { unit: Unit, members: NonEmpty(Candidate), narrative: Prose, excerpts: List(Excerpt), assembly: Optional(Assembly) }
                  -- the one object at every scope; unit = session ⟺ |members| = 1. As Find returns it, narrative is the members' index gist and excerpts = ∅ — enough to order, never to assert. After Ground, excerpts holds one entry per member and narrative is composed FROM them, in the unit's shape (one session: origin → direction → outcome; a line: origin → development → arrival; a topic: fragments → standing; a concept: forged by → settled at); nothing the excerpts do not carry is asserted
Assembly         = { joined: the inferred edges that joined this recognizable's members, skipped: the links that resolved to no written record }   -- references/supra-session.md TYPES: Edge. Null at unit = session, where one member joins nothing. Carried on the object rather than in Λ so that surface(Λ.presented) at AttemptsExhausted still has the coverage after the yield — the recognizable is already the thing that crosses it
Excerpt          = { member: Candidate, text: Prose, locator: Source, handle: Optional(ResumeHandle) }   -- the record's own words at the span the cue reaches, where that record is, and the resume command the runtime reference validates (Null ⇒ non-resumable, stated in the prose). A member whose record cannot be opened yields text = ∅ and contributes nothing the narrative may assert
ResumeHandle     = String   -- the literal command the runtime reference emits (references/claude.md, references/codex.md; fork members per references/fork-resume.md)
O[]              = List(Recognizable)   -- Find's ranked result at V.unit; O[top] is its head
Find             = (Store, V) → List(Recognizable)   -- Scan_{Track} over the compact INDEX and the record spines (── STORE TOPOLOGY ──), joined above session scope (── FIND ABOVE SESSION SCOPE ──), ordered by Rank
Rank             = (List(Recognizable), RecallTrace) → List(Recognizable)   -- ordering only, computed from how well each recognizable matches the cue — no stored tier is read, because no writer publishes one: track-primary signal dominates, evidence_mode is a secondary tie-break (never a filter; Null neutral), edge connectivity joins it above session scope. No gate reads the order; it decides only which recognizable Ground opens first
Ground           = Recognizable → Recognizable   -- one read per member of O[top]: open the member's own record at member.record, resolved by its runtime reference, take the excerpt the cue reaches, bind its locator and handle (a fork member per references/fork-resume.md), then compose the narrative from the excerpts. Bounded by |members(O[top])|, never by the store — it opens named records, it does not scan bodies. Idempotent: a member already read is not re-read
grounded         = predicate; grounded(o) ≡ ∃ e ∈ o.excerpts : e.text ≠ ∅   -- at least one member's record opened. A recognizable no member's record could be opened for carries no evidence at all, and since the narrative is composed FROM the excerpts there is nothing it may assert — so it is never presented
Ask              = V → Question   -- one open question when Find returns nothing: what else the user remembers, in their own words; no dimension taxonomy, no candidate to regenerate
Question         = String
H                = Cue      -- the answer to the open question
U                = Response ∈ {Identified, Corrected(Cue), Withdrawn}   -- read from the user's next utterance after Present: Identified when they say so or proceed on that context; Corrected when the utterance turns the cue — "not that one", an earlier one, the whole line — and the utterance IS the cue; Withdrawn when it leaves the recall behind. Premise: one utterance carries one disposition, and silence is none of them — a turn that has not come back has decided nothing
X                = StoreExpansion ∈ {ExpandFullText, StopAtSpine}
ScanScope        ∈ {spine, full_text}   -- spine = INDEX ⊕ SSOT_spine (the initial scope); full_text additionally admits SSOT_body
fulltext_unscanned ≡ Λ.scan_scope = spine
fulltext_scanned   ≡ Λ.scan_scope = full_text
recue            = (V, Cue) → V   -- folds the cue into V.trace and V.cues; a cue naming a different whole re-reads V.unit
emitted(x)       = predicate; the emit(x) has fired in session text
RecalledContext  = session text carrying the identified Recognizable: its narrative, each member's excerpt with locator and handle, and the currency caveat below
               -- recall establishes IDENTITY (this WAS discussed/decided), not current-reality FIDELITY (it still HOLDS). Store-currency (the INDEX entry is fresh) ⊂ fidelity-to-current-reality: a recalled decision may be superseded, a recalled path renamed, a recalled convention revised. RecalledContext describes a PAST state; downstream consumers re-verify against current state before commit rather than treating it as confirmed current context.
find_empty       = predicate; find_empty ≡ |O[ranked]| = 0 at the Phase 1 branch point   -- the guard that reaches Ask, Qx, and NullMatch; a predicate over it is evaluated where the guard holds, in the same pass as the empty Find, never reconstructed after a yield
NullMatch        = predicate; canonical definition in ── CONVERGENCE ──
AttemptsExhausted = predicate; canonical definition in ── CONVERGENCE ──   -- the candidate-in-hand terminal, distinct from NullMatch's nothing-found one
Phase            ∈ {0, 1, 2, 3}
max              = the recall-try cap LOOP fixes   -- a bound on user attention, not a sufficiency criterion

── V-BINDING ──
bind(V) = explicit_arg ∪ colocated_expr ∪ prev_user_turn   -- priority: explicit_arg > colocated_expr > prev_user_turn

/recollect "text"           → V.trace = extract_trace("text", Σ)
"recall... topic"           → V.trace = extract_trace(text before trigger, Σ)
/recollect (alone)          → V.trace = extract_trace(previous user message, Σ)

Edge cases:
- Multiple vague references: bind to first, note others; re-invoke after NullMatch: fresh V, no carryover
- Composition (/recollect * /inquire): V from Anamnesis, Aitesis receives RecalledContext via session text

── PHASE TRANSITIONS ──
Phase 0: V → Detect(V) → empty_intention(V)?                    -- trigger (silent)
           [¬empty_intention(V)] relay(finding) → proceed       -- zero-signal: present activation finding, proceed without activation
           → Cue(V, Σ) → V.trace, V.unit → set(scan_scope = spine, attempts = 0)   -- the cue and the whole it names; Find's dispatch (InputType → Track) is read here too; initial scope + recall-try budget (silent)
Phase 1: V → Find(INDEX ⊕ SSOT_spine ⊕ (scan_scope = full_text ? SSOT_body : ∅), V) → O[ranked]   -- index + spine always; bodies too once ExpandFullText widened the scope, so a re-entry after a correction does not narrow back to spine and report a body-scoped miss; above session scope Find joins its candidates into recognizables (── FIND ABOVE SESSION SCOPE ──) [Tool]
           |O[ranked]| > 0 → O[top] := Ground(O[top]) →   -- one read per member of O[top]: excerpt, locator, handle; the narrative is composed from the excerpts before anything is presented [Tool]
             grounded(O[top]) → Phase 2
             ¬grounded(O[top]) → O[ranked] := O[ranked] \ {O[top]} → Ground the new head; when none is left the |O[ranked]| = 0 guards below receive it, and the records that could not be opened are named in whatever those guards emit — the open question's framing, Qx's pre-gate text, or the NullMatch diagnosis. All of them fire in this same turn, so the list needs no carrier
           |O[ranked]| = 0 ∧ attempts = 0 → Ask(V) → Stop → H → recue(V, H) → set(attempts = attempts + 1) → Phase 1   -- one open question [Tool]
           |O[ranked]| = 0 ∧ attem
