Skip to Content

← All archived runs

Run: product-conversation

run.md

Run: product-conversation

  • branch: claude/pipeline-product-conversation-vv6o2w
  • pr: #630

00_intake/stub.md

Stub: Product conversation — typed profile, richer crawl, gap interview

  • feature-slug: product-conversation
  • epic: vendor-onboarding-mvp
  • personas: Vendor
  • initiative: Build the Bridge / objective: Q2-2026 O1 — Establish Product-Market Fit with Vendor Partners
  • depends-on: none
  • sequence: 1 of 3

Problem

The onboarding concierge already crawls a website (analyse_source), drafts confidence-scored items into the onboarding-blueprint document, and commits products as name + description into the thin product model — but everything else the conversation learns about a product lands in the blueprint item's free-form data payload and is then never used. The BRD agent that will consume product knowledge sees three strings. The MVP needs the conversation to actually acquire a rich, typed, confidence-scored product profile — with no new DB model — by constraining what the blueprint's product data holds and driving a gap-filling conversation over it.

Proposed change

Three coupled backend changes in packages/services/src/ai/onboarding/, all writing into the existing blueprint item data payload (no schema work):

  • Typed contract (contract.ts): replace the free-form products-section payload with a typed ProductProfileDataSchema (Zod, all fields optional) — e.g. overview, targetCustomers, useCases, keyFeatures, pricingModel, integrations, differentiators, docsUrl, supportChannels, faq. Enforce it at the persistence boundary exactly as services/skills are today (CONSTRAINED_SECTION_FIELDS / sanitiseSectionData) so both write paths stay in lockstep.
  • Richer crawl (analyse-source.ts): point product extraction at the typed profile schema so a product URL yields a substantially filled profile draft with per-item confidence — the interview then only mops up the gaps.
  • Product-first gap interview (prompt.ts, blueprint-tools.ts): after analyse_source drafts profiles, run a per-product gap interview that asks only about fields the analysis left empty or low-confidence, one product at a time, instead of the generic section walkthrough for products. A data-driven gap computation (which fields are missing/low-confidence per product) is exposed to the model so "what to ask next" is data-driven, not prompt-vibes. Extend the tool set with a product- focused update path — either draft_section accepting the typed payload, or a dedicated update_product_profile tool on the same defineTenantTool infra. Vendor answers land as data updates with confidence 100 (vendor-stated) and conversational provenance.

Acceptance criteria (rough)

  • Giving the concierge a product URL produces a typed, confidence-scored rich product profile in the blueprint's products section.
  • The conversation then asks only about profile fields that are empty or low-confidence for that product; each answer updates the profile at confidence 100 with conversational provenance.
  • Off-contract product fields are stripped at the persistence boundary on both write paths (crawl and conversation).
  • The generic products-section walkthrough is replaced by the per-product gap interview; the other sections' walkthrough is unchanged.

Out of scope (this feature)

  • Commit-path changes and the getProductKnowledge read service — product-knowledge-read-seam (stub 2).
  • Rendering the profile in the UI — product-profile-review-panel (stub 3).
  • Document-upload ingestion (Vercel Blob), AI service-attachment proposals, skill derivation — deferred to the epic.
  • Any new or changed Mongoose model — the profile rides in the existing blueprint item data.

Notes for Define

The field list is the contract to nail here — finalise it against what the BRD agent will actually read (see the epic's brd-agent-upgrade.md); the schema shape is what we're buying, individual fields are cheap to adjust later. Both write paths (analyse_source and the conversational tool) must funnel through the same sanitiser so they cannot drift. The "low-confidence" threshold that drives the gap computation needs a concrete number set in Define. touches: packages/services/src/ai/onboarding/{contract.ts, analyse-source.ts, prompt.ts, blueprint-tools.ts}.

01_define/output/spec.md

Spec: Product conversation — typed profiles, richer crawl, per-product gap interview

  • slug: product-conversation
  • personas: Vendor
  • touches: packages/services/src/ai/onboarding (contract.ts, analyse-source.ts, prompt.ts, blueprint-tools.ts)
  • complexity: standard

Problem

The onboarding concierge already crawls a vendor's website URL (or a pasted description) via analyse_source, drafts confidence-scored items into the onboarding-blueprint document, and commits products as name + description into the thin product model — but everything else the conversation learns about a product lands in the blueprint item's free-form data payload and is then never used. Two gaps follow. First, the BRD agent that will later consume product knowledge (brd-agent-upgrade, epic stub 9) sees only three strings. Second, the interview treats products as a single section — it presents the crawled product list, asks one blanket question, and moves on — so a vendor with many products is never actually grilled about each one. This is the first stub of the vendor-onboarding-mvp epic (Build the Bridge / Q2-2026 O1 — Establish Product-Market Fit with Vendor Partners): it makes the onboarding conversation acquire a rich, typed, confidence-scored profile for every product the vendor brings — with zero schema change — so the committed blueprint becomes the interim product-knowledge store the rest of the epic reads from.

Inputs the conversation actually has

  • One vendor-level source, not product URLs. AnalysisSource is {kind:"url"} (the vendor's website) or {kind:"text"} (a pasted description). analyse_source crawls that single source and returns a products array — one candidate per product it found. There is no per-product URL to analyse, and Build must not assume one.
  • Many products. A vendor typically sells several products; the crawl yields a set, and the vendor may name more in conversation. The feature is about covering each of them, bounded by the existing MAX_ITEMS_PER_SECTION cap on a section's items.

Proposed change

Three coupled backend changes in packages/services/src/ai/onboarding/, all writing into the existing blueprint item data payload — no Mongoose model added or changed:

  • Typed contract (contract.ts): add a typed ProductProfileDataSchema (Zod, every field optional) for the products section, mirroring how ProfileDataSchema types the profile section today. The products slot in DraftBlueprintSchema changes from free-form sectionItems to items whose data is ProductProfileDataSchema. Enforce the same field set at the persistence boundary by adding products to CONSTRAINED_SECTION_FIELDS, so sanitiseSectionData strips off-contract keys on both write paths (crawl and conversation) exactly as services/skills are today. Each field is per-product; the schema types one product's profile, and a blueprint holds one such item per product.

    The locked field set (all optional):

    field meaning
    overview what the product is, in a sentence or two
    targetCustomers who it is for
    useCases primary problems it solves / jobs it does
    keyFeatures headline capabilities
    pricingModel how it is priced (tiers/seat/usage/quote)
    integrations systems it connects to
    differentiators why it wins vs. alternatives
    docsUrl link to product documentation
    supportChannels how customers get help
    faq notable Q&A the vendor volunteers
  • Richer crawl (analyse-source.ts): point the products extraction at ProductProfileDataSchema so crawling the vendor's one website URL (or description) yields, for each product found, a substantially filled profile draft — every field carried on a confidence-scored item. The extraction system prompt is extended to fill the typed product fields (not just name + description). The per-product interview then only mops up the gaps.

  • Per-product gap interview (prompt.ts, blueprint-tools.ts): replace the single products-section walkthrough with a loop that iterates over every product — each one the crawl drafted, plus any the vendor adds in conversation — and grills each in turn. For the current product, a data-driven gap computation (exposed to the model) lists which of its fields are empty or below the gap threshold, and the concierge asks only about those, one product and one question at a time, until that product's gaps are filled or the vendor skips a field. Only when all products are covered does the interview advance to the next section (services). Vendor answers write through a dedicated update_product_profile tool (built on the existing defineTenantTool infra, same as draft_section) that takes the typed payload for one named product, landing answers as data updates at confidence 100 (vendor-stated) with conversational provenance. The profile, services, skills, sla and settings walkthroughs are unchanged.

Decisions settled in Define

  • Gap threshold = confidence < 70. A product field is a "gap" (interview asks about it) when it is absent or carried at confidence < 70. Fields the crawl supported strongly (≥ 70) are kept without re-asking; vendor answers always land at 100. (Steer by editing this number.)
  • Field set = the 10 fields above. Finalised against what brd-agent-upgrade will read (product knowledge to ground the requirements conversation). The schema shape is the deliverable; individual fields are cheap to add later.
  • Coverage = every product, capped by MAX_ITEMS_PER_SECTION. The interview grills each product the crawl found and any the vendor adds, up to the existing per-section item cap — it does not silently stop at the first product.
  • Write path = a dedicated update_product_profile tool, not an overload of draft_section — keeps the per-product gap loop self-documenting and leaves the generic section tool untouched. Both funnel through sanitiseSectionData so the two write paths cannot drift.

Acceptance criteria

  • Crawling the vendor's website URL (or a pasted description) — the only source; no per-product URL — produces a typed, confidence-scored rich profile (fields drawn from the locked set) for each product found, i.e. a set of product profiles in the blueprint's products section.
  • The interview then iterates over every product (crawled or vendor-added) and, for each, asks only about that product's fields that are empty or below the gap threshold (confidence < 70) — one product and one question at a time; each answer updates that product's profile at confidence 100 with conversational provenance. It advances past the products section only once all products are covered.
  • A vendor can name a product the crawl missed and the interview adds it and grills it the same way.
  • Off-contract product fields are stripped at the persistence boundary on both write paths (crawl analyse_source and the conversational update_product_profile tool) — an invented field never reaches the stored blueprint.
  • The single products-section walkthrough is replaced by the per-product gap loop; the profile, services, skills, sla and settings section walkthroughs are unchanged.

Out of scope

  • Commit-path changes and the getProductKnowledge read service — product-knowledge-read-seam (epic stub 2).
  • Rendering the profiles in the UI — product-profile-review-panel (epic stub 3).
  • Document-upload ingestion (Vercel Blob), per-product URL crawling, AI service-attachment proposals, skill derivation — deferred to the wider vendor-onboarding-redesign epic.
  • Any new or changed Mongoose model — the profiles ride in the existing blueprint item data.
  • Raising the MAX_ITEMS_PER_SECTION cap — the interview grills up to the existing bound.
  • Migration of existing tenants — greenfield.

Open questions

  • none. (The tuning decisions above — threshold, field set, coverage cap, write-tool — are settled with documented defaults; edit spec.md to change any before ticking Spec approved.)

02_build/output/notes.md

Build notes: product-conversation

  • commits: feat: product-conversation — typed product profiles, richer crawl, per-product gap interview

What changed

All in packages/services/src/ai/onboarding/ — no Mongoose model or migration, per spec.

  • contract.ts — added ProductProfileDataSchema (Zod, all fields optional): the 10 rich PRODUCT_PROFILE_FIELDS plus name + description (kept because the product commit reads data.name/data.description into the thin product model — commit.ts — and that path is out of scope to change). Pointed DraftBlueprintSchema.products at the typed items and added products to CONSTRAINED_SECTION_FIELDS so sanitiseSectionData strips off-contract keys on every write path. Added GAP_CONFIDENCE_THRESHOLD = 70 and a pure computeProductGaps(item) helper returning, per product, the empty fields (missing), the filled fields, and whether item confidence is below threshold (lowConfidence) — the shared definition of "gap".
  • analyse-source.ts — extended the extraction system prompt's products bullet to fill the typed profile fields for each product (not just name + description). No per-product URL: the single vendor source (website URL or pasted description) yields the product set, unchanged.
  • blueprint-tools.ts — two new tenant tools on the existing defineTenantTool infra:
    • read_product_gaps (read-only) — lists each product with its computeProductGaps breakdown, so the model drives the interview from data, not prompt-vibes.
    • update_product_profile (write) — the products counterpart to draft_section: merges the vendor's stated fields for one product into its profile at confidence 100 with human_edited provenance (the conversational, vendor-stated marker vs the ai_drafted crawl), only merging fields actually given (no undefined-clobber), funnelled through sanitiseSectionData. An unknown key is added, so a vendor can name a product the crawl missed. Both registered in buildOnboardingConciergeTools.
  • prompt.ts — described the rich product profile in the six-section list, and replaced the single products walkthrough with a per-product gap loop: call read_product_gaps, grill one product at a time about only its missing/low-confidence fields (one question per message), record via update_product_profile, add vendor-named products, and advance to services only once every product is covered. The other five sections' walkthrough is unchanged.

Acceptance criteria status

  • Crawl yields a typed, confidence-scored profile for each product found — analyse-source.ts system prompt fills the typed fields; DraftBlueprintSchema.products is the typed schema so the crawl produces one profile item per product.
  • Per-product gap interview asks only empty/low-confidence fields, one product at a time, updating at confidence 100 with conversational provenance — read_product_gaps + update_product_profile + the prompt.ts loop.
  • A vendor can name a product the crawl missed and it is grilled the same way — update_product_profile adds an unknown key; the prompt instructs adding vendor-named products.
  • Off-contract product fields are stripped on both write paths — sanitiseSectionData("products", …) in analyse-source.ts (toCandidates) and in update_product_profile.
  • The single products walkthrough is replaced by the per-product loop; profile/services/skills/sla/ settings walkthroughs are unchanged — prompt.ts override is products-only; draft_section untouched for the other sections.

Verify result

  • mechanical checks (format · lint · typecheck · build) run in CI + the Vercel preview, not here. Release reads them back from the PR's check runs. No check is known to fail.

Notes for review

  • Confidence is per-item, not per-field (onboarding-blueprint.ts). "Low-confidence field" is therefore modelled as: an empty field is always a gap; when the item's overall confidence is < 70 its filled fields are also flagged for confirmation. This is the schema-free reading of the spec's threshold — no per-field confidence was added.
  • name + description retained in the products contract on purpose — the commit path (out of scope, stub 2) reads them. Constraining products to only the 10 rich fields would have silently dropped product descriptions at commit.
  • draft_section still technically accepts products (and sanitises them), but the prompt directs the model to use update_product_profile for products; the acceptance-criterion behaviour lives in the prompt + the two new tools.

03_release/output/changelog.md

Changelog entry — product-conversation

Live at apps/help/app/changelog/2026-07-10-product-conversation/page.mdx (goes live on merge).


Title: Onboarding now captures a detailed profile for every product you sell Personas: vendor PR: https://github.com/sustentus/sustentus/pull/630

When you set up your workspace, the assistant now builds a rich profile for each of your products — not just a name and description.

  • It reads your website first and drafts what it finds, with a confidence score on every detail.
  • Then it goes through your products one at a time, asking only about the things it couldn't find or wasn't sure about — an overview, who it's for, use cases, key features, pricing, integrations, what sets it apart, docs, support and FAQs.
  • Mention a product it missed and it adds it, then asks about that one too.

03_release/output/investor-update.md

Vendor onboarding now captures a rich profile for every product

Who it's for: Vendors onboarding to Sustentus What shipped: The onboarding assistant now builds a typed, confidence-scored profile for every product a vendor sells — from their website, then filling only the gaps. Why it matters: Richer product knowledge from day one advances Build the Bridge — Establish Product-Market Fit with Vendor Partners.

Every product is profiled, not just the first.

Dig deeper: https://github.com/sustentus/sustentus/pull/630 · https://help.sustentus.com/changelog/2026-07-10-product-conversation

03_release/output/release.md

Release: product-conversation

  • pr: https://github.com/sustentus/sustentus/pull/630 · merged: yes — 2026-07-10 (squash 4852575)
  • CI: green (Quality Project ✓, Vercel Agent Review ✓, Audit database ✓, preview migrate ✓; production-migrate + CONVENTIONS-review skipped by config)
  • technical docs: updated apps/docs/app/technical/packages/services/page.mdx — onboarding concierge section now describes the typed per-product profile + per-product gap interview and its read/update tools
  • business docs: no business docs impact — the change deepens data capture within the existing onboarding flow; service-journey has no onboarding step, feature-role-matrix/products is a role-permission table, platform-overview is coarser-grain than this
  • release notes: both — changelog entry (apps/help/app/changelog/2026-07-10-product-conversation/page.mdx) + investor draft in this PR
  • deploy: READY (web, docs, help all READY on merge commit 4852575)
  • sent: investor update sent to 2 recipients on 2026-07-10, after green deploy

Review summary

  • Reviewed the branch diff at medium effort (complexity: standard). No correctness bugs.
  • update_product_profile preserves reviewState while draft_section resets it to pending — accepted; per-item review is legacy (commit.ts) and launch confirms the blueprint as a whole.
  • Minor merge-map duplication between the two write tools — accepted; different provenance/confidence semantics justify it.
  • GAP_CONFIDENCE_THRESHOLD exported but used only within contract.ts — accepted (harmless; available for tests/future).

Acceptance check (vs spec)

  • Crawl yields a typed, confidence-scored profile per product — analyse-source.ts extraction prompt + typed DraftBlueprintSchema.products.
  • Per-product gap interview asks only empty/low-confidence fields, one product at a time, at confidence 100 with conversational provenance — read_product_gaps + update_product_profile + prompt.ts loop.
  • Vendor can name a product the crawl missed and it is grilled the same way — update_product_profile adds an unknown key.
  • Off-contract fields stripped on both write paths — sanitiseSectionData("products", …) in the crawl (toCandidates) and in update_product_profile.
  • Single products walkthrough replaced by the per-product loop; other five sections unchanged — prompt.ts override is products-only.