/**
 * defineRAG — retrieval-augmented generation over a document corpus.
 *
 * Embed the user's question, retrieve the most similar chunks from a
 * vector store, inject them into the system-prompt slot of the next LLM
 * call. It is the same machinery as `defineMemory({ type: SEMANTIC,
 * strategy: TOP_K })` — with three differences that are not cosmetic:
 *
 *   defineMemory ─┬─► EPISODIC   (raw conversation)
 *                 ├─► SEMANTIC   (extracted facts / RAG chunks)
 *                 ├─► NARRATIVE  (beats / summaries)
 *                 └─► CAUSAL     (footprintjs decision snapshots)
 *
 *   defineRAG    ─►  SEMANTIC + TOP_K, READ-ONLY, over a NAMED CORPUS,
 *                    rendered as citable <source> blocks.
 *
 * ─── What changed in 8.8.0, and why ────────────────────────────────────
 *
 * **1. A corpus is read-only.** Until 8.8.0 `defineRAG` also mounted the
 * semantic pipeline's WRITE half, which embedded every conversation turn
 * and stored it in the same namespace as the documents. The consequence
 * was measurable and bad: the user's own question, re-embedded, is the
 * single best-scoring "document" in the corpus (cosine 1.0 against
 * itself), so it came back as retrieval hit #1 and consumed a slot in
 * the top-K budget that a real passage should have had. A corpus is not
 * a conversation log. Nothing this retriever reads is written back.
 *
 * If you want conversation memory ALONGSIDE a corpus, that is what
 * `defineMemory` is for, and the two are registered separately, each
 * with its own store:
 *
 * ```ts
 * const agent = Agent.create({ provider })
 *   .rag(defineRAG({ id: 'product-docs', store: corpusStore, embedder }))
 *   .memory(defineMemory({
 *     id: 'chat',
 *     type: MEMORY_TYPES.EPISODIC,
 *     strategy: { kind: MEMORY_STRATEGIES.WINDOW, size: 10 },
 *     store: conversationStore,
 *   }))
 *   .build();
 * ```
 *
 * **2. The corpus has its own namespace.** A memory reads under the
 * identity passed to `agent.run()`. For conversation recall that is
 * right. For a shared corpus it was the reason the documented example
 * retrieved NOTHING: `indexDocuments` writes under
 * `{ conversationId: '_global' }` by default, while a run with no
 * explicit identity gets `{ conversationId: 'run-<timestamp>-<n>' }` —
 * so the write side and the read side never met, silently. `corpus`
 * defaults to the same `'_global'` the indexer uses, so the documented
 * example works with no extra arguments; pass it explicitly when you
 * index per tenant.
 *
 * **3. Chunks are citable.** Retrieved passages render as
 * `<source id="refunds.md#3" doc="refunds.md" score="0.81">` under a
 * corpus header, instead of the conversation-recall shape that printed
 * `role="unknown" turn="0"` on a page of a PDF and gave the model
 * nothing to cite.
 *
 * Pattern: Composition over duplication — defineRAG returns a
 *          MemoryDefinition produced by defineMemory. No new engine
 *          code, no new slot subflow, no new stage.
 *
 * Role:    Layer-3 context-engineering primitive. Lives next to
 *          defineSkill / defineSteering / defineInstruction / defineFact
 *          but resolves to a memory subflow rather than an Injection
 *          (RAG content is computed at runtime via async retrieval —
 *          can't fit the synchronous Injection.inject shape).
 *
 * Emits:   `agentfootprint.memory.retrieved` (once per retrieval, with
 *          every candidate and its score — including the rejected ones),
 *          `agentfootprint.memory.attached` (once per chunk that reached
 *          the prompt), and `agentfootprint.context.injected` with
 *          `source: 'rag'` (once per chunk).
 *
 * @see ./indexDocuments.ts  for the seeding helper
 * @see ../../memory/define.ts  for the underlying factory
 * @see ../../memory/retrieval/  for the retrieval seam
 * @see ../../memory/asRoleRefusal.ts  for why `asRole` is refused, not honoured
 *
 * @example  Basic usage — works with no identity argument anywhere
 * ```ts
 * import { Agent, defineRAG, indexDocuments } from 'agentfootprint';
 * import { InMemoryStore, mockEmbedder } from 'agentfootprint/memory';
 * import { mock } from 'agentfootprint/providers';
 *
 * const embedder = mockEmbedder();
 * const store = new InMemoryStore();
 *
 * // Seed the corpus once at startup (defaults to the '_global' namespace).
 * await indexDocuments(store, embedder, [
 *   { id: 'refunds.md#0', content: 'Refunds are processed within 3 business days.',
 *     metadata: { source: 'refunds.md' } },
 *   { id: 'pricing.md#0', content: 'Pro plan costs $20/month.',
 *     metadata: { source: 'pricing.md' } },
 * ]);
 *
 * const docs = defineRAG({
 *   id: 'product-docs',
 *   description: 'Retrieve product documentation chunks',
 *   store,
 *   embedder,
 *   topK: 3,
 *   threshold: 0.6,
 * });
 *
 * const agent = Agent.create({ provider: mock({ reply: 'ok' }), model: 'mock' })
 *   .rag(docs)
 *   .build();
 *
 * agent.on('agentfootprint.memory.retrieved', (e) => {
 *   // every candidate, admitted or not, with its score and its reason
 *   console.log(e.payload.candidates);
 * });
 *
 * await agent.run({ message: 'How long do refunds take?' });
 * ```
 */
import type { Embedder } from '../../memory/embedding/index.js';
import type { MemoryStore } from '../../memory/store/index.js';
import type { MemoryIdentity } from '../../memory/identity/index.js';
import type { MemoryDefinition } from '../../memory/define.types.js';
import type { RetrievalStrategy } from '../../memory/retrieval/index.js';
/**
 * The namespace a corpus lives in unless told otherwise — the same one
 * `indexDocuments` writes to by default. The two defaults are one value
 * on purpose: index with no options, retrieve with no options, and the
 * documents are found.
 */
export declare const DEFAULT_CORPUS_IDENTITY: MemoryIdentity;
export interface DefineRAGOptions {
    /** Stable id. Becomes the scope-key suffix and the Lens label. */
    readonly id: string;
    /**
     * Human-readable description. Surfaces in narrative + Lens hover.
     * Recommend describing the *corpus* (e.g., "Product documentation
     * chunks indexed weekly from docs.example.com").
     */
    readonly description?: string;
    /**
     * Store containing the indexed corpus. Must implement `search()`. Use
     * `indexDocuments(store, embedder, docs)` at startup to populate it. Ships
     * with `InMemoryStore` for dev/tests; swap to a durable adapter in
     * production.
     *
     * A store that declares `ranksBy: 'server-text'` is served by the backend's
     * own index rather than one built here — see {@link embedder}.
     */
    readonly store: MemoryStore;
    /**
     * Embedder used for the read-side query. Pass the SAME embedder
     * instance (or one with the same `embedderId`) that was used for
     * indexing — cross-model similarity scores are not comparable.
     *
     * **Optional since 9.3.0, for one case only.** A store that declares
     * `ranksBy: 'server-text'` (see {@link MemoryStore.ranksBy}) takes the
     * question as WORDS and ranks it on the backend's side; there is nothing
     * here for an embedder to do, and embedding the query anyway would be spend
     * on a vector discarded on arrival. Against such a store this must be
     * OMITTED — passing one is refused rather than ignored, because an ignored
     * embedder reads, from the wiring, exactly like a working one.
     *
     * Against every other store it is still required.
     */
    readonly embedder?: Embedder;
    /**
     * Stable id of the embedder. Stored on entries during indexing
     * (via `indexDocuments`) and filtered at search time so a later
     * embedder swap doesn't pollute results.
     *
     * Refused alongside a `'server-text'` store for the same reason
     * {@link embedder} is: the backend's records were never written here and
     * carry no `embeddingModel` to filter on, so the option would name a filter
     * that filtered nothing.
     */
    readonly embedderId?: string;
    /**
     * The namespace this corpus lives in. Default
     * `{ conversationId: '_global' }` — the same default `indexDocuments`
     * writes to, so the plain path needs no argument on either side.
     *
     * A corpus is deliberately NOT scoped to the run's identity: it is
     * shared by every conversation, and reading it under a per-run
     * conversation id is the bug this default fixes.
     *
     * **Multi-tenant:** pass the tenant's identity here AND the same one to
     * `indexDocuments({ identity })`. A namespace that holds nothing is now
     * reported (`corpusEmpty` on `agentfootprint.memory.retrieved`, plus a
     * one-time warning naming the namespace), so a mismatch is loud rather
     * than an empty answer.
     */
    readonly corpus?: MemoryIdentity;
    /**
     * Top-K chunks to retrieve per turn. Default 3 (balanced —
     * defends against lost-in-the-middle while giving multiple
     * perspectives). Increase for richer context, decrease for cost.
     *
     * Shorthand for `retrieval: topK({ k })`; the two EXCLUDE.
     */
    readonly topK?: number;
    /**
     * Minimum cosine similarity to inject. **Strict** — when no chunk
     * meets the threshold, NO injection happens (no fallback that would
     * pollute the prompt with weak matches). Default 0.7.
     *
     * Tuning note: the right threshold is a property of the EMBEDDER. 0.7
     * is a high bar for some of them. Sentence-BERT relatives
     * (`all-MiniLM-L6-v2`, etc.) often score 0.4–0.6 even on relevant
     * chunks; Amazon Titan Text V2 was field-measured at 0.55–0.57 for a
     * direct hit, ~0.49 for the right section diluted, 0.36–0.42 for
     * noise — on that embedder the 0.7 default retrieves NOTHING,
     * silently. You no longer have to guess: the rejected candidates and
     * their scores are on every `agentfootprint.memory.retrieved` event,
     * so the right threshold is a number you can read off a run.
     *
     * Shorthand for `retrieval: topK({ threshold })`; the two EXCLUDE.
     */
    readonly threshold?: number;
    /**
     * A character budget for the retrieved passages, spent across them in
     * RANK order (8.19.0). Default: none — `topK` stays the only bound, so
     * nothing changes for a retriever that does not ask for this.
     *
     * **A count bound is not a size bound.** `topK` says how many passages
     * may reach the prompt and nothing about how long they are: ten chunks
     * cut by `byHeading()` off ordinary documentation measured 11,153
     * characters in the field, against a `systemPrompt` slot whose default
     * budget is 4,000 — an overflow produced entirely by defaults on both
     * sides. Nothing truncates (the slot warns and emits
     * `agentfootprint.context.budget_pressure`), so the run was honest about
     * the over-run and had no way to BOUND it. This is that bound.
     *
     * The two numbers, side by side, because they are the ones that meet:
     *
     * | knob | default | what it bounds |
     * |---|---|---|
     * | `defineRAG({ topK })` | 3 | how MANY passages |
     * | `defineRAG({ maxChars })` | none | how much TEXT they may be |
     * | `Agent.create({ contextBudget: { systemPrompt } })` | 4000 chars | the whole slot they land in |
     *
     * Retrieved passages share that slot with the system prompt, steering,
     * facts and skill bodies, so a budget of roughly half the slot is a
     * sane starting point: `maxChars: 2000` with the 4,000-char default.
     *
     * The spend is RECORDED, never silent: passages past the budget are
     * refused with `reason: 'over-char-budget'` on
     * `agentfootprint.memory.retrieved`, and the record carries `maxChars`
     * and `charsUsed`. Rank order, tail dropped — so a budget smaller than
     * the best-scoring passage admits nothing and says so per candidate,
     * rather than quietly injecting half a passage.
     *
     * NOT the splitters' `maxChars`: `byHeading({ maxChars })` bounds ONE
     * chunk at index time (default 1000), this bounds the WHOLE retrieved set
     * at query time. The first is why the arithmetic above lands where it
     * does — ten chunks off a 1000-char splitter is ten thousand characters
     * before a single tag is added.
     *
     * Composes with `retrieval` (unlike `topK`/`threshold`, which exclude
     * it): the strategy picks the candidates, this bounds their size.
     */
    readonly maxChars?: number;
    /**
     * The retrieval rule, spelled out. Replaces `topK` + `threshold`
     * entirely — passing both is refused, because they could disagree
     * and the recording would then name a `k` the run did not use.
     *
     * ```ts
     * import { topK } from 'agentfootprint/memory';
     * defineRAG({ id, store, embedder, retrieval: topK({ k: 5, threshold: 0.55 }) });
     * ```
     *
     * A cross-encoder re-ranker and a diversity (MMR) selector are the
     * next two adapters behind this same interface; neither ships in
     * 8.8.0, and the seam exists so that when they do, nothing else moves.
     */
    readonly retrieval?: RetrievalStrategy;
}
/**
 * Build a RAG context-engineering definition. The returned
 * `MemoryDefinition` is registered on the Agent via `.rag(definition)`
 * (or, equivalently, `.memory(definition)` — same plumbing).
 *
 * @throws when `store` does not implement `search()`. RAG requires a
 *         store that can retrieve.
 * @throws when `embedder` is missing and the store does not rank text
 *         server-side — somebody has to turn the question into a vector.
 * @throws when `embedder`/`embedderId` is passed to a store that DOES rank
 *         text server-side — the option would be read by nothing.
 * @throws when `retrieval` is combined with `topK` or `threshold`.
 */
export declare function defineRAG(opts: DefineRAGOptions): MemoryDefinition;
