Ask Phoenix — Public Knowledge-Base Chat Assistant
Audiences: investor, client, partner, developer
Ask Phoenix is the public-facing chat surface that lets anyone — investor, client, partner, developer — query the Dudoxx HMS knowledge base in natural English / German / French and get cited answers grounded in the 12-layer technical corpus. Available as a floating side panel on every
/docs/[layer]/[topic]page and as a full-page chat at/docs/ask.
Business Purpose
The HMS knowledge base contains ~100 topic pages spread over 12 architectural layers. Static documentation works for users who already know what to look for — but most prospects, clients, and partners do not. Ask Phoenix lowers the discovery cost from "browse 12 sections" to "type a question". It produces answers with clickable citation chips that deep-link to the exact KB topic + heading the claim came from — so the chat surface accelerates discovery without replacing the structured docs.
Three concrete use cases:
- Investor due diligence: "How does FHIR partitioning work?" returns a
short grounded answer with chips into
[02-data-and-fhir/fhir-partitions-tenancy]. - Partner onboarding: "What components must I change to add a new TUCAN tool?" returns multi-step guidance with chips into ai-tucan layer topics.
- Sales conversations: "Why does ddx-web never talk to HAPI FHIR directly?"
returns the security rationale with chips into
[03-security-and-tenants/...].
Audiences
- Investor: Ask Phoenix is the public demo of the Dudoxx KB. Try it at
https://<host>/en/docs/ask. Every answer cites its sources — the assistant cannot fabricate. Refusal-class questions ("What is OpenAI's pricing for GPT-5?", "What is Dudoxx's revenue?") are politely declined, not hallucinated. - Client / partner: Self-service onboarding. Skim a KB topic, click the Phoenix launcher in the page chrome, ask a follow-up question. The panel preserves chat state across topic navigations within the same session.
- Developer: Ask Phoenix shares the same Mastra agent runtime
(
streamPhoenixinddx-web/src/mastra/agents/phoenix.ts:84) as the internal TUCAN system. To extend the assistant's knowledge, ingest new topic chunks into thedudoxx_kb_techdocsQdrant collection; the chat surface picks them up immediately — no front-end change needed.
Architecture
Public KB surfaces
| Route | Renders |
|---|---|
/[locale]/docs/[layer]/[topic] | topic reader page — KbShellPhoenixLauncher button in page chrome · AskPhoenixPanel (createPortal floating drawer) |
/[locale]/docs/ask | full-page chat surface — PhoenixFullPageChat (centered chat column) |
/api/phoenix/chat (POST) — validate request · streamPhoenix({ messages, env }) → Response (SSE)
Mastra agent: Phoenix
| Setting | Value |
|---|---|
| model | dudoxx-gemma, temp 0.3, enable_thinking: false |
| tools | kb-plan (private), kb-search (cited) |
stopWhen | steps.length >= 8 |
UI surfaces
AskPhoenixPanel (ddx-web/src/components/kb/AskPhoenixPanel.tsx:37)
- Floating side panel mounted via
createPortal. - Triggered by
KbShellPhoenixLauncherbutton in the topic reader chrome. - Shows "Planning…" chip when the agent is in the hidden kb-plan step
(
PhoenixToolPartshort-circuitstool-kb-planto a chip-only render). - Shows expandable "Searching…" chip for each kb-search call.
- Renders the final answer via
PhoenixAnswer→MarkdownRenderer(same chrome as topic pages — code/mermaid/admonitions look identical). - "Open full page" link routes to
/docs/askpreserving the conversation state via search params.
PhoenixFullPageChat (ddx-web/src/components/kb/PhoenixFullPageChat.tsx:34)
- Centered chat column (max-w-prose) for distraction-free reading.
- Hero header instead of drawer chrome.
- Routed at
/[locale]/docs/askviaddx-web/src/app/[locale]/docs/ask/page.tsx:27. - Same Mastra agent backend as the side panel — only the layout differs.
PhoenixToolPart (ddx-web/src/components/kb/PhoenixToolPart.tsx:42)
- Renders each tool-call step in the chat transcript.
case 'kb-plan'→ chip-only render witht('kb.phoenix.planning')(i18n keys insrc/locales/{en,de,fr}/kb.json: "Planning…" / "Plane…" / "Planification…"). Never expands kb-plan args into the visible message — that would leak the hidden plan and inflateassistant_textlength (AC-10 invariant).case 'kb-search'→ expandable chip showing the query and top hits.
PhoenixAnswer (ddx-web/src/components/kb/PhoenixAnswer.tsx:67)
- Markdown renderer for the assistant message.
- Detects
[layer/slug]citation chips and converts them into clickable<Link>elements pointing to/[locale]/docs/[layer]/[slug]. - Reuses
MarkdownRendererfrom the topic-page surface so code blocks, admonitions, and tables share visual chrome.
Tech Stack & Choices
| Layer | Choice | Rationale |
|---|---|---|
| Client streaming | AI SDK v6 useChat + DefaultChatTransport | Auto-handles SSE reconnect, message ordering, tool-call streaming |
| API route | Next.js 16 route handler (/api/phoenix/chat) | Lives in ddx-web (port 6200). Browser never talks to ddx-api here — the Mastra agent runs in Next.js |
| Agent backend | Mastra agent in ddx-web (src/mastra/agents/phoenix.ts) | Same Mastra runtime as TUCAN — one source of truth for AI orchestration |
| Vector store | Qdrant dudoxx_kb_techdocs | Curated technical-docs collection. Re-ingested from ddx-presentation/knowledge-base/ content |
| Markdown | MarkdownRenderer reused from topic-page surface | Visual consistency between static docs and chat answers |
| i18n | next-intl with kb.* namespace | EN/DE/FR all carry kb.phoenix.planning, etc. Locales in lockstep |
| Citations | [layer/slug] chip syntax detected client-side | Lightweight — no per-token parsing required. The agent emits chips verbatim from kb-search results |
Why a side panel AND a full-page chat?
Side panel preserves topic context (the user keeps reading the docs while
asking follow-up questions). Full-page chat is the discovery entrypoint —
linked from the landing page / sales materials, no topic context required.
Both share the same Mastra agent and the same /api/phoenix/chat route, so
there is one place to fix any Phoenix behaviour.
Data Flow
| # | Step |
|---|---|
| 1 | User opens /en/docs/02-data-and-fhir/fhir-partitions-tenancy. |
| 2 | Clicks KbShellPhoenixLauncher → AskPhoenixPanel mounts via createPortal. |
| 3 | User types "How does the multi-tenant data isolation work?" |
| 4 | useChat POSTs to /api/phoenix/chat with messages array. |
| 5 | Server-side: streamPhoenix → first step is the hidden kb-plan tool call: { question: '...', sub_questions: ['How is FHIR partitioning implemented?', 'How is data isolated between tenants in HAPI FHIR?'] }. PhoenixToolPart receives a tool-kb-plan stream event → renders chip 'Planning…' (or 'Plane…' / 'Planification…' depending on locale). |
| 6 | Then 2 kb-search calls fire — one per sub_question. PhoenixToolPart renders 'Searching…' chips with expandable detail. |
| 7 | Final answer streams in as text-delta chunks. PhoenixAnswer renders the markdown, converts [02-data-and-fhir/fhir-...] into clickable links to the actual topic pages. |
| 8 | User clicks a citation chip → navigates to the cited topic page. |
| 9 | The side panel persists; the user can continue the conversation about the new topic with full context. |
Implicated Code
Mandatory ≥3 file:line citations.
ddx-web/src/app/[locale]/docs/ask/page.tsx:27—PhoenixAskPageserver component. Builds metadata viagenerateMetadata(line 16), renders<PhoenixFullPageChat />client component.ddx-web/src/components/kb/PhoenixFullPageChat.tsx:34—export function PhoenixFullPageChat(). WiresuseChat({ api: '/api/phoenix/chat', transport: new DefaultChatTransport() }), renders header + chat column + composer.ddx-web/src/components/kb/AskPhoenixPanel.tsx:37—export function AskPhoenixPanel(...). Side-panel variant usingcreatePortalto mount outside the topic-page tree.ddx-web/src/components/kb/PhoenixToolPart.tsx:42—export function PhoenixToolPart({...}). The function-level branch onpart.type === 'tool-kb-plan'returns a chip-only fragment;tool-kb-searchrenders an expandable detail. Critical for AC-10 (no sub-question leak into visible answer).ddx-web/src/components/kb/PhoenixAnswer.tsx:67—export function PhoenixAnswer({ text }). Markdown rendering with citation chip detection + deep-link conversion.ddx-web/src/app/api/phoenix/chat/route.ts:21—POSThandler. Builds env, callsstreamPhoenix, returns the streamed SSE response.ddx-web/src/locales/en/kb.json(+ de, fr) — i18n keyphoenix.planning= "Planning…" / "Plane…" / "Planification…". All three locales in lockstep.
Operational Notes
Public route exposure
/[locale]/docs/ask is a public route — no auth required. The Mastra
agent runs server-side in ddx-web (Next.js 16 route handler), and the
Qdrant query happens server-side too. The browser never talks to Qdrant or
the LLM gateway directly. This means:
- Anyone can use Ask Phoenix without signup.
- Rate limiting must live in the
/api/phoenix/chatroute (currently inherits Next.js platform-level limits — consider explicit per-IP throttling if traffic grows). - No PII should ever appear in user questions (the route does not strip PII before passing to the LLM). Document this in the privacy note on the landing page.
Pitfalls
- Citation chips that don't deep-link:
[layer/slug]chips MUST use the raw slug from the kb-search result (layer: 'infrastructure',slug: 'infra-postgres'). Inventing prefixes like[01-infrastructure/infra-postgres]breaks the markdown chip detection. The Phoenix system prompt forbids invented prefixes; verify by spot-checking the eval report. - Locale fallback gotcha: if a user's browser is
it-ITand the route lacks an Italian locale, next-intl falls back toen. The Mastra agent detects the language from the user's typed message regardless — so a user typing in French on an/en/docs/askroute gets a French answer. This is by design (the agent's output-language-strict rule). - Tool-call chips can flash on slow networks: if the SSE stream stutters between the kb-plan event and the first kb-search event, users see "Planning…" alone for ~1 second. This is acceptable UX (clearly conveys "working") but consider a smoother transition if feedback flags it.
- No conversation persistence: each
useChatinstance keeps history in React state only. Refresh = new conversation. Persisting to backend would require anai-chat-sessions-style record but is OUT of scope for the public surface.
Validation
# Build sanity cd ddx-web && pnpm build # i18n keys present in all 3 locales for L in en de fr; do jq -r '.phoenix.planning // "MISSING"' ddx-web/src/locales/$L/kb.json done # Type + lint Phoenix UI surface pnpm tsc --noEmit pnpm eslint src/components/kb/*.tsx src/app/[locale]/docs/ask/page.tsx
Common defects
- "Planning…" chip never disappears → kb-plan tool returned ok:false on first call and the agent stopped. Check the Mastra agent prompt's rule 3 ("retry kb-plan exactly once on ok:false using suggestion").
- Empty answer with 0 citations → falls into the Q6/Q8 anti-loop class. Verify the tool-layer guard is wired (see phoenix-agent-architecture Operational Notes).
- Citation chip 404s on click → the slug in the chip doesn't match
any KB topic page. Cause: a kb-search hit whose
slugfield is stale or the chunk was ingested without a matching markdown page inddx-presentation/knowledge-base/. Re-run the inventory check.