Algolia-Central

Documentation/04-Functions-Reference.md

04 — Functions Reference (ALIVE Call Graph)

Last updated: 2026-04-17. Signatures read from code, not guessed.

Every function on the critical path. Each entry: purpose, signature (input/output), dependencies (calls / called-by), side effects, observed timing. Functions marked [EXPAND] need deeper documentation in a later pass.


Conventions

  • Input: argument list with types
  • Output: return type
  • Calls: functions this function invokes
  • Called by: functions that invoke this one
  • Side effects: network, Redis, SSE emit, logging beyond standard
  • Timing: observed from live traces (averaged across Runs 001–006)

Entry — handler (api-src/search.ts)

handler(req: VercelRequest, res: VercelResponse): Promise<void>

  • Purpose: The single entry into the system. CORS, rate limit, circuit breaker, input validation, then dispatch.
  • Calls:
  • checkRateLimit(clientId)lib/security/rate-limiter
  • checkCircuit()lib/security/circuit-breaker
  • normalizePersonaID(rawPersona)lib/search/persona_routing
  • isSpecialist(persona)lib/search/persona_routing
  • Dynamic import → igniteMaverick or igniteSpecialist from lib/search/orchestrator
  • recordSuccess() / recordFailure() → circuit breaker
  • Called by: Vercel runtime
  • Side effects: Sets CORS + security response headers; writes SSE chunks to res; may return HTTP 400/405/429/500/503
  • Branching:
  • If req.body.consent === trueigniteSpecialist({trigger:'execute'})
  • If isSpecialist(persona)igniteSpecialist({trigger})
  • Else → igniteMaverick()

Maverick Path

igniteMaverick(input, sessionId, requestId?): Promise<ReadableStream>

  • File: lib/search/orchestrator.ts:45
  • Input: {query: string, history: any[]}, sessionId: string, requestId?: string
  • Output: ReadableStream (SSE body)
  • Calls (in order): 1. getSessionState(sessionId) → Redis 2. resetSessionState(sessionId) if first turn 3. extractSignals(input, sessionState, requestId) → Gemini 4. guardIntent(query, intent) → intent validator 5. updateSessionState(sessionId, newSignals) → Redis merge 6. countLockedSignals(sessionState) → Redis util 7. analyzeDiscoveryState(...) → discovery state 8. fetchGoldenMap() → Algolia Atlas load (cached 60s) 9. orchestrateRetrieval({...}) → main retrieval call 10. injectPricingIfNeeded(query, concepts, chunks) → conditional chunk injection 11. trackView(...) → Insights (fire-and-forget) 12. generateDossierSummary(sessionState, lockedSignals) → dossier text 13. formatHitsForLLM(chunks) → prompt injection string 14. buildMaverickPromptWithPersona(...) → final system prompt 15. provider.startChat({systemPrompt, history, temperature, maxTokens}) → Gemini chat init 16. streamMaverickResponse({...}) → the streaming + audit 17. Handoff regex parse, keyword override (STRONG_BRUNO_SIGNALS) 18. addAskedSignal(sessionId, signal) → Redis track 19. updateSessionState(sessionId, {pendingHandoff, active_persona}) if handoff 20. processPostGenerationMetadata(...) → metadata manager 21. updateSessionState(sessionId, {turn_count: +1}) 22. constructFinalEvent(...) → final payload 23. persistSessionTelemetry(...) → Redis telemetry
  • Side effects: Writes ~10 distinct SSE event types via controller.enqueue
  • Error behavior: catch block emits event: error {error: message} and closes controller

extractSignals(input, existingState, requestId?): Promise<BrainOutput>

  • File: lib/search/signal_extractor.ts:172
  • Input: {query, history}, existingState: SessionState, requestId?
  • Output: BrainOutput (typed — see prompts/brain.ts:39) — contains intent, onion_signals, entities, search_query, keyword_query, discovery_question, filters, ...
  • Calls:
  • CHIP_SIGNAL_MAP[query.toLowerCase()] → chip answer detection (line 183)
  • getBrainPrompt(currentDate)prompts/brain.ts:66
  • getLLMProvider()lib/llm/index
  • LLMAPI.extractSignals(fn, requestId) → rate-limited wrapper
  • provider.generateContent({systemPrompt, messages, jsonMode:true, model:'gemini-2.0-flash'})
  • Called by: igniteMaverick (step 3)
  • Side effects: single Gemini call (~1800–2000ms — 27% of Maverick turn time)
  • Error behavior: returns a safe fallback BrainOutput with all signals null
  • Carry-forward rule (prompt-level): passes current SessionState as CURRENT USER PROFILE block; tells LLM to preserve existing values unless explicitly contradicted. F-026: this rule is a PROMPT instruction only — no code-level enforcement. If the LLM ignores it, nothing catches.

detectPushback(query, state): { contradictsField, denialStrength } — NEW Step 3 (F-026)

  • File: lib/search/pushback_detector.ts
  • Input: current user query + session state (8-signal shape)
  • Output: { contradictsField: string | null, denialStrength: number (0..1) }
  • Purpose: Regex/keyword-only pass (NO LLM call). Detects when the user is pushing back on a previously-locked signal, so orchestrator can null the field BEFORE signal_extract merges with the old value via ||.
  • Called by: orchestrator.igniteMaverick step 1.5 (between session load and signal extract).
  • Side effects: none (pure). Caller issues updateSessionState to null the field.
  • Patterns matched: "I am not X", "we are not", "I never said", "that's wrong", "what makes you say", "why would you think". Word-boundary token match against field-specific keywords AND the current locked value.

detectChipAnswer(query: string): { field, value } | null

  • File: lib/search/signal_extractor.ts:285
  • Input: raw query
  • Output: matched chip entry from CHIP_SIGNAL_MAP or null
  • Purpose: when user clicks a canned discovery chip (e.g., "Poor relevance"), we bypass LLM and directly map to the right signal field.

analyzeDiscoveryState(sessionState, askedSignals, query, turnCount, intent): DiscoveryAnalysisResult

  • File: lib/search/discovery_analyzer.ts:36
  • Input: SessionState, askedSignals: string[], query: string, turnCount: number, intent: string
  • Output: {isQualified: boolean, isStrictDiscovery: boolean, isFatigueReached: boolean, availableFields: string[], showDiscoveryUI: boolean, ...}
  • Calls: internal-only
  • Called by: igniteMaverick (step 7)
  • Side effects: pure (no network / no state mutation)
  • F-035 location: qualification rule is here. Currently requires high lockedSignals + turn count. Step 3 will relax.

generateDossierSummary(rawState, lockedSignals): string

  • File: lib/search/discovery_analyzer.ts:190
  • Purpose: pretty-prints the 8-signal state as a short markdown string (observed 213–271 chars)
  • Called by: igniteMaverick (step 12). Output is emitted as event: maverick_header and also injected into the Maverick system prompt.

fetchGoldenMap(): Promise<any[]>

  • File: lib/search/retrieval_orchestrator.ts:570
  • Input: none
  • Output: Array of Atlas entries (classification map)
  • Side effects: Algolia searchSingleIndex on Atlas index; memoized 60s via in-process cache
  • Called by: igniteMaverick (step 8); also by classifyLink() for customer URL resolution

orchestrateRetrieval(options): Promise<RetrievalResult>

  • File: lib/search/retrieval_orchestrator.ts:113
  • Input: RetrievalOptions (line 21) — {query, searchQuery, keywordQuery?, filters, mapData, persona, sourceTypeFilters?, matchCount}
  • Output: RetrievalResult (line 44) — {chunks, atlasMatch, strategy, queryID, ...}
  • Calls:
  • Algolia searchSingleIndex on NeuralSearch index
  • Internal strategy selection: filtered → relaxed → fallback
  • Called by: igniteMaverick (step 9)
  • Side effects: network call to Algolia; observed 700–1600ms

injectPricingIfNeeded(query, architectureConcepts, chunks): any[]

  • File: lib/search/retrieval_orchestrator.ts:532
  • Purpose: If query mentions pricing/scale, adds pricing chunks to the result set that might not have been semantically matched
  • Called by: igniteMaverick (step 10)

formatHitsForLLM(hits): string

  • File: lib/search/chunk-merger.ts:5
  • Input: hits: any[]
  • Output: a single formatted string with [SOURCE N] title \n url \n chunk_text for each hit
  • Called by: igniteMaverick (step 13) + potentially by buildMaverickPromptWithPersona

buildMaverickPromptWithPersona(dossier, sources, discoveryInstructions, turnCount, isStrictDiscovery): Promise<string>

  • File: lib/search/prompts/maverick.ts:12
  • Purpose: Assembles the full Maverick system prompt (sandwich structure: persona → knowledge → directives)
  • Output size: ~16KB baseline; known to spike to 157KB (F-006 — root cause unknown)
  • F-032 lives here: the Namedrop template instruction that leaks into UI

streamMaverickResponse(options): Promise<StreamResult>

  • File: lib/search/stream_processor.ts:60
  • Input: StreamOptions (line 26) — {encoder, controller, chat, query, lockedSignals, isQualified, isFatigueReached, isAccelerated, availableFields, rawState, products, contextSources, sessionId}
  • Output: StreamResult (line 45) — {fullContent, askedSignal, detectedSources, zombiePreventionTriggered, auditResult}
  • Calls:
  • new InlineStreamAuditor(contextSources, products) — line 82
  • inlineAuditor.auditParagraph(paragraph) per paragraph — line 90 (the hallucination guard)
  • chat.sendMessageStream(query) — Gemini streaming
  • Eventually: post-stream ContentAuditor.auditContent() for HEAD checks
  • Called by: igniteMaverick (step 16)
  • Side effects: writes event: chunk per paragraph to SSE; writes event: audit_trace diagnostic lines

determineHandoffTarget(sessionState, query): 'elena' | 'bruno'

  • File: lib/search/question_expander.ts:16
  • Purpose: keyword score — which specialist is the better match for this query?
  • Called by: igniteMaverick (step 17 — AFTER regex match, as a fallback)
  • Known issue: 3-layer handoff routing (regex → this function → STRONG_BRUNO_SIGNALS override) is a Frankenstein pattern — F-027 cleanup target.

processPostGenerationMetadata(options): Promise<MetadataResult>

  • File: lib/search/metadata_manager.ts:98
  • Purpose: computes wordCount, uniqueSources[], updates discovery history
  • Called by: igniteMaverick (step 20)

constructFinalEvent(options): object

  • File: lib/search/metadata_manager.ts:315
  • Purpose: assembles the event: final SSE payload (content, sources, signals, discoveryResult, etc.)
  • Called by: igniteMaverick (step 22), igniteSpecialist (step 9 equivalent — Specialist builds its own final payload inline, does NOT call this)

persistSessionTelemetry(sessionId, turns, state, handoff, chunkCount, status): Promise<void>

  • File: lib/search/telemetry.ts:93
  • Side effects: Redis write (telemetry:sessionId key)
  • Called by: both igniteMaverick and igniteSpecialist

Specialist Path

igniteSpecialist(input, sessionId, requestId?): Promise<ReadableStream>

  • File: lib/search/orchestrator.ts:611
  • Input: {query, history, persona: 'elena'|'bruno', trigger?, expandedQuestion?}, sessionId, requestId?
  • Output: ReadableStream
  • Calls (in order): 1. validatePersona(input.persona) → guardrail 2. getSessionState(sessionId) → Redis 3. buildAgentStudioMessage({query, trigger, expandedQuestion, sessionState}) → internal 4. getAgentId(validatedPersona) → agent ID resolver 5. callAgentStudio({agentId, message, history}) → Agent Studio POST 6. adaptAgentStudioStream(response, {encoder, controller, persona, sessionId}) → parse + pass through 7. Parallel HEAD check on all links in response (lines 697–712) 8. persistSessionTelemetry(...) → Redis 9. Emit event: final inline (not via constructFinalEvent)
  • Side effects: Agent Studio HTTP call (1500–1800ms first byte); total stream 3–35s
  • 🚨 F-038: Does NOT instantiate InlineStreamAuditor. Specialist output is NOT audited for paragraph-level hallucinations, attributed metrics, or grounding.

buildAgentStudioMessage(params): string

  • File: lib/search/orchestrator.ts:789 (internal, not exported)
  • Input: {query: string, trigger?: string, expandedQuestion?: string, sessionState: SessionState}
  • Output: XML-tagged message string — <user_question>...</user_question>\n<extracted_entities>...\n</extracted_entities>
  • Trigger branching:
  • handshake → constructs canned "EXECUTE HANDSHAKE PROTOCOL" instruction
  • execute / consent → uses expandedQuestion from session's pendingHandoff
  • default → uses raw query

callAgentStudio(request): Promise<Response>

  • File: lib/agent-studio/client.ts:62
  • Input: {agentId, message, history}
  • Output: raw Response with SSE body
  • Side effects: HTTPS POST to Agent Studio endpoint
  • Observed timing: 1500–1800ms to first byte

getAgentId(persona: AgentPersona): string

  • File: lib/agent-studio/client.ts:150
  • Output: 'f029acbb-...' for elena, 'facb549e-...' for bruno
  • Lookup: AGENT_IDS[persona]

adaptAgentStudioStream(response, options): Promise<StreamAdapterResult>

  • File: lib/agent-studio/stream-adapter.ts:42
  • Input: raw Agent Studio Response, {encoder, controller, persona, sessionId}
  • Output: {fullContent: string, sources: Source[]}
  • Purpose: parses Agent Studio's XML-tagged SSE chunks, strips control tags via StatefulStreamStripper, emits clean event: chunk {content} to the client
  • 🚨 F-038: this is where the hallucination audit SHOULD be wired in but currently isn't.

Session State (redis.ts)

getSessionState(sessionId): Promise<SessionState>

  • File: lib/search/redis.ts:105
  • Side effects: Redis GET
  • Observed timing: 41–53ms (warm)

updateSessionState(sessionId, updates: Partial<SessionState>): Promise<boolean>

  • File: lib/search/redis.ts:189
  • Pattern: partial merge into existing state
  • Called at: multiple points in both ignite functions

countLockedSignals(state: SessionState): number

  • File: lib/search/redis.ts:225
  • Logic: counts non-null signal fields (stack, scale, role, pain, brand, industry, product, architecture_concepts)
  • F-026 related: a "wrong but present" value counts as locked — no notion of "revisable" signals. Step 3 fix.

addAskedSignal(sessionId, signal): Promise<boolean>

  • Purpose: appends to state.asked_signals[] — no-repeat protocol
  • Side effects: Redis

resetSessionState(sessionId): Promise<boolean>

  • Purpose: called on first turn (history.length === 0) to clear prior session

Audit Primitives (audit.ts)

classifyLink(label, url, ctx: ClassifyContext): LinkDecision

  • File: lib/search/audit.ts:159
  • Input: link label text, URL, context (sources + mapData + allowedHostnames + healthCheck enabled flag)
  • Output: LinkDecision = {action: 'keep'|'replace'|'strip', reason, replacement?}
  • Purpose: THE core decision function for hallucination-prevention link audit. Called per link in every paragraph.
  • Decision tree: 1. Hostname not in ALLOWED_HOSTNAMES → strip 2. URL matches HALLUCINATED_CUSTOMER_SENTINEL → strip sentence 3. Generic label pattern matches → try upgrade via sources 4. Customer path pattern → verify against mapData or strip 5. Technical doc URL + not whitelisted path → strip 6. Default → keep
  • Called by: InlineStreamAuditor.auditParagraph (and formerly post-stream ContentAuditor.auditContent)

upgradeGenericLinkLabels(content, sources): string

  • File: lib/search/audit.ts:256
  • Replaces generic labels ("Algolia blog") with real doc titles from sources. Step 3 added a sibling scrubber normalizeLinkLabelSuffix() that runs right after to collapse duplicated | Algolia | Algolia suffixes (F-031).

normalizeLinkLabelSuffix(content): string — NEW Step 3 (F-031)

  • File: lib/search/audit.ts
  • Regex collapses [Title | Algolia | Algolia(| Algolia)*](url) to [Title | Algolia](url). Case-insensitive. Middle-of-label | Algolia untouched.

stripPromptInstructionLeaks(content): string — NEW Step 3 (F-032)

  • File: lib/search/audit.ts
  • Strips whole heading lines when a prompt-template label (Namedrop, VALUE SELL, Customer Example, SIGNAL EXTRACTION) appears at line-start followed by colon. Defence-in-depth against prompt leaks.

validateCustomerClaim(sentence, sources, mapData): { valid, reason? } — NEW Step 3 (F-038)

  • File: lib/search/audit.ts
  • Purpose: Hallucination guard for specialist responses (and Maverick). Returns valid=false when a sentence contains a non-Algolia capitalized name + metric (%, $, Nx) but fails ANY of: company in sources, metric in sources, case-study link in sentence.
  • Called from: InlineStreamAuditor.auditGrounding in a non-destructive sentence-level regex replace.

stripDanglingAttributions(content): string

  • File: lib/search/audit.ts:269
  • Purpose: strips orphaned "according to ..." or "— Source X" fragments after a link was removed

stripHallucinatedCustomerSentences(content): string

  • File: lib/search/audit.ts:300
  • Purpose: removes entire sentences that mention a fabricated customer (flagged by classifyLink with HALLUCINATED_CUSTOMER_SENTINEL).

findCanonicalCustomerUrl(label, mapData, sources): string | null

  • File: lib/search/audit.ts:119
  • Purpose: when the label sounds like a customer name, find the canonical algolia.com/customers/<slug> URL via mapData/sources

formatTraceLine(label, url, decision): string

  • File: lib/search/audit.ts:224
  • Purpose: produces the [AUDIT] KEEP | "Label" → URL | reason console lines visible in live trace
  • Called by: InlineStreamAuditor after every decision

Audit Classes (content_auditor.ts)

class InlineStreamAuditor

  • File: lib/search/content_auditor.ts:375
  • Constructor: new InlineStreamAuditor(contextSources: any[], products: any[])
  • Primary method: auditParagraph(paragraph: string): string
  • Runs: htmlAnchorsToMarkdown → match links → classifyLink per link → applyDecisionstripDanglingAttributionsstripHallucinatedCustomerSentencesupgradeGenericLinkLabels
  • Returns the cleaned paragraph (or the original if no issues)
  • Log lines emitted: [AUDIT] ══ paragraph in (N chars, M links) ══ / [AUDIT] KEEP|STRIP|REPLACE | ... / [AUDIT] ══ paragraph out (N chars) ══
  • Currently instantiated by: streamMaverickResponse only — F-038

class ContentAuditor

  • File: lib/search/content_auditor.ts:113
  • Post-Step-2 scope: link HEAD checks only (tone/grounding/confidence methods deleted in Step 2)
  • Primary method: async auditContent(content, sources, config): Promise<AuditResult> — runs AFTER stream complete

Helper Utilities

normalizePersonaID(raw: string|null|undefined): PersonaID

  • File: lib/search/persona_routing.ts:24
  • Maps frontend values like "solutions_engineer" → canonical "elena"

isSpecialist(persona: string): boolean

  • File: lib/search/persona_routing.ts:34
  • persona in ['elena', 'bruno']

guardIntent(query: string, currentIntent?: 'ae'|'technical'): 'ae'|'technical'

  • File: lib/search/domain_guardrails.ts:110
  • Scans query for TECHNICAL_MARKERS / NON_TECHNICAL_BLOCKERS to override LLM intent if LLM got it wrong

validatePersona(persona?: string|null): Persona

  • File: lib/search/domain_guardrails.ts:159
  • Defaults to 'maverick' if invalid input

trackView(params: {index, eventName, userToken, queryId, objectIds}): Promise<void>

  • File: lib/search/insights.ts:46
  • Fire-and-forget Insights event

Frontend Hooks

useChatStream({...})

  • File: src/hooks/chat/useChatStream.ts:20
  • Purpose: React hook — sends POST to /api/search (Maverick), parses SSE, updates message state
  • Events handled: pulse, pipeline_step, signals, sources, maverick_header, chunk, discovery, handoff_proposal, quality, audit, turn_snapshot, final, error
  • P-9 target: currently triggers 3 message re-renders per turn (signals → sources → final) — collapse to 1 reducer commit

useSpecialist({...})

  • File: src/hooks/chat/useSpecialist.ts:18
  • Purpose: React hook — sends POST to /api/search with persona=elena|bruno and either trigger=handshake or consent=true
  • Events handled: same set as useChatStream, specialist-variant payloads
  • Duplication (F-027): ~80 lines of handlers duplicated between the two hooks. Step 7 target.

parseStreamTags(content): {...}

  • File: src/hooks/chat/streamParsers.ts:7
  • Purpose: parses special inline markers (<specialist_handoff>, <discovery_pivot>) from the streamed markdown

normalizeSources(rawSources): Source[]

  • File: src/hooks/chat/streamParsers.ts:106
  • Purpose: normalizes source shape across Maverick + Specialist payloads

[EXPAND] Gaps to fill later

These are marked for deepening in a later doc pass:

  • retrieval_orchestrator.ts — full strategy selection tree (filtered → relaxed → fallback)
  • discovery_analyzer.ts — the exact qualification formula (F-035 target)
  • content_auditor.tsInlineStreamAuditor.auditGrounding behavior (specifics of attributed-metric detection)
  • persona_loader.ts — YAML load pipeline, cache invalidation logic
  • lib/llm/index.ts — the getLLMProvider() abstraction (not yet documented)
  • lib/security/rate-limiter — sliding window + bucket logic
  • lib/security/circuit-breaker — state machine

Cross-references

  • 02-Data-Flow — when each function fires in the request lifecycle
  • 03-Files-Reference — the file-level catalog
  • 08-Audit-Pipeline — deep dive on InlineStreamAuditor + F-038 gap
  • 09-Session-State — SessionState schema
  • 10-Known-Issues — F-026, F-031, F-035, F-038 all trace back to functions listed here