Skip to content

feat: ask capability for natural-language Q&A#538

Draft
igrigorik wants to merge 3 commits into
mainfrom
feat/ask
Draft

feat: ask capability for natural-language Q&A#538
igrigorik wants to merge 3 commits into
mainfrom
feat/ask

Conversation

@igrigorik

Copy link
Copy Markdown
Contributor

Adds dev.ucp.shopping.ask, a new top-level shopping capability. A platform, on behalf of the buyer, asks a free-form question and receives a text answer with optional related links. ask is the standardized ingress for the open questions buyers actually ask — returns, shipping, warranty, store hours, fit, suitability, etc — and for business-specific facts and knowledge that no structured capability exposes.

Examples

A broad question, no grounding. Request:

{
  "query": "What is your return policy for items bought on sale?"
}

Response:

{
  "ucp": { "version": "draft", "capabilities": { "dev.ucp.shopping.ask": [{ "version": "draft" }] } },
  "answer": {
    "markdown": "Sale items can be returned within **14 days** of delivery for store credit. Items returned to a different region may be subject to local return rules — see the refund policy for details."
  },
  "links": [
    { "type": "refund_policy", "url": "https://business.example.com/policies/refunds", "title": "Refund Policy" }
  ]
}

Grounded, multi-turn conversation

Turn 1 grounds the question on a specific product via ids; the business issues an opaque conversation value. Turn 2 replays it to continue, and the business builds on the prior turn. Request:

{
  "query": "Is this jacket warm enough for winter?",
  "ids": ["https://business.example.com/products/winter-jacket"]
}

Response:

{
  "ucp": { "version": "draft", "capabilities": { "dev.ucp.shopping.ask": [{ "version": "draft" }] } },
  "answer": {
    "plain": "Yes — it's insulated for sub-zero conditions and rated for harsh winter use."
  },
  "conversation": "conv_9a3f2e7b"
}

Turn 2, Request:

{
  "query": "And is it waterproof?",
  "conversation": "conv_9a3f2e7b"
}

Turn 2, response:

{
  "ucp": { "version": "draft", "capabilities": { "dev.ucp.shopping.ask": [{ "version": "draft" }] } },
  "answer": {
    "plain": "It's water-resistant with a durable water-repellent finish — good for snow and light rain, though not fully waterproof."
  },
  "conversation": "conv_9a3f2e7b"
}

"Can't answer" (best-effort)

A question the business can't address still returns a successful response with a populated answer that states the limitation — never an error.

{
  "ucp": { "version": "draft", "capabilities": { "dev.ucp.shopping.ask": [{ "version": "draft" }] } },
  "answer": {
    "plain": "I don't have information about competitor pricing. For questions about this store's products, I can help with policies, materials, fit, and availability."
  }
}

Design highlights

  • Best-effort contract — every well-formed, authorized request returns success with a populated answer. A "can't answer" outcome is a populated answer that says so plainly, never an error code or non-2xx status.
  • Multi-turn — a business MAY issue an opaque conversation value; the platform replays it to continue, and the business builds on prior turns. Omitting it starts fresh; an unresolved value starts a new conversation and notes it in messages. Platforms MUST treat it as opaque.
  • Grounding via ids — a business SHOULD accept grounding identifiers (product and variant IDs are the recommended minimum) and MAY also accept secondary identifiers (SKU, handle, URL) and other held GIDs such as a cart or checkout. For a cart or checkout, a held GID MAY act as a bearer reference — possession is enough, with no separate authentication — and a business MAY otherwise require authentication. ask stays independent of catalog.
  • Messages — non-blocking notes from the shared vocabulary: info/not_found when a referenced item can't be resolved, warnings with presentation: "disclosure" for notices the platform MUST surface.
  • No new primitives — reuses existing leaf types (context, signals, attribution, description, link, message); introduces no new shared types, enums, or monetary fields.

Checklist

  • Capability: New schemas (Discovery, Cart, etc.) or extensions.
  • Documentation: Updates to README, or documentations regarding schema or capabilities.
  • I have followed the Contributing Guide
  • I have updated the documentation (if applicable).
  • My changes pass all local linting and formatting checks.
  • I have added tests that prove my fix is effective or that my feature works.

   Adds `dev.ucp.shopping.ask`, a new top-level shopping capability. A platform,
   on behalf of the buyer, asks a free-form question and receives a text answer
   with optional related links. `ask` is the standardized ingress for the open
   questions buyers actually ask — returns, shipping, warranty, store hours, fit,
   suitability — and for business-specific facts and knowledge that no structured
   capability exposes.

   Why a dedicated capability: catalog, cart, checkout, and order answer questions
   that map to a structured operation, and they should own those. `ask` is their
   open-question complement — a question that maps to a dedicated operation is
   owned by that capability, while `ask` covers everything stated as a free-form
   question. Modeling it as a first-class capability, rather than burying Q&A
   inside catalog, keeps routing honest: a classifier picks `ask_business` for
   open questions and the structured tools for their own lanes.

   Request — a `query`, with optional `ids` to ground it and platform `context`:

       {
         "query": "What is your return policy for these on sale?",
         "ids": ["https://business.example.com/products/winter-jacket"],
         "context": { "address_country": "US", "language": "en" }
       }

   Response — an always-populated `answer` (best-effort), optional `links`, and
   optional `conversation` / `messages`:

       {
         "ucp": { "version": "...", "capabilities": { "dev.ucp.shopping.ask": [...] } },
         "answer": {
           "markdown": "Sale items can be returned within **14 days** for store credit..."
         },
         "links": [
           { "type": "refund_policy", "url": ".../policies/refunds", "title": "Refund Policy" }
         ]
       }

   Design highlights:

   - Best-effort contract: every well-formed, authorized, in-limits request returns
     success with a populated `answer`. A "can't answer" outcome is a populated
     answer that says so plainly — never an error code or non-2xx status.
   - Multi-turn: a business MAY issue an opaque `conversation` GID; the platform
     replays it to continue, and the business builds on prior turns. Omitting it
     starts fresh; an unresolved GID starts a new conversation and notes it in
     `messages`. Platforms MUST treat the GID as opaque.
   - Grounding via `ids`: a business SHOULD accept grounding identifiers (product
     and variant IDs are the recommended minimum) and MAY also accept secondary
     identifiers (SKU, handle, URL) and other held GIDs such as a cart or checkout.
     For resources like a cart or checkout, a business MAY accept the held GID as a
     bearer reference — possession is enough, with no separate authentication — and
     MAY otherwise require authentication to reach a resource. Questions needing a
     buyer's private account data (e.g. order history) are out of scope for this
     version.
   - Messages: non-blocking notes from the shared vocab — `info`/`not_found` when a
     referenced item can't be resolved, warnings with `presentation: "disclosure"`
     for notices the platform MUST surface.
   - Reuses existing leaf types (context, signals, attribution, description, link,
     message); introduces no new shared types, enums, or monetary fields.

   Transport: MCP tool `ask_business` and REST `POST /ask`; scope
   `dev.ucp.shopping.ask:read` for personalized answers. Includes the schema,
   OpenRPC/OpenAPI bindings, capability docs (overview, MCP, REST), worked
   single-turn and multi-turn examples, and mkdocs nav.
@igrigorik igrigorik self-assigned this Jun 22, 2026
@igrigorik igrigorik added the TC review Ready for TC review label Jun 22, 2026
@igrigorik igrigorik requested a review from jamesandersen June 22, 2026 22:25
grounded against specific items ("is this washable?"); without them, it applies
to the storefront broadly ("what is your return policy?").

`ids` is **optional**. A business **SHOULD** accept the identifiers that ground a

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm curious at the choice to model this as an array of possibly varied types of identifiers. I'd guess platforms will typically know what type of ID they'd be including and that it would be advantageous to send that context along to businesses (and their AI agents) as well e.g.

"ids": {
   "dev.ucp.product.id": [...],
   "dev.ucp.variant.url": [...]
}

If users are asking a business about products (one of the likely use cases noted) as similar structure could be used in the response to identify product / variant ids that could be immediately leveraged for catalog, cart or checkout capabilities.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

UCP contract is that ids are self-describing GIDs (e.g. gid://Shopify/Product/...). The platform treats them as opaque IDs, but the business can differentiate between a product, variant, cart, etc. This allows us to keep single array and provide an array of grounding references -- which is a nice property.

For URLs it's a good flag, I'm borrowing from the contract we adopted in catalog lookup operation where we allow GIDs and other identifiers that business can recognize (e.g. GTIN, URL, etc). It is overloading ids but I'd like to see how far we can push it; URLs are also self-describing, which keeps the overall contract more or less consistent for the business.

sufficient to ask about that resource, with no separate authentication. Access is
otherwise the business's to govern: it **MAY** require authentication to reach a
given resource (a gated catalog, for example).

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could very well fall outside the scope of a "v1" of this capability ... but just to explore, what about an attachments property as another way to scope / ground / contextualize the conversation? e.g.:

  • Do you have a sweater with patterns like this available?
  • Can I get a refund when the lace on my shoe broke after 3 weeks of normal wear?

attachments: [<jpg data url>]

This could be useful where people are very comfortable snapping pictures from their phones as well as possible wearables use cases.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great callout. Multi-modal input is definitely something we should model and consider, but agree that this may be a good v2 iteration: it'll likely require exposing a config on business schema, so we can communicate what type of attachments (image, audio, video, ...) are supported, etc. Added a note for this in the draft.

As an aside, "find me something like this image" is better handled in catalog as a visual-similarity input. As a live example, Shopify global catalog extension provides similarity search that accepts ID or image input.

Comment thread docs/specification/ask/index.md

| Scope | Description |
| :--- | :--- |
| `dev.ucp.shopping.ask:read` | Ask on behalf of the authenticated user — personalized answers reflecting member pricing, entitlements, or gated availability. |

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice! 💯

Comment thread source/schemas/shopping/ask.json
Comment thread docs/specification/ask/index.md Outdated
      - `conversation` becomes an object (`id` + optional `expires_at`, mirroring
        cart/checkout) instead of a bare string — room to grow.
      - Access reframed as a credential spectrum (public / resource reference /
        business posture / authenticated user), replacing the ad-hoc bearer + scope
        wording and resolving the `:read` vs out-of-scope contradiction.
      - `ids` framing cites catalog lookup's contract; product/variant the
        recommended minimum.
      - Future-direction note for `attachments` (multimodal grounding).
      `link.json` gains an optional `id` (a GID). When an answer recommends an
      addressable resource — e.g., a product — its link can now carry the `id`
      alongside the `url`: `url` for display, `id` to act on the resource through
      the capability that owns it (resolve in catalog, add to cart) without
      re-deriving it from the answer prose. The field is optional and on the
      shared link type, so it's non-breaking for cart/checkout/variant.

      Rewrites the ask Links section to match: links are the addressable
      references to the resources an answer names; a business SHOULD return one
      per referenced resource, with a `title` that captures the resource and, for
      an addressable resource, its `id`.
@igrigorik

Copy link
Copy Markdown
Contributor Author

@jamesandersen great feedback, ty! Updated the draft, ptal.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

TC review Ready for TC review

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants