Plans/Maverick-Migration.md
Maverick → Agent Studio Migration (Path β)
⚠️ CORRECTIONS APPLIED 2026-04-27 (read first before any reference to this plan)
-
All
Zustandreferences should be read as "React Query + plain hooks + BroadcastChannel." R-UI research verified that the codebase does NOT use Zustand (no dependency inpackage.json). State persistence pattern: React Query for server state, plain hooks for local state, BroadcastChannel if cross-tab needed. The Zustand framing was speculative; no dependency was ever added. See vaultArchitecture/DecisionLog.mdDecision 15 (2026-04-27) and memoryfeedback_no_zustand_in_codebase.md. -
source_type allow-list = 8 of 9 live values (was 6 in earlier sections of this plan). Live facet query verified 9 actual values:
doc, support, blog, other, developer, customer_story, academy, resource, changelog. Maverick filter allows 8 (excludesdeveloper— code-exchange content; routes to Bruno later).support= public help-center articles at support.algolia.com (NOT internal Zendesk tickets). KNOWLEDGE CHARTER reworded accordingly inscripts/agent-studio/maverick-agent-config.ts. SeeArchitecture/DecisionLog.mdDecision 9 (2026-04-27). -
The Supabase ingestion task referenced anywhere in this plan is DELETED 2026-04-27. Stale config from rc2-algolia POC era. Per CLAUDE.md "current bootstrap data layer is explicitly wrong — replace, don't preserve." See
Architecture/DecisionLog.mdDecision 10. -
react-instantsearch v7 has a native
<Chat>widget (BETA) — replaces ~70% of current chat code. Step 6 of "Rest of P1" should pinreact-instantsearch@7.x.xexactly (BETA — breaking changes possible in minor versions). 3 SE-blocking unknowns must resolve first. SeeArchitecture/DecisionLog.mdDecision 14 and memoryproject_native_chat_widget.md.
Path β chosen 2026-04-26. Phase structure corrected 2026-04-27 — original P1 (prompt-only / orchestrator-stays / XML-preserved) was internally inconsistent with the locked decisions in the same plan (D-State, D-Hook, D-Telemetry, D-Retrieval all require the lift to be whole). P1 and P2a have been collapsed into a single "Lift Maverick" phase. The auditor architecture is now explicitly specified — see §"Auditor correction loop" below.
Custom backend (
orchestrator.ts+prompts/maverick.ts+stream-adapter.ts+ custom hooks) is the architectural debt that blocks UI modernization. Lift Maverick — the whole agent — into Agent Studio in a single phase. Port the auditor to a frontend hook with dossier-constrained corrective reroll. Install native<Chat>widget for Maverick path. End state of P1: Maverick runs entirely in Agent Studio, Maverick's frontend uses native<Chat>, orchestrator code shrinks to aprepare_dossierserver endpoint (or deletes if no telemetry shipping is retained). Then unify Elena/Bruno on the same pattern in P2.Read this BEFORE any work on agent prompts, agent configs, frontend chat hooks, or
lib/search/orchestrator.ts. This plan supersedes prior B-cleanup increments AND the original 2026-04-26 P1/P2a split.
Why Path β
The custom backend isn't extra plumbing — it IS Maverick. lib/search/orchestrator.ts (920 lines) runs Maverick's signal extraction, discovery state, retrieval, prompt assembly, hallucination auditing, and custom SSE side-channel events. As long as it exists:
- Frontend custom hooks (1,647 lines:
useChatStream+useSpecialist+stream-adapter) cannot be replaced with native<Chat>widget — they consume custom SSE events the widget can't produce - Elena/Bruno's "native" Agent Studio output gets reverse-converted back to legacy XML by
stream-adapter.ts'sconvertJsonToXmlTags()for frontend compatibility - Refactoring prompts inside this architecture is patching a sinking ship
Path β eliminates the architectural debt by putting Maverick into Agent Studio, then unifying the UI on the native widget.
Override: This plan explicitly overrides the CLAUDE.md "do not touch lib/search/prompts/maverick.ts without approval" rule. Approval was given by Arijit on 2026-04-26.
Override: This plan does NOT wait for R-gate (R1-R12) research to close. R-gate informs Maverick's index strategy and content tuning; it does NOT block the architectural migration.
Locked decisions
| ID | Decision | Rationale |
|---|---|---|
| D-Path | Path β: migrate Maverick to Agent Studio + unify UI on native <Chat> |
(A) was blocked by Maverick custom backend; (γ) was patching a sinking ship; (α) created dual integration patterns |
| D-Auditor (UPDATED 2026-04-27) | Auditor logic moves to frontend hook useStreamAuditor.ts with three layers: (1) pure text processing (link regex, label upgrade, dangling attribution strip, spacing fixes) runs synchronously per paragraph; (2) constraint-set validation against the dossier (entity grounding, study citation grounding, F-038 customer+metric, F-047 quote attribution) uses Zustand-resident dossier; (3) corrective LLM reroll triggered when whole units (sentences via F-038, paragraphs via F-047) would otherwise be dropped — sends a refinement message to Maverick via Agent Studio /completions with explicit dossier constraint set, replaces failed unit with corrected stream, capped at 2 reroll attempts. URL HEAD checks stay server-side as a small /api/validate-urls endpoint (preserves Redis 24h cache + network access). |
Today's auditor (verified by reading lib/search/content_auditor.ts 2026-04-27) silently drops whole sentences/paragraphs on F-038/F-047 failure — leaving incoherent gaps. Pure port would relocate the gap problem to the frontend. The dossier (sources + mapData + knownEntities) is already the constraint set in current code; lifting it to Zustand makes the reroll loop possible without a backend proxy. URL HEAD-check needs network + cache, so the only piece that stays server-side is that endpoint. |
| D-State | 8-signal state moves to frontend Zustand + localStorage, fed from prepare_dossier tool result |
Tool already returns structured signals object; frontend mounts directly on dossier card. Drops Redis dependency for session state. |
| D-Hook | Opening hook (<analogy_canvas>) drops from agent. Frontend renders animated welcome card with canned rotating hooks on first turn. |
Decorative, not behavioral. Frontend ownership simpler. |
| D-Telemetry | Drop per-turn telemetry endpoint. Rely on browser console traces (turn_snapshot) + Agent Studio's native conversation persistence. |
Fewer moving parts. If observability gap surfaces later, add a logging-service sink (Sentry/Datadog) with concrete questions. |
| D-Phasing (UPDATED 2026-04-27) | 3 phases: P1 = Lift Maverick — whole agent into Agent Studio: prompt + systemPrompt + all tools (prepare_dossier, ask_discovery_question, handoff_to_specialist, algolia_search_index) + 8-signal Zustand state + native <Chat> widget for Maverick path + auditor port with corrective reroll loop + orchestrator collapse. P2 = Specialist cleanup — Elena + Bruno prompt cleanup + render tools + native <Chat> for their paths (same pattern as P1 but smaller surface). P3 = Cleanup — delete useChatStream.ts, useSpecialist.ts, stream-adapter.ts, residual orchestrator code; anything that survived P1+P2 by accident. |
Original 2026-04-26 phasing split P1 (prompt-only) from P2a (tools + state) — but the P1 description ("orchestrator stays / XML preserved / hooks unchanged") contradicted D-State, D-Hook, D-Telemetry, D-Retrieval all locked in the same plan. The contradictory P1 was a half-step that didn't actually lift Maverick. Collapsing P1+P2a removes the contradiction and the artificial split. P2 inherits P1's tool-call frontend surface, so specialist work is faster after P1 ships. P3 is genuine cleanup, not new feature work. |
| D-Retrieval | Maverick's Atlas + retrieval collapse into client-side tool prepare_dossier(query, history). Pricing injection DROPPED (2026-04-27) — algolia.com/pricing not indexed (0 hits, confirmed via live search). Fix is crawling the pricing page, not injecting synthetic chunks. Qualification signals tracked by agent from conversation context — endpoint does NOT extract them. |
Agent decides WHEN to call (turn start). Endpoint is thin: Atlas search → ledger search with optionalFilters → return dossier. No LLM call inside. See DecisionLog 2026-04-27 for full architecture. |
Master migration table (18 components — phase column corrected 2026-04-27)
| # | Component | Today | Migration target | Phase | Effort |
|---|---|---|---|---|---|
| 1 | Agent host | Custom code (orchestrator.ts + maverick.ts) |
Agent Studio app 0EXRPAXB56 (same as Elena/Bruno) |
P1 | Low |
| 2 | Maverick prompt | prompts/maverick.ts 126 lines, templated |
instructions field + new systemPrompt (identity + hierarchy + information guard) |
P1 | Low |
| 3 | Two prompt modes (strict-discovery vs post-discovery) | Conditional templating from orchestrator | Single prompt with Multi-Intent Handling section; agent decides mode from signals | P1 | Low |
| 4 | Session state (8-signal) | Redis (rc2:session:*) |
Frontend Zustand + localStorage, fed from prepare_dossier tool result |
P1 | Medium |
| 5 | Pushback detection | detectPushback() server-side, before signal extraction |
Prompt instruction ("if user contradicts a prior signal, drop it") + tool re-runs naturally | P1 | Low |
| 6 | Signal extraction + Atlas + retrieval + pricing injection | Steps 3-5 in orchestrator | Single composite server-side tool prepare_dossier(query, history) returning { retrieval_entities, qualification_signals, sources, atlas_match, suggested_discovery_question, supported_entities, source_urls }. Two-layer extraction (locked 2026-04-27): retrieval_entities (8 fields — industry, stack, audience_role, pain, product, feature, solution, customer) map to Algolia index attributes and BOOST the search query. qualification_signals (5 fields — scale, buyer_role, timeline, budget, decision_process) guide the conversation but are NOT in the index. |
P1 | Medium-High |
| 7 | Discovery question generation | Inside signal extractor; output as <discovery_pivot> XML |
prepare_dossier returns suggested question; agent calls ask_discovery_question(signal, question) client-side tool when appropriate |
P1 | Medium |
| 8 | Specialist handoff | <specialist_handoff> XML, frontend parses |
handoff_to_specialist(target, scope_summary, signals) client-side tool; frontend renders handoff card |
P1 | Medium |
| 9 | Hallucination auditor | InlineStreamAuditor + ContentAuditor server-side; today drops whole sentences/paragraphs on F-038/F-047 failure (leaves gaps) |
Port logic to frontend hook useStreamAuditor.ts with corrective reroll loop (see §"Auditor correction loop"). URL HEAD checks stay server-side as /api/validate-urls endpoint (Redis cache preserved). |
P1 | Medium-High |
| 10 | Track view (Algolia Insights) | Server-side after retrieval | Client-side post-processing on frontend after sources render | P1 | Low |
| 11 | Telemetry + post-gen metadata | Steps 9-12 in orchestrator (Redis writes) | DROPPED — rely on console traces + Agent Studio native persistence | P1 | Low |
| 12 | Opening hook (<analogy_canvas>, turn 0) |
Mandatory first-turn prompt requirement | DROPPED from agent. Frontend animated welcome card with canned hooks. | P1 | Low |
| 13 | Custom SSE side-channel events | pulse, pipeline_step, audit_trace, turn_snapshot |
pulse → native widget loading state. pipeline_step/turn_snapshot → debug-only, drop from prod. audit_trace → emitted from new client-side auditor hook. |
P1 | Low |
| 14 | UI side-panels (X-Ray, vitals, dossier, discovery card, handoff banner) | Read custom SSE events from custom hooks | Read tool-call results + native widget events. Dossier reads from Zustand. Discovery + handoff cards = layoutComponents on tool calls. |
P1 (Maverick) / P2 (Elena+Bruno specialist panels) | High |
| 15 | Frontend integration | Custom hooks (1,647 lines: useChatStream.ts 639 + useSpecialist.ts 652 + stream-adapter.ts 356) |
Native <Chat> widget + per-tool layoutComponents |
P1 (Maverick path: install widget, wire Maverick agent ID, replace useChatStream.ts for Maverick view) / P2 (Elena+Bruno path) / P3 (delete dead code) |
High |
| 16 | LLM provider | Direct Gemini (getLLMProvider()) |
Agent Studio configured with gemini-2.5-flash-lite |
P1 | Low |
| 17 | API bundle rebuild | After backend changes | Each phase ends with node scripts/bundle-api.mjs + commit api/*.mjs |
All | Low |
| 18 | Verification per phase | None today | Live demo run + REAL_QUERIES.maverick + 3 supplementary cases (code, empty, injection) + auditor catches a known fabrication + reroll loop tested with intentional bad-grounding query, at each phase boundary | All | Low |
Plus Elena/Bruno work in P2 (separate from Maverick rows above; runs after P1 ships, NOT parallel — P2 inherits P1's frontend tool-call surface):
- Strip persona theatre / Unicode dividers / FORBIDDEN PHRASES / formatting pedantry
- Add systemPrompt with identity + hierarchy + Information Guard
- Define render tools: render_handshake (shared), render_evidence_card, render_implementation_plan, render_risk_list, conditional render_code_block (Elena), render_architecture_diagram (Mermaid in tool schema), render_phased_roadmap, render_recommendation (Bruno)
- Replace XML execute mode in prompts with tool-call instructions
- Frontend tool handling extends from P2a work
Phase plan (corrected 2026-04-27)
P1 is the whole lift. There is no orchestrator-stays half-step. There is no XML-preserved transition. The plan's original P1+P2a split was an artifact of conservative phasing language that contradicted the locked decisions; this is the corrected phasing.
Phase 1 — Lift Maverick (whole agent into Agent Studio)
Scope: Maverick — instructions + systemPrompt + all tools + 8-signal state + native <Chat> widget for Maverick path + auditor port with corrective reroll loop — runs entirely inside Agent Studio app 0EXRPAXB56. Orchestrator code shrinks to a prepare_dossier server endpoint plus a small /api/validate-urls HEAD-check endpoint (or the orchestrator file deletes entirely if those endpoints land in their own modules per D-Telemetry). XML emission gone. Output shape = Shape C (narrative text + client-side tool calls). Maverick frontend uses native <Chat> widget; specialist (Elena/Bruno) frontend stays on useSpecialist.ts until P2.
Steps:
-
Create
Maverickagent in Agent Studio app0EXRPAXB56: -templateType: "askai",widgetType: "chat"-useCache: true,memory.enabled: true,suggestions.enabled: true- Model:gemini-2.5-flash-lite(matches current Maverick) -
Write
instructions(per-turn prompt) per wiki §03 AskAI skeleton + §10 Maverick gap list: - AGENT ROLE (1 paragraph): Senior AE, value-selling, no code, no tech depth - RESPONSE GUIDELINES (5–7 bullets) - DATA SOURCES: 2 indices (algolia-central_atlas,algolia-central_enterprise_ledger); index split deferred to R-gate - MULTI-INTENT HANDLING: collapses today's strict-discovery vs post-discovery modes into one section; agent decides depth from signal completeness in the dossier returned byprepare_dossier- TOOL USAGE PRIORITIES: when to callprepare_dossier(start of every turn that needs grounding),ask_discovery_question(when dossier signal-completeness is below threshold AND answer warrants),handoff_to_specialist(topic exceeds AE scope, signals sufficient) - ACTION LIMITATIONS: no code, no JSON, no terminal commands, no inventing customers - FALLBACK RULE: verbatim refusal text when no grounding evidence - Strip from current prompt: persona theatre ("Vibrant, Energetic, Witty"), ASCII dividers, "VALUE-SELLING MACHINE" voice posters, markdown indentation pedantry (2-vs-4 spaces), FORBIDDEN PHRASES, mandatory<analogy_canvas>opener -
Write
systemPrompt(cross-turn rules): - AGENT IDENTITY (Senior AE, brief, no hex/emoji) - HIERARCHY OF INSTRUCTIONS (SW two-field pattern) - INFORMATION GUARD (the missing prompt-injection defense — wiki §03) -
Define tools (Shape C, per wiki §02 + §05 + §10): -
algolia_search_index(server-side native): 2 indices, source-type filters (marketing,blog,customer_story,guide,news,doc,changelog,video), descriptions per wiki §02 patterns -prepare_dossier(query, history)(client-side, round-trip): ononToolCall, frontend POSTs to/api/prepare-dossier. Endpoint does: (1) Atlas search → canonical entity resolution, (2) enterprise_ledger search with optionalFilters from Atlas results. No LLM call. No pricing injection (DROPPED 2026-04-27 — crawl gap, not code gap). Returns{ retrieval_entities, qualification_signals, sources, atlas_match, suggested_discovery_question, supported_entities: string[], source_urls: string[] }. Frontend writes to Zustand for auditor use, callsaddToolResult, agent grounds response.layoutComponentrenders dossier card (right panel). -ask_discovery_question(signal: string, question: string)(client-side): replaces<discovery_pivot>.layoutComponentrenders the discovery card; user's answer is captured into Zustand and surfaces in next turn's history. -handoff_to_specialist(target: "elena" | "bruno", scope_summary: string, signals: object)(client-side): replaces<specialist_handoff>.layoutComponentrenders the handoff banner and triggers the active-agent switch in app state. -
Drop opening hook from agent (D-Hook). Frontend
WelcomeHookCard.tsxrenders animated welcome with canned rotating hooks on first turn before any tool call. -
Migrate 8-signal state from Redis to frontend Zustand (D-State). - New store:
stores/signals.tswithlocalStoragepersistence keyed on conversation ID - Subscribe toprepare_dossiertool result; mergesignalsinto store - Pushback handling: prompt instruction handles it (per migration table row 5); store has aremoveSignal(name)action for explicit user contradictions detected client-side - Delete Redisrc2:session:*writes from currentsignal_extractor.tsand orchestrator. Keep Redis for the URL-health 24h cache only (used by/api/validate-urls). -
Port auditor to frontend with corrective reroll loop (D-Auditor — see §"Auditor correction loop" below for full architecture). - New hook:
src/hooks/chat/useStreamAuditor.ts— Layer 1 (pure text processing) + Layer 2 (constraint validation against Zustand-resident dossier) + Layer 3 (corrective LLM reroll on F-038/F-047 whole-unit drops) - New endpoint:app/api/validate-urls/route.ts— exposes the HEAD-check + Redis cache logic from currentContentAuditor.validateUrl. Frontend calls during stream completion; applies decisions client-side. - Delete server-sideInlineStreamAuditorinvocations from orchestrator -lib/search/content_auditor.tsshrinks to the URL-health endpoint code only (or fully deletes — that logic moves to the new endpoint module) -lib/search/audit.ts(the pure-function helpers) ports tolib/agent-studio/audit-shared.tsso the same code runs in browser + edge endpoint -
Install + wire native
<Chat>widget for Maverick path. - Addreact-instantsearch(latest beta) +algoliasearchlite client topackage.json- New page-level component:src/components/chat/MaverickChat.tsx— wraps<InstantSearch>+<Chat agentId={maverickAgentId} tools={maverickTools} />-toolsprop:prepare_dossier,ask_discovery_question,handoff_to_specialistwithonToolCall+layoutComponentfor each -assistantMessageLeadingComponentrunsuseStreamAuditoron each streamed paragraph - Pin widget version inpackage.json(BETA — read changelog before upgrading per wiki §08) - ReplaceuseChatStreaminvocation in Maverick view with<MaverickChat />.useChatStream.tsfile is NOT deleted in P1 — Elena/Bruno views still use it (viauseSpecialist). Deletion happens in P3. -
Orchestrator collapse: - Main streaming Gemini call → DELETED (Agent Studio
/completionsvia<Chat>widget owns it) - Signal-extraction Gemini call → DELETED. Qualification signals tracked by Agent Studio LLM from conversation context. Retrieval entities resolved by Atlas search inside endpoint. - Atlas + retrieval → moves into/api/prepare-dossier(Atlas search first, then ledger search with optionalFilters) - Pricing injection (injectPricingIfNeeded) → DELETED. Pricing must come from index via crawl ofalgolia.com/pricing. See DecisionLog 2026-04-27. - Discovery analysis → moved entirely to prompt MULTI-INTENT HANDLING +ask_discovery_questiontool - Telemetry endpoint → DELETED (D-Telemetry) -lib/search/orchestrator.tsend state: either deleted entirely, or shrinks to a thin re-export module if any helper is still imported elsewhere (audit imports during the lift; delete if no callers). -
Frontend custom SSE side-channel cleanup (Maverick path only):
useChatStreamno longer wired to Maverick view (replaced by<Chat>widget)- Custom events
pulse,pipeline_step,audit_trace,turn_snapshot— Maverick path uses native widget loading state instead ofpulse;audit_tracebecomes auditor hook console output for debugging;pipeline_stepandturn_snapshotare dropped from Maverick path. Elena/Bruno paths continue using these events viauseSpecialistuntil P2.
-
Rebuild API bundles:
node scripts/bundle-api.mjs→ commitapi/*.mjs. -
Verify (per
superpowers:verification-before-completion+superpowers:test-driven-development):REAL_QUERIES.maverickregression — every query inlib/search/__tests__/real-api-helpers.tsproduces a coherent answer with grounded citationsREAL_QUERIES.supplementary3 cases (code-only, empty-results, prompt-injection) handled gracefully- Discovery card fires on vague queries via
ask_discovery_questiontool call - Handoff banner fires via
handoff_to_specialisttool call when topic exceeds AE scope - Dossier card hydrates from Zustand on
prepare_dossiertool result - Information Guard refuses prompt-injection attempts (no system-prompt leak)
- Auditor catches a known fabrication — run a query that historically produced a hallucinated customer (e.g., the Walmart-ASOS pattern); confirm auditor flags it and triggers reroll
- Reroll loop verified — paragraph that fails F-038 is replaced by corrected paragraph in 1–2 reroll attempts; after 2 attempts, inline marker surfaces; no silent gaps in output
- Native
<Chat>widget renders streaming output, tool calls, andlayoutComponents correctly across desktop + mobile breakpoints
Risk: High. Largest single phase. P1 is intentionally the whole lift because the locked decisions don't permit a smaller honest scope.
Ships when: all 12 verification checks pass, bundles committed, no orchestrator code path executed during a Maverick session.
Phase 2 — Specialist cleanup (Elena + Bruno)
Scope: Apply wiki §10 refactor to Elena and Bruno + native <Chat> widget on their paths. P1's frontend tool-call surface is reused; this is the same pattern at smaller surface area. Runs after P1 ships, NOT in parallel.
Steps:
1. Strip persona theatre (hex codes, taglines, emoji descriptors) from Elena + Bruno instructions
2. Strip Unicode dividers (────)
3. Strip FORBIDDEN PHRASES + NEGATIVE CONSTRAINTS blocks
4. Strip markdown formatting pedantry
5. Collapse "DEEP-DIVE ANSWER FRAMEWORK" + "ANSWER STRUCTURE" overlap
6. Add systemPrompt with AGENT IDENTITY + HIERARCHY + INFORMATION GUARD (same skeleton as Maverick's)
7. Define render tools (Elena): render_handshake (shared), render_evidence_card, render_implementation_plan, render_risk_list, conditional render_code_block
8. Define render tools (Bruno): render_handshake (shared), render_evidence_card (shared), render_architecture_diagram (Mermaid syntax constraints in tool schema description), render_phased_roadmap, render_recommendation (verdict: GO | NO_GO | CAUTION)
9. Update Elena/Bruno prompts: replace XML execute mode with tool-call instructions
10. New components: ElenaChat.tsx, BrunoChat.tsx — each wraps <InstantSearch> + <Chat agentId={elenaOrBrunoId} tools={specialistTools} /> with their layoutComponents
11. Replace useSpecialist invocation in specialist views with the new components. useSpecialist.ts file deletion is P3.
12. Auditor reroll loop reuses useStreamAuditor from P1 — Elena/Bruno responses go through the same constraint-validation + reroll path
13. Rebuild bundles + commit
14. Verify: before/after fixture diff using REAL_QUERIES.elena + REAL_QUERIES.bruno + supplementary cases. Mermaid diagram from Bruno's render_architecture_diagram validates and renders. No silent drops in specialist responses.
Risk: Medium. Pattern is proven by P1; surface is bigger but the framework exists.
Ships when: verification passes, bundles committed.
Phase 3 — Cleanup
Scope: Delete dead code that survived P1+P2.
Steps:
1. Delete src/hooks/chat/useChatStream.ts (639 lines) — confirm no callers via grep
2. Delete src/hooks/chat/useSpecialist.ts (652 lines) — confirm no callers
3. Delete lib/agent-studio/stream-adapter.ts (356 lines, including convertJsonToXmlTags)
4. Delete lib/search/orchestrator.ts if not already deleted in P1
5. Delete lib/search/content_auditor.ts if its remaining endpoint code moved cleanly to app/api/validate-urls/route.ts
6. Delete lib/search/stream_processor.ts if no callers remain
7. Audit lib/search/prompts/maverick.ts — should have no callers after P1; delete the file
8. Audit lib/search/persona_loader.ts — Maverick consumer is gone; check Elena/Bruno consumers; delete if unused
9. Rebuild bundles + commit
10. Verify: full pipeline regression. All 3 agents work end-to-end via native widgets. Bundle size reduced by ~2,000+ lines of dead code.
Risk: Low. Pure deletion guarded by grep -r for callers.
Ships when: all dead code gone, bundles committed, no regression.
Dossier shape — 13-field two-layer extraction (locked 2026-04-27)
The prepare_dossier tool extracts 13 things from each turn's conversation context, split into two functional layers. The split is the architectural correction over PERSONAS.md's flat 8 — different fields do different jobs.
Layer 1: Retrieval Entities (8 fields, query-boostable)
These map to attributes in the Algolia indices (algolia-central_atlas + algolia-central_enterprise_ledger). Extracted values are passed as optionalFilters boosts on the search query, sharpening retrieval for the rest of the conversation. The customer field also drives Atlas direct lookups.
| Key | What it captures | Example |
|---|---|---|
industry |
Vertical / industry classification | "B2C apparel retail", "B2B SaaS", "media streaming" |
stack |
Current tech stack | "Shopify Plus", "Adobe Commerce", "headless Next.js" |
audience_role |
Content audience tier | "developer", "business buyer", "merchandiser", "executive" |
pain |
Primary pain category | "relevance", "indexing latency", "merchandising overhead" |
product |
Algolia product surface | "Search", "NeuralSearch", "Recommend", "Atlas" |
feature |
Specific Algolia feature | "typo tolerance", "rules", "A/B testing", "query suggestions" |
solution |
Solution category | "ecommerce search", "content discovery", "B2B catalog" |
customer |
Named customers in conversation | "Adobe", "Walgreens", "DocuSign" — used for Atlas-boost |
Layer 2: Qualification Signals (5 fields, conversation-guiding only)
These do NOT live in any index. They guide Maverick's conversation pacing, depth, and handoff decisions.
| Key | What it captures | Example |
|---|---|---|
scale |
Catalog volume / traffic order-of-magnitude | "120k SKUs", "8M monthly searches" |
buyer_role |
MEDDPICC role of the prospect | "economic buyer", "champion", "technical buyer", "user" |
timeline |
Urgency / when they need it | "Q1 launch", "evaluating", "6-month horizon" |
budget |
Budget signal | "enterprise budget secured", "comparing pricing", "tight" |
decision_process |
How they decide | "RFP", "pilot then expand", "VP-signs-off" |
Retrieval entity boost mapping (verified live API 2026-04-27)
7 of 8 retrieval entities boost the Algolia search via optionalFilters. Only pain is text-relevance only.
| Entity | Algolia facet | Mechanism |
|---|---|---|
industry |
facets.facet1 + industry_tags |
optionalFilter + vertical synonym expansion |
product |
category |
optionalFilter |
feature |
tags (partial) |
optionalFilter |
solution |
— | text relevance only (no dedicated facet) |
customer |
— | Atlas-direct lookup → drives atlas_match; no ledger facet |
stack |
tags (integrations) |
optionalFilter (Shopify, Salesforce, Adobe, React, etc.) |
audience_role |
buyer_personas + category |
optionalFilter |
pain |
— | text relevance only — shapes tone and framing, not query boost |
Retrieval entities have DUAL use: (1) search boost as above; (2) narrative shaping — industry/stack → case study selection; product/feature/solution → pitch focus; customer → validated name-drops; pain → framing depth and urgency. Both uses are active every turn.
Real source_type values in algolia-central_enterprise_ledger (verified 2026-04-27)
All valid: doc, support, blog, other, developer, customer_story, academy, resource, changelog
Maverick's Knowledge Charter filter (AE-appropriate): customer_story, blog, doc, academy, resource, other
Excluded from Maverick (specialist territory): support, developer, changelog
Known bug (Section 8 review fix pending): Current search_algolia_central filter in maverick-agent-config.ts still contains 4 nonexistent values: marketing, guide, news, video. Layer 1 unit test enterprise_ledger filter uses only valid source_type values is intentionally RED until fixed.
Implementation rules for /api/prepare-dossier
signal_extractor.ts(today's file) extracts both layers fromquery + history. Returns{ retrieval_entities: {...}, qualification_signals: {...} }. Each field isstring | null.- The Algolia search query is boosted using ONLY
retrieval_entities(asoptionalFiltersin thesearch_algolia_centralrequest).qualification_signalsNEVER touch the search query. customerfield drives a separate Atlas-direct lookup before the main search.- The
supported_entitiesarray in the dossier return shape is derived fromsources+mapDataat dossier-build time and lists every entity the agent is allowed to cite. - The
source_urlsarray is derived fromsources+mapDataand lists every URL the agent is allowed to cite.
Handoff threshold (locked 2026-04-27)
"Rich enough for a specialist to take over without re-discovery" — typically 3+ retrieval entities locked AND at least one qualification signal. Maverick exercises judgment; the threshold is heuristic, not numeric.
ask_discovery_question tool
Single signal enum carries all 13 values (8 retrieval entities + 5 qualification signals). The agent picks the one most likely to unlock the next concrete value step. Discovery questions are generated dynamically per turn (no Discovery Question Bank).
Auditor correction loop (the architecture for D-Auditor)
The auditor's "what does it do when it catches something" question — answered properly so the lift doesn't relocate a silent-drop bug from server to client.
Today's behavior (verified 2026-04-27 against lib/search/content_auditor.ts)
InlineStreamAuditorruns paragraph-by-paragraph during streaming. Pure text processing, no LLM, no network — designed to be <1ms per paragraph.- On link issues: classifies link as keep/rescue/strip_link/strip_sentence using
classifyLinkfromlib/search/audit.ts. Rescue replaces URL with canonical Atlas URL. Strip removes link or whole sentence. - On entity-grounding failure ("ASOS saw 15%" where ASOS is not in dossier): replaces company name with generic "leading companies"; replaces "study by Baymard" with "Industry research". Cheap masking, keeps the sentence flowing.
- On F-038 failure (customer+metric sentence with no case-study link in dossier): strips the whole sentence, returns empty string. Surrounding whitespace collapsed.
- On F-047 failure (quote+attribution block with no verifiable customer link): strips the whole paragraph, returns empty string.
ContentAuditor(post-stream) does HEAD checks on every kept link with 3s timeout + Redis 24h cache. Replaces unhealthy URLs with Atlas alternative or marks warning.- Failure mode today: silent gap in the response. F-038 and F-047 leave incoherent jumps. Today's auditor never asks the LLM to regenerate.
Target architecture (post-P1)
Layer 1 — Pure text processing, frontend, synchronous. Same logic as today's InlineStreamAuditor for cheap masking cases. Runs in useStreamAuditor hook on every streamed paragraph from the <Chat> widget. Operates on:
- Link regex extraction
- classifyLink decisions (port lib/search/audit.ts to lib/agent-studio/audit-shared.ts so it runs in browser + edge endpoint)
- Generic label upgrade ("click here" → entity name)
- Entity name masking ("ASOS" → "leading companies") for ungrounded sentences that don't trip F-038
- Study citation masking ("study by Baymard" → "Industry research")
- Verbatim-paste collapse (F-050)
- Spacing/heading fixes (F-051, F-052)
These are token-level operations that can run synchronously without breaking streaming UX. No reroll triggered.
Layer 2 — Constraint-set validation against the dossier (Zustand). Dossier shape from prepare_dossier includes:
- supported_entities: string[] — every customer name, study source, partner name extracted from sources and mapData at prepare_dossier time. This is the read-only constraint set for grounding checks.
- source_urls: string[] — every URL that appears in dossier sources. Constraint set for link grounding.
- signals: object — the 8-signal state for prompt-side reasoning (not auditor input)
- Other current prepare_dossier return fields
useStreamAuditor reads these from Zustand. Runs F-038 (customer+metric validation) and F-047 (quote attribution validation) per paragraph using ported validateCustomerClaim + validateQuoteAttribution helpers.
Layer 3 — Corrective LLM reroll on whole-unit failures. This is the upgrade over today's behavior. Triggered when: - F-038 would strip a whole sentence - F-047 would strip a whole paragraph - A URL HEAD-check fails AND no Atlas alternative is found
On trigger: 1. Pause visible rendering of the offending unit. Earlier paragraphs stay visible. 2. Construct refinement message:
"Your previous paragraph contained an unverified claim: '{verbatim offending sentence/paragraph}'. The dossier supports the following customer evidence only: {supported_entities list with their case study URLs}. Regenerate the paragraph using only this evidence. If no comparable customer is in the dossier, say so explicitly using the adjacent-vertical pattern from your instructions. Do NOT introduce new entities, do NOT cite sources outside the supported list." 3. Send refinement message via Agent Studio
/completionsto the Maverick agent (same agent ID, same conversation ID — so the agent has full context, not a fresh conversation). 4. Stream the corrected unit back via<Chat>widget; it replaces the offending unit in the visible thread. 5. Re-run Layer 1 + Layer 2 on the regenerated unit. 6. Cap: 2 reroll attempts per unit. If second pass also fails the same check: drop the offending claim entirely and surface an inline marker (<span class="audit-marker">⚠ couldn't verify this claim against the provided sources</span>). Honest, user-visible, no fabrication, no silent gap. 7. Log the reroll event to console ([AUDIT] REROLL paragraph N attempt M) for debugging during P1 verification.
Layer 4 — URL health, server-side. New endpoint app/api/validate-urls/route.ts:
- POST { urls: string[] } → returns Record<string, boolean>
- Wraps current ContentAuditor.validateUrl (3s timeout HEAD check + Redis 24h cache)
- Called by useStreamAuditor after Layer 1+2 pass on a paragraph (or batched at end of streaming for efficiency — TBD during P1 build)
- Frontend applies decisions: unhealthy + Atlas alternative exists → swap URL; unhealthy + no alternative → trigger Layer 3 reroll for the sentence containing the link
Why this lives in frontend (with the URL-health endpoint exception)
- Dossier is already there. Per D-State,
prepare_dossierresult lands in Zustand on the frontend. - Stream is already there. Per D-Path, the
<Chat>widget streams to the browser. - Reroll is just another
/completionscall. Same Agent Studio endpoint the widget already uses; no orchestrator proxy needed. - HEAD checks need network + Redis cache. That's the only piece that has to stay server-side. Wrapping it in a thin endpoint preserves the cache without dragging the rest of the orchestrator with it.
Constraint set integrity
The reroll is only as good as the dossier's supported_entities and source_urls arrays. Hard requirement on prepare_dossier: these arrays must be derived deterministically from sources + mapData at dossier-build time and must contain every entity the agent could legitimately cite. Underpopulating them = false-positive reroll loops. Overpopulating them = ungrounded claims slip through. Build-time tests: feed known dossier inputs, assert exact supported_entities and source_urls outputs.
Test architecture — 3-layer TDD (established 2026-04-27)
Tests are written before implementation (RED observed first), not after. Every section lock in Phase A adds test cases to the relevant file.
| Layer | File | Runs when | Gate |
|---|---|---|---|
| 1 — Unit | scripts/agent-studio/__tests__/maverick-agent-config.test.ts |
Always (npm test) |
Standard vitest |
| 2 — Behavioral | scripts/agent-studio/__tests__/maverick-agent-behavior.test.ts |
Phase C (live agent) | RUN_LIVE_TESTS=true + MAVERICK_AGENT_ID set |
| 3 — Contract | scripts/agent-studio/__tests__/maverick-agent-contracts.test.ts |
Partially now; fully Phase D | PREPARE_DOSSIER_ENDPOINT for live dossier tests |
Layer 1 current status: 49 tests, 48 pass, 1 intentionally RED (Section 8 source_type filter bug).
Layer 2 covers: tool call ordering, discovery protocol (one question per turn), knowledge charter compliance, handoff routing (elena vs bruno), information guard, fallback rule, pushback handling, latency.
Layer 3 covers: handoff↔dossier shape contract, ask_discovery_question↔13-field enum, index name consistency, live /api/prepare-dossier return shape.
Latency threshold (open): placeholder 8000ms total / 3000ms first token — needs Arijit confirmation.
Rule: No section is considered locked until its behavioral commitments have at least one test case. Going forward, section lock = section text + test.
Open items deferred / out of scope
- Index split (wiki §06): still gated on R1-R6. Maverick uses current 2 indices through all 3 phases.
- Platform features (memory, suggestions, useCache): turn on in P2a/P2b inside Agent Studio config. Test impact case-by-case.
- Mermaid validation strategy: addressed in P2b — strict syntax in tool schema description, frontend
layoutComponentvalidates and rejects invalid syntax with fallback. - R-gate research (R1-R12): continues independently. Informs index split + content tuning, NOT migration architecture.
What this plan replaces
- Prior B-cleanup increment plan (Increment 1 handshake / Increment 2 Elena / Increment 3 Bruno) — superseded by P2
- Prior recommendation of (A) native
<Chat>migration as part of B — corrected; (A) is gated on Maverick migration which IS this plan - Prior assumption that Maverick is untouchable — explicitly overridden 2026-04-26
- Prior R-gate-blocks-WS1-WS9 sequencing — R-gate continues but does NOT block migration
- Original 2026-04-26 P1/P2a/P2b/P3 phasing (corrected 2026-04-27): P1 ("prompt-only port / orchestrator stays / XML preserved / hooks unchanged") was internally inconsistent with locked decisions D-State, D-Hook, D-Telemetry, D-Retrieval. Collapsed P1+P2a into single "Lift Maverick" phase. P2b → P2. Old P3 cleanup operations split between P1 (Maverick-specific dead code on its path) and new P3 (Elena/Bruno-specific + final cleanup).
- Auditor "port to frontend" sub-decision from 2026-04-26 (sharpened 2026-04-27): Today's auditor silently drops whole sentences/paragraphs on F-038/F-047. A pure port relocates the bug. Replaced with the Layer 1/2/3/4 architecture in §"Auditor correction loop".
Cross-references
- Wiki entry point:
Knowledge/AgentStudio/README.md - Maverick prompt anatomy:
Knowledge/AgentStudio/10-our-agents-vs-best-practice.md - Tool patterns:
Knowledge/AgentStudio/02-tool-types.md - Output shape rationale:
Knowledge/AgentStudio/05-output-shape-decision.md - Multi-index decision:
Knowledge/AgentStudio/06-multi-index-routing.md - UI refactor wiki:
Knowledge/AgentStudio/12-ui-and-instantsearch.md - Repo-side operational state:
SESSION.mdat repo root - Decision Log entry:
Architecture/DecisionLog.md(2026-04-26 — Maverick migration to Agent Studio)