/** * B8 M6 — Sub-turn reasoning loop. * * Pure helper. Given a mid-turn attend input that carries a partial assistant * response, decides whether the Attendant should re-score its own in-progress * output against memory and re-run retrieval with new entity hints harvested * from the partial text. Never executes observe() itself — the caller fires at * most one extra retrieval when the plan says `attempted=true`. * * This is A3's iterative refinement pattern re-applied on response progress * rather than initial-pass emptiness. Gates follow the same shape as * planRefinementPass: post-response closeout is gated off, pre-response is * rejected (pre-response is the initial pass, not a sub-turn loop), empty or * short partial responses are declined, and a no-new-tokens verdict declines * the retry so we don't thrash on text the caller already searched for. * * When the plan does propose a retry it surfaces `rescoreTokens` (novel * lowercase tokens from the partial) + `proposedHints` (novel type/id * patterns extracted by regex). The caller UNIONs those proposed hints with * the original entity hints before firing observe() again — same fix pattern * as B6's refinement retry (see the union-of-original+widened comment in * AttendantInstance.ts around line 5228). * * Pure function. No LLM. No I/O. Deterministic. Fully unit-testable. */ export type SubTurnLoopOutcome = 'declined_phase' | 'declined_post_response' | 'declined_no_partial' | 'declined_too_short' | 'declined_no_new_tokens' | 'budget_exhausted' | 'attempted_empty' | 'attempted_added'; export interface SubTurnLoopPlan { outcome: SubTurnLoopOutcome; attempted: boolean; partialResponseLength: number; initialFactCount: number; addedFactCount: number; rescoreTokens: string[]; proposedHints: string[]; budgetMax: number; reason: string; note: string; } export interface PlanSubTurnLoopInput { phase?: 'pre-response' | 'mid-turn' | 'post-response'; partialResponse?: string; latestMessage?: string; originalEntityHints: readonly string[]; initialFactCount: number; /** Max retry budget (default 1). Caller fires one extra observe when attempted=true. */ budget?: number; } export declare const SUB_TURN_LOOP_DEFAULT_BUDGET = 1; export declare const SUB_TURN_LOOP_MIN_PARTIAL_LENGTH = 40; export declare const SUB_TURN_LOOP_MAX_RESCORE_TOKENS = 5; export declare const SUB_TURN_LOOP_MAX_PROPOSED_HINTS = 3; export declare const SUB_TURN_LOOP_MIN_TOKEN_LENGTH = 4; /** * Pure function. Decides whether the Attendant should run a bounded sub-turn * retrieval retry against its own partial response. Never calls observe(). * Never hits I/O. * * Gate order: * 1. Phase gate — post-response and pre-response reject immediately. M6 only * fires on mid-turn attends. * 2. Partial response presence — no field → declined_no_partial. * 3. Length gate — partial under MIN_PARTIAL_LENGTH → declined_too_short. * 4. Budget gate — budgetMax <= 0 → budget_exhausted. * 5. Novelty gate — extract tokens + type/id hints from partial; if every * token and hint is already in the baseline (original entity hints + * latest message), decline as declined_no_new_tokens. * 6. Otherwise set attempted=true with rescoreTokens + proposedHints * populated. Outcome starts as 'attempted_empty' and the caller flips it * to 'attempted_added' when the retry observe returns new facts. */ export declare function planSubTurnLoop(input: PlanSubTurnLoopInput): SubTurnLoopPlan; //# sourceMappingURL=subTurnLoop.d.ts.map