export type Slice = { /** * - Stable, word-like id (no whitespace/commas — it is echoed back by the judge). */ id: string; /** * - The item content shown to the judge. */ text: string; }; export type Provider = import("../types").Provider; export type ToolDef = import("../types").ToolDef; /** * SCAN — process every slice, LLM-judge each window, union matching ids across windows AND passes, CODE-count * the union. The default reliability mechanism (§9.2.1): the only path that does not silently undercount. * RC-9: a dead window is recorded in `missingSlices`, never folded into the count as a zero. * @param {string} predicate - The task the slices are judged against. * @param {Slice[]} corpus - The array slice-source (already validated/normalized by the caller). * @param {object} opts * @param {Provider} opts.provider * @param {number} [opts.window] * @param {number} [opts.passes] * @param {object} [opts.ctx] * @param {Function} [opts.onLlmResult] * @param {Function} [opts.policy] * @returns {Promise<{matchedIds: string[], count: number, missingSlices: string[], window: number, passes: number, scanned: number}>} * @throws {HaltError} a governance cap halted a window judge. */ export function scanCount(predicate: string, corpus: Slice[], opts: { provider: Provider; window?: number | undefined; passes?: number | undefined; ctx?: object; onLlmResult?: Function | undefined; policy?: Function | undefined; }): Promise<{ matchedIds: string[]; count: number; missingSlices: string[]; window: number; passes: number; scanned: number; }>; /** * @typedef {object} Slice * @property {string} id - Stable, word-like id (no whitespace/commas — it is echoed back by the judge). * @property {string} text - The item content shown to the judge. */ /** * Judge ONE window: run an isolated Loop with the classify prompt over the window's items, and intersect the * returned ids with the ids actually SHOWN this window (RC-2 — a window's judge can only "match" what it was * given; a hallucinated id from another window is dropped). A governance HaltError propagates (the caller turns * it into a clean incomplete); any other Loop fault marks the window DEAD (→ RC-9 missingSlices), never a * silent zero that would undercount. * @param {string} predicate * @param {Slice[]} window * @param {{provider: Provider, ctx?: object, onLlmResult?: Function, policy?: Function, nonce: number}} opts * @returns {Promise<{ids: string[]|null, dead: boolean}>} * @throws {HaltError} */ export function judgeWindow(predicate: string, window: Slice[], opts: { provider: Provider; ctx?: object; onLlmResult?: Function; policy?: Function; nonce: number; }): Promise<{ ids: string[] | null; dead: boolean; }>; /** * The §9.2-validated classify system prompt, GENERALIZED from the POC's hardcoded "SPORTS news" predicate to * an arbitrary one. The load-bearing wording is verbatim ("Examine EACH item individually", "Output ONLY the * IDs", "Comma-separated. If none, output 'none'. No count, no prose.") — the predicate is the only variable. * It MUST return ids, not a count: counting is CODE's job (RC-5 / §9.1), so the judge never does arithmetic. * @param {string} predicate - The task/goal the items are judged against. * @returns {string} */ export function classifySystem(predicate: string): string; /** * Does this text imply a completeness ("all / every / count / how many") ask? Used to upgrade a capped search * to a scan (the only complete path). Conservative by design — a false positive only costs a thorough scan. * @param {unknown} text * @returns {boolean} */ export function impliesCompleteness(text: unknown): boolean; /** * Normalize a raw slice-source into validated `{id, text}` slices (drops malformed entries — a slice with no * string id/text cannot be judged or counted, so it is excluded rather than silently miscounted). * @param {unknown} corpus * @returns {Slice[]} */ export function normalizeCorpus(corpus: unknown): Slice[]; /** * The `search` handle tool (RC-5 needle path): litectx `recall` — embeddings ON, `fact`/`episode` (the * KNN-nominate kinds), capped at `KNN_K`. For FINDING the relevant few; the description tells the worker it * CANNOT count (the completeness guard catches a "how many" ask before this tool is ever offered). Returns the * matched items' bodies as text the worker reads; never the whole corpus. * @param {{recall: Function}} litectx * @param {{kinds?: string[], n?: number}} [opts] * @returns {ToolDef} * @category integration * @when you want to give a recurse worker a needle-search handle over litectx (embeddings recall for the relevant few) — for FINDING, never counting * @fails returns matched bodies only and CANNOT count (the completeness guard blocks a "how many" ask before it is offered); a dead window yields nothing, never a fabricated hit. * @example * const tool = buildSearchTool(litectx, { n: 8 }); * await recurse(task, ctx, { retrieval: 'search', tools: [tool] }); */ export function buildSearchTool(litectx: { recall: Function; }, opts?: { kinds?: string[]; n?: number; }): ToolDef; /** * The `exact` handle tool (RC-5 rule path): a deterministic, embeddings-free code-side predicate filter over * the slice-source — returns every record whose text contains ALL given terms (case-insensitive AND). This is * the "code-side predicate filter" half of §9.2.1; it is complete over the slices it is given (no recall cap) * but only as good as a lexical rule. (FTS-AND over a litectx instance is the alternative, but needs embeddings * OFF to stay exact — deferred; the code-side filter is the embeddings-free path shipped now.) * @param {Slice[]} corpus - The validated slice-source. * @returns {ToolDef} * @when you want an embeddings-free, exact AND-term filter handle over a slice-source — complete over its slices, for precise lexical matches * @fails only as good as a lexical rule; complete over the slices it is given (no recall cap) but matches nothing outside them. * @example * const tool = buildExactTool(corpus); * await recurse(task, ctx, { retrieval: 'exact', tools: [tool] }); */ export function buildExactTool(corpus: Slice[]): ToolDef; /** * The `scan` handle tool (RC-5 COMPLETE path, the per-query Family-A face of §10 step 7) — the deterministic * counterpart to `search`/`exact` as a TOOL a worker may call per sub-query. Where `search_memory` returns the * top FEW (capped, cannot count) and `exact_match` is a lexical rule, `scan_count` runs the full §9.2.1 scan * (`scanCount`) over EVERY record and returns an exact, CODE-counted total — the only tool that does not silently * undercount. The completeness routing lives in the DESCRIPTIONS, not a code-guard: this tool says "use for how * many / all / count"; `search_memory` says "never use to count" — so a worker picks the complete path per * sub-query (the shape can differ per sub-query with no adopter declaration). RC-9 honesty is preserved at the * tool boundary: a dead window surfaces as an explicit `INCOMPLETE — the count is a floor`, never a clean number * over a hole. A governance `HaltError` from the inner scan PROPAGATES (the Loop turns it into a clean halt — * never wrapped to a `ToolError`). * @param {Slice[] | (() => Promise)} corpus - The slice-source (array or async, like `litectxCorpus`); * materialized lazily on first call and cached for the tool's lifetime. * @param {{provider: Provider, window?: number, passes?: number, ctx?: object, onLlmResult?: Function, policy?: Function}} opts * @returns {ToolDef} * @when you need the complete count/"all" path — scan every slice + LLM-judge + code-count — the only retrieval mode that can honestly answer "how many" * @fails a dead window surfaces as `INCOMPLETE — the count is a floor`, never a clean number over a hole; a governance HaltError propagates clean. * @example * const tool = buildScanTool(corpus, { provider, window: 8 }); * await recurse('how many mention X', ctx, { tools: [tool] }); */ export function buildScanTool(corpus: Slice[] | (() => Promise), opts: { provider: Provider; window?: number; passes?: number; ctx?: object; onLlmResult?: Function; policy?: Function; }): ToolDef; /** * Build a SCAN slice-source backed by a litectx-RESIDENT corpus (facts/episodes the agent already accrued) — * the §10-step-7 deferral un-blocked by litectx 0.26's `enumerate` verb (spec: * docs/archive/prd.md). Returns the generic async slice-source recurse's scan reads: a * `() => Promise` that pages through EVERY row of `kind` via `enumerate` (exhaustive — the rank-free * read `recall` structurally cannot do) and maps each to `{id: item.path, text: item.body}`. recurse stays * litectx-agnostic: it depends on this source SHAPE, never on litectx (same stance as `remember`'s Store * socket) — an adopter can hand any `() => Promise` (a DB, a file, an API) instead. * * Only for a corpus ALREADY in litectx for its own reasons — never ingest a fresh corpus just to enumerate it * back (strictly worse than scanning the in-hand array; spec §1.1). * @param {{enumerate: Function}} litectx * @param {{kind?: 'fact'|'episode', pageSize?: number}} [opts] * @returns {() => Promise} * @category integration * @when you want a resident slice-source that paginates a litectx corpus via enumerate — for a corpus ALREADY in litectx, feeding scan/partition * @fails never ingests a fresh corpus (strictly worse than scanning an in-hand array); returns an async source materialized once and cached. * @example * const corpus = litectxCorpus(litectx, { kind: 'fact' }); * await recurse(task, ctx, { mode: 'partition', corpus }); */ export function litectxCorpus(litectx: { enumerate: Function; }, opts?: { kind?: "fact" | "episode"; pageSize?: number; }): () => Promise; /** * Deterministic rotation by `k` — the shuffled-boundary mechanism for multi-pass union WITHOUT an RNG (keeps * RC-3 determinism: same corpus + same passes ⇒ identical scan). Rotating the array changes which items share a * window (and each item's within-window position), so an item under-recalled at one window's tail in pass 0 * lands mid-window in pass 1 — the §9.2.1 mechanism that lifts recall ~0.85 → ~0.93. * @template T @param {T[]} arr @param {number} k @returns {T[]} */ export function rotate(arr: T[], k: number): T[]; export const SCAN_WINDOW: 8; export const SCAN_PASSES: 2; export const KNN_K: 8;