import type { ProviderMessage } from '../providers/interface.js'; import type { CodeContextResult, CodeIndexStats, MemoryRecord, MemoryRegistry, MemorySemanticSearchResult, MemoryVectorStats } from '../state/index.js'; /** * Default per-turn injection budget: min(800 tokens, 3% of the model's context window). * 800 is the static floor for models with an unknown/zero context window (0 short-circuits * the percentage term); the percentage term keeps the block a small, bounded slice of the * turn's own budget for large-context models rather than a fixed absolute cost. */ export declare const DEFAULT_TURN_KNOWLEDGE_BUDGET_TOKENS = 800; /** * Default relevance floor: the minimum `scoreKnowledge` score required to survive stage 2 * filtering (stage 1 is the existing confidence>=55 gate inside selectKnowledgeForTaskScored). * Derived directly from the scoreKnowledge weights (knowledge-injection.ts) rather than * picked arbitrarily: a record sitting exactly at the confidence floor (55) with the * weakest positive reviewState bonus ('fresh', +20) that matches at least one task token * (+20) scores 55 + 20 + 20 = 95. Below that, a record is either under-confidence, has no * reviewState credit, or matched nothing about the current turn, filler, not relevance. */ export declare const DEFAULT_TURN_KNOWLEDGE_RELEVANCE_FLOOR = 95; /** Default candidate breadth passed through to selectKnowledgeForTaskScored. */ export declare const DEFAULT_TURN_KNOWLEDGE_LIMIT = 3; /** Default candidate breadth for the code-index retrieval (Stage B). */ export declare const DEFAULT_TURN_CODE_LIMIT = 3; /** * Similarity → floor-scale projection for code-index hits (Stage B). * * Memory records are ranked on an ADDITIVE score scale (knowledge-injection.ts * scoreKnowledge): confidence (>=55) + reviewState bonus + per-token match * bonuses, with the default relevance floor of 95 = confidence 55 + 'fresh' 20 * + one token match 20. Code-index hits carry a cosine-derived `similarity` in * [0,1] (code-index-store.ts distanceToSimilarity = clamp(1 - L2distance/2)), * a DIFFERENT scale entirely. To let a single shared relevance floor govern * BOTH sources honestly, a code hit's similarity is projected onto the memory * score scale by: * * codeScore = similarity * CODE_SIMILARITY_TO_SCORE_SCALE (= similarity * 200) * * Consequences of scale = 200, stated so the mapping is auditable, not magic: * - The default floor 95 admits code at similarity >= 0.475. * - An orthogonal (unrelated) normalized-embedding pair has cosine 0, i.e. * L2 distance sqrt(2) ≈ 1.414, i.e. similarity ≈ 0.293, BELOW 0.475, so * unrelated chunks never clear the floor. * - A genuinely similar chunk (similarity 0.5–1.0 → score 100–200) clears it. * - Because the SAME configurable floor scales both sources, raising the * floor (stricter memory) also raises the code similarity bar in lockstep, * and lowering it loosens both. There is no separate, silently-diverging * code threshold to keep in sync. */ export declare const CODE_SIMILARITY_TO_SCORE_SCALE = 200; /** Bounded ring size for AgentRecord.turnInjections (see recordTurnInjection). */ export declare const DEFAULT_TURN_INJECTION_RING_SIZE = 20; /** * Structural code-index surface the per-turn retrieval reads (Stage B). Kept * structural (not `Pick`) so tests can supply a minimal * fake without constructing a real sqlite-backed store. `stats()` exposes the * exact honesty signals the retrieval gates on: an empty index, a provider- * space mismatch, or a hashed-only (no real semantic) provider each mean "do * not auto-inject", see collectCodeInjectionCandidates. */ export type TurnCodeIndexSource = { search(query: string, opts?: { limit?: number; }): readonly CodeContextResult[]; stats(): Pick; }; /** Source of one injected line: reviewable project memory vs the repo code index. */ export type TurnInjectionSource = 'memory' | 'code-index'; /** * Default per-turn knowledge injection budget: min(ceiling, 3% of the model * context window). `ceilingTokens` defaults to DEFAULT_TURN_KNOWLEDGE_BUDGET_TOKENS * but callers with config in scope pass agents.passiveInjection.budgetTokens so the * absolute cap is operator-tunable while the 3%-of-window clamp is preserved. */ export declare function defaultTurnKnowledgeBudgetTokens(contextWindow: number, ceilingTokens?: number): number; /** * Per-turn honesty record for one agent turn's passive-injection attempt. Stored on * `AgentRecord.turnInjections` (bounded ring) and appended verbatim to the agent's * session transcript (`{type:'knowledge_injection', turn, ...record}`), no new * event-contract member, per the brief's "prefer existing-event reuse" constraint. */ export interface TurnInjectionRecord { /** The agent turn number this retrieval ran on. */ readonly turn: number; /** The derived query actually sent through the ranking pipeline (task + conversation tail). */ readonly query: string; /** Count of scored, confidence-gated, not-already-injected MEMORY candidates considered this turn. */ readonly candidatesConsidered: number; /** * Count of not-already-injected CODE-INDEX hits considered this turn (before the relevance floor). * 0 when code injection was off / no code source was wired / the index was empty or mismatched * (see codeInjectionSkipped for which). Stage B, memory-only records keep this at 0. */ readonly codeCandidatesConsidered: number; /** Record ids actually injected into the prompt this turn (empty when block===null). */ readonly injectedIds: readonly string[]; /** * Source of each injected id, SAME ORDER as injectedIds: 'memory' for a reviewable * project-memory record, 'code-index' for a repo source-tree chunk. Kept as a parallel * array (not folded into ingestModes, which is a retrieval-quality label, not source * plumbing) so /recall and the transcript can label a code hit as a code hit honestly. */ readonly injectedSources: readonly TurnInjectionSource[]; /** Record ids that cleared the relevance floor but were dropped to fit the token budget. */ readonly droppedForBudget: readonly string[]; /** Estimated token cost of the rendered block (0 when block===null). */ readonly tokenCost: number; /** The token budget this turn was evaluated against. */ readonly budgetTokens: number; /** The relevance floor this turn was evaluated against. */ readonly relevanceFloor: number; /** * Retrieval-quality label of each injected record, same order as injectedIds: for a memory * record its ingest mode (keyword/semantic/hybrid-ranked); for a code hit its honest match * label ('semantic' when a real vector match, 'lexical' when a degraded name/path match). */ readonly ingestModes: readonly string[]; /** * Present exactly when a code source WAS wired and enabled this turn but contributed no * injected line, stating why in the store's own terms: 'code index empty', a provider-space * mismatch string, 'no semantic embedding provider', or 'no code chunks cleared the relevance * floor'. Undefined when code injected at least one line, or when code injection was off (the * flag/setting gate never called into the index, nothing to explain). */ readonly codeInjectionSkipped?: string | undefined; /** Honest embeddings signal: 'available' when the registry's vector index is enabled and * usable, 'fallback-lexical' when memory-store.ts's searchSemantic() degraded to keyword * ranking (no vector index, or the registry does not expose vectorStats at all). */ readonly embeddingBackend: 'available' | 'fallback-lexical'; /** Present exactly when block===null: why nothing was injected. */ readonly reason?: string | undefined; } /** * Structural registry surface, mirroring knowledge-injection.ts's private * `KnowledgeRegistrySource` plus one addition: optional `vectorStats`, the sole signal this * module uses to tell a real semantic search apart from memory-store.ts's silent lexical * fallback (searchSemantic() never throws on a missing/disabled vector index, it just * degrades to keyword ranking). Kept structural (not `Pick`) so tests * can supply a minimal fake without constructing a real MemoryStore/SQLite. */ export type TurnKnowledgeRegistrySource = { getAll(): readonly MemoryRecord[]; searchSemantic?(input: Parameters[0]): readonly MemorySemanticSearchResult[]; vectorStats?(): MemoryVectorStats; }; export interface BuildPerTurnKnowledgeInjectionInput { readonly memoryRegistry: TurnKnowledgeRegistrySource; /** The agent's (possibly frozen) task text, always included in the derived query. */ readonly task: string; readonly writeScope?: readonly string[] | undefined; /** The current conversation, formatted for the LLM, the source of "what changed this turn". */ readonly conversationTail: readonly ProviderMessage[]; /** Hard token budget for the rendered block. budgetTokens<=0 is the caller's job to no-op on; * this function still honors it correctly (an empty/null block, honest record). */ readonly budgetTokens: number; readonly relevanceFloor: number; readonly limit?: number | undefined; /** Ids never to re-list (the spawn-time baseline plus every id injected on prior turns). */ readonly alreadyInjectedIds: readonly string[]; readonly turn: number; /** * Stage B, repo code index. Optional; the two callers pass it only when a store is wired. * Whether code hits are actually retrieved is gated by `codeInjectionEnabled` (below) AND * the store's own honesty checks (empty index / provider mismatch / no semantic provider), * so a wired-but-disabled source is a hard no-op with an honest record. */ readonly codeIndex?: TurnCodeIndexSource | undefined; /** * Stage B, resolved code-injection gate for this turn: (the `agent-passive-code-injection` * gate, off by default via agents.passiveInjection.code) AND (the embedder's storage.codeIndexEnabled setting). Resolved * by the caller, not this pure function. Defaults to false, code injection never happens * unless the caller explicitly opted in this turn, matching the flag's default-off posture. */ readonly codeInjectionEnabled?: boolean | undefined; /** Candidate breadth for the code-index retrieval. Defaults to DEFAULT_TURN_CODE_LIMIT. */ readonly codeLimit?: number | undefined; } export interface BuildPerTurnKnowledgeInjectionResult { readonly block: string | null; readonly record: TurnInjectionRecord; } /** * The one behavioral difference from spawn-time selection: derive the query from the * LATEST user-role message in the conversation tail (a steer, a drained directive, or, * on turn 1, before any steer exists, the initial task itself) concatenated with the * task, instead of the task alone. On turn 1 the latest user message IS the task (the * runner seeds it via `conversation.addUserMessage(record.task)` before the turn loop), * so this collapses to the task with no duplication. */ export declare function deriveTurnKnowledgeQuery(task: string, conversationTail: readonly ProviderMessage[]): string; export declare function buildPerTurnKnowledgeInjection(input: BuildPerTurnKnowledgeInjectionInput): BuildPerTurnKnowledgeInjectionResult; /** * Push one entry onto a bounded ring, evicting the oldest entry once * `retention` is exceeded. Pure and exported so it is independently * unit-testable (and so orchestrator-runner.ts never has to hand-roll ring * eviction inline). */ export declare function recordTurnInjection(existing: readonly TurnInjectionRecord[] | undefined, entry: TurnInjectionRecord, retention?: number): TurnInjectionRecord[]; //# sourceMappingURL=turn-knowledge-injection.d.ts.map