Case file 07 / Client Delivery
Muriersilk AI Sales Concierge
Retrieval-grounded commerce concierge
A reusable commerce concierge that streams through CopilotKit and AG-UI, retrieves products and knowledge through a guarded hybrid pipeline, hydrates current commercial facts in the browser, preserves durable context, and hands operations to Chatwoot.
- CLASSIFICATION
- Client Delivery
- PUBLIC STATUS
- Local end-to-end verified / production rollout pending
- CASE
- 07 / 07

The problem
A useful commerce assistant must help with discovery and care questions without inventing prices, recommending ineligible products, turning marketing copy into medical claims, or coupling model failures to checkout.
Muriersilk AI Sales Concierge / system view
How the system works
Medusa and PostgreSQL remain commercial truth. The indexer projects products and knowledge into filtered dense and sparse recall lanes, RRF bounds the candidate set, and the planner can select only stable product IDs.
- 01SourceMedusa + PostgreSQL
Canonical products, content, and policy
- 02CapabilityOutbox + indexer
Incremental rebuildable projection
- 03CapabilityQdrant dense
Nomic v1.5 via llama.cpp
- 04CapabilityBM25 / FTS sparse
Exact terms and catalog language
- 05GuardrailTaxonomy filters
Product type and material first
- 06CapabilityRRF candidate pool
Products 10, knowledge 4 to 5
- 07GuardrailGuarded planner
Selects only 1 to 3 stable product IDs
- 08OutcomeBrowser hydration
Live price, currency, image, and URL
My role
I defined the product boundary, retrieval architecture, two-run interaction contract, context budgets, catalog and safety guards, multi-tenant runtime, operator workflow, and release evidence. AI agents produced most implementation code under those constraints.
Interaction evidence
The retrieval decision and the safety boundary, shown in the product.

The planner and semantic retrieval preserve the glove intent despite the misspelling, then the eligibility gate keeps the displayed set to real silk glove SKUs.

When the user asks about eczema, the assistant does not turn product copy into treatment advice. It states the medical boundary and continues with material and design facts.
Capabilities
- 01
CopilotKit and AG-UI streaming with activity-aware customer states
- 02
Dense and BM25 sparse product retrieval fused with reciprocal rank fusion
- 03
Stable product ID selection followed by live Medusa hydration
- 04
Parallel product and knowledge retrieval with bounded candidate sets
- 05
Durable conversation state, turn packs, trace, replay, and recovery
- 06
Catalog eligibility and medical-claim safety guards
- 07
Chatwoot replay, assignment, internal notes, takeover, and embedded operations
Product decisions
- 01
Keep PostgreSQL and Medusa as the commercial source of truth while treating Qdrant as a rebuildable retrieval projection.
- 02
Let the model select stable product IDs and reasons, then let the browser hydrate current names, images, prices, currency, and URLs.
- 03
Compile planner and final-generation context below a 28k hard budget so provider input remains below the 32k pricing boundary.
- 04
Share one capability core across dedicated and multi-tenant profiles while routing from trusted origins and Chatwoot accounts with fail-closed behavior.
- 05
Keep business prompt editing in Chatwoot while platform safety policy, tool schema, and tool permissions remain outside operator control.
Evidence
- 01
Cold local indexing covered 35 active products, 15 categories, and 125 blogs. Canonical reconciliation later covered 175 of 175 entities with zero missing, zero unexpected, zero pending outbox items, and zero unresolved dead letters.
- 02
The initial local generation produced 866 Qdrant points and 866 indexed vectors with a green health state.
- 03
A real provider smoke completed Run A, exact glove cards, Run B, a second product-card turn, and replay. The highest observed compiler input was 25,822 tokens and the highest provider prompt was 5,785.
- 04
Backend fresh verification passed 70 test suites and 572 tests, followed by build:chatbot.
- 05
Storefront verification passed 56 test files with 860 unit tests, four Playwright browser scenarios, and the production build.
- 06
API readiness reported acceptingInput=true and retrievalMode=hybrid.
Honest limits
- !
The evidence covers local end-to-end browser use, Compose services, and a real OpenAI-compatible provider. It does not claim deployment to Muriersilk production.
- !
Production rollout, operational monitoring, and live customer traffic remain pending.
- !
Customer, order, payment, credential, private environment, and personally identifiable data are not published.
From brief to system boundary
The brief was not "add a chatbot." It was to create a reusable sales concierge that could answer care questions, help a shopper discover products, remain useful across page changes, and give an operator a clean takeover path.
The public experience is intentionally narrow. It appears only on English locale routes. The customer sees a persistent, enlarged popup that can hold real product cards, not a generic text bubble. Waiting states describe business work such as "Finding suitable pieces" and "Checking care guidance" instead of exposing model or tool jargon.
The safety boundary is equally deliberate. This is a read-only pre-sales surface. It does not reach customer, order, cart, payment, or checkout data. The runtime shares a repository with Medusa, but chatbot-api, chatbot-indexer, Qdrant, and the local model server run as separate services so a retrieval or generation failure does not become a checkout failure.
The first product and runtime designs were committed separately (0259d8d and 5fa56a3) before the contracts and retrieval implementation landed. That sequence matters: it kept product behavior, source-of-truth ownership, and failure boundaries visible before code made them expensive to change.
Retrieval architecture and source-of-truth split
PostgreSQL and Medusa own commercial truth. Qdrant is a rebuildable projection. Product, category, blog, landing-page, and policy changes enter a durable PostgreSQL outbox, wake the indexer through LISTEN/NOTIFY, debounce bursts, and remain recoverable through periodic sweeps.
The first retrieval profile combines dense Nomic Embed Text v1.5 vectors with BM25/FTS sparse evidence and reciprocal rank fusion. The embedding model runs as a Q6_K GGUF through the OpenAI-compatible llama.cpp endpoint, with the correct query and document prefixes. Product retrieval returns at most ten candidates. Knowledge retrieval returns four or five pieces of evidence. The model selects one to three products from the candidate set for each relevant turn.
That last phrase is important. The limit is per turn, not per conversation. A second shopping question can produce a fresh set of one to three cards.
Product and knowledge retrieval can run in the same turn. Pure conversation does not pay that cost. A planner decides whether the turn needs product retrieval, knowledge retrieval, both, or neither.
Why the first UI and mock path failed
The early browser path looked close to complete, but it hid several contract failures.
CopilotKit styles were not mounted correctly. A custom class landed on ChatView rather than the popup shell. The official header used three equal slots that could not hold a multi-line brand treatment. Scoped preflight rules flattened rounded controls. A newly created thread was also treated as an explicit persisted thread, so the welcome state disappeared.
The correction kept the official components, imported the v2 styles at the right boundary, targeted stable data attributes, reorganized the header slots, and applied only local CSS overrides. Thread creation and thread restoration became different states again.
The first mock commerce adapter was more dangerous. It hydrated different product IDs into the same demo scarf, masking real region selection, price, image, and CORS behavior. The browser path moved to real Medusa data with an explicit USD region and the secure local storefront origin. Only then could product cards count as evidence.
A second silent failure appeared when the llama.cpp base URL omitted /v1. Dense retrieval quietly became bm25_only. Readiness now verifies the configured mode and refuses to present a degraded path as hybrid.
Planner, Run A, hydration, and Run B
The interaction contract evolved into two bounded model runs for shopping and evidence turns.
Run A plans the turn, retrieves evidence, and emits stable product IDs. The browser hydrates those IDs from Medusa and renders current image, name, price, currency, and URL. Run B receives the rendered product context and writes the customer-facing explanation and recommendation reasons.
The model never owns the final price, currency, image, or link. This removes stale commercial fields from the generation contract and keeps a product card correct even when catalog data changes after indexing.
The wire path also needed hardening. CopilotKit can omit additionalProperties: false from a tool schema, so the backend applies a safe normalization before validation. The built-in agent can repeat lifecycle events, so the runner deduplicates them without replacing the real error code.
Refresh and retry exposed a race between the two runs and a pending product tool. The frontend now restores pending tool state. The service applies atomic transactions, idempotency, replay, and lease recovery. Commits cd7d700, a1c155a, and 727601c capture the storefront recovery path; the backend race and continuation work progressed through f694e5c, 592fe81, 153f39e, and 1015281.
Follow-up language created a harder problem. "Show me some" or "silk" is not a complete new search. The planner now reads structured conversation state and continues an unfinished task. It does not depend on brittle keyword rules for words such as "one", "it", or "them".
Context and cost boundary
The provider moves to a higher pricing tier above 32k input tokens. Both the planner and final generation therefore pass through a Context Compiler with a hard 28k budget.
The compiled context has four layers:
- Stable policy and deterministic tool schema, arranged as a cacheable prefix.
- Recent complete turns, current page, and unfinished tool state.
- Structured medium-term intent, preferences, constraints, displayed product IDs, accepted or rejected products, and unresolved questions.
- Product and knowledge evidence for the current turn.
The complete transcript remains durable in PostgreSQL. It is not copied into every provider call.
If a turn exceeds budget, the compiler deduplicates retrieval, truncates knowledge evidence, folds old conversation into structured summary, then compresses product evidence. It does not discard the current customer message, hard constraints, selected product IDs, unfinished tool state, or system policy.
The highest observed compiled input was 25,822 tokens. The highest observed provider prompt was 5,785. A separate cache check sent the same Alibaba-compatible request twice: both requests reported 6,320 prompt tokens, and the second reported 6,314 cached tokens.
Safety and catalog eligibility
One failure made the difference between semantic similarity and a usable retail system obvious. A shopper asked for gloves, but an early dense result included an eye mask and bonnet because all three were close to "silk" and "sensitive skin."
The fix was not another instruction asking the model to try harder. The planner gained a canonical product taxonomy. Qdrant applies product-type and material filters before dense and FTS fusion. A final eligibility guard rejects any selection outside the candidate set and the requested facet. The verified glove + silk request returned four real glove SKUs.
Tenant policy also records catalog truth. Muriersilk sells silk, so the assistant cannot suggest that cotton is an available alternative.
Product descriptions can contain language about eczema or dry hands. Run B may explain material, touch, friction, and intended design. It may not convert that copy into a claim that a product treats eczema, and it may not present a product as medical advice. The two interaction frames above show both sides of that boundary: the planner preserves a misspelled shopping intent, then the answer remains safe when the user asks for a treatment claim.
Chatwoot operations and reuse
Chatwoot Community Edition is the only operator workspace. The team did not build a second admin product.
Operators can search and replay conversations, assign ownership, add internal notes, and take over a customer thread. An embedded Dashboard App adds four operational views: Need to know, Qdrant Index Inspector, Context Inspector, and Prompt Settings.
Administrators can edit tenant business prompts. A save creates an immutable revision that applies to the next logical turn and can be rolled back. Every planner, retrieval, selection, display trace, and Run B generation inside one logical turn remains pinned to the same prompt revision. Normal agents see the setting but cannot change it.
Tool schemas, tool permissions, and platform safety policy are not operator-editable.
The same code supports shared multi-tenant and dedicated deployment profiles. Tenant routing trusts explicit storefront origins and Chatwoot accounts and fails closed when those anchors are missing or ambiguous. Tenant-composite keys, scoped dashboard sessions, atomic webhook receipt and message transitions, and commit-only broadcasts prevent one tenant or operator session from leaking into another.
Verification evidence
The cold local ingest covered 35 active products, 15 categories, and 125 blogs, for 175 entities. The initial retrieval generation produced 866 Qdrant points and 866 indexed vectors with green health.
The canonical Context Pack V2 reconciliation later reported:
- 175 of 175 entities covered
- zero missing and zero unexpected entities
- zero pending outbox items
- zero unresolved dead letters
acceptingInput=trueretrievalMode=hybrid
The fresh backend gate passed 70 test suites and 572 tests, followed by build:chatbot. The storefront record passed 56 test files with 860 unit tests, four Playwright browser scenarios, and the production build.
The real Alibaba-compatible provider smoke completed Run A, rendered actual glove cards from Medusa, completed Run B, rendered another product-card turn, and replayed the conversation. The final Context Pack V2 operation is recorded in commit 77561bd.
Honest limits
This is production-shaped work with real local services, real catalog data, a real provider, durable PostgreSQL state, Compose recovery, and browser verification.
It is not evidence that the concierge is deployed on Muriersilk production. Production rollout, live customer traffic, production monitoring, and the operational acceptance period are still pending. The case therefore reports the local end-to-end boundary directly instead of turning implementation maturity into a deployment claim.