This post covers the client and platform boundaries behind user experience and interactions with Ask DoorDash: how we moved from hackathon carousels to that model, how one artifact serves three readers, and how context and state stay consistent as consumers move through DoorDash. It follows Building DoorDash Assistant: An Engineering Overview and our deep dives Building Ask DoorDash (Part 2): Intelligence, Building Ask DoorDash (Part 3): Evaluation, and Building Ask DoorDash (Part 4): A Platform for Building and Evolving Agents.
Ask DoorDash turns requests like “build me a $60 dinner for 10 people”, “help me make chicken tacos”, and “snacks for the week” into editable shopping lists. It produces a list with shoppable products, prices, photos, quantity controls, and simple buttons for swapping any item, and checking out. For restaurant requests, stores and dishes are returned that connect into the native shopping flow. Both functionalities run on the same client and platform machinery. This post is focused on the grocery use case, as it puts the most pressure on all of the major complexities of creating an intuitive interface for agent-driven shopping. Grocery lists commonly hold ten or twenty items, go through several rounds of revision, and must easily deal with consumer constraints like budgeting and dietary restrictions.
User sessions from July 2026 showed that in grocery sessions where a shopping list was rendered, consumers averaged close to two UI interactions each. Roughly a third of those sessions went on to apply the list to a cart. The assistant takes the broad intent of consumers and boils it down. The output is native components, which allow for granular refinement by consumers. Simple edits update an authoritative artifact immediately, while changes that require reasoning start a new agent turn.
In order to produce an effective collaborative consumer/agent shopping experience, shopping agents need to integrate with an interactive surface grounded in live commerce data and the consumer’s current app context. When a consumer is shopping in an app, they expect to see at a glance real items they can purchase. Text can describe that environment, but image-based, interactive components let consumers actually traverse it. Context matters too. Consumers opening DoorDash arrive with different expectations than they bring to a general-purpose chat app. They shop by browsing, moving between stores and pages rather than describing what they want. Meeting them where they are is an essential piece of the puzzle.
Big things have small beginnings
The DoorDash grocery assistant began as a hackathon project. We knew from the start that integrating existing DoorDash components would be a key piece of whatever we built. The earliest versions leaned heavily on carousels: first a list of nearby store candidates, then a carousel of search results for each item the agent was looking for. They were rendered inline, each with its search term attached. The result was essentially a chat transcript with commerce results pasted into it.
Functionally, it was close to what we ship today. As an experience it was rougher, and the specifics are worth walking through.
Having found a set of stores, the assistant asked which one to explore and waited for the answer to be typed. Each carousel represented a single item the consumer needed, and picking an option from it added that item to the cart. Every one arrived with its candidates spread out at full width and the choice still open. So building a week of groceries meant working down the stack one selection at a time, with no way to accept a set of items all at once.
Underneath all of it was one failure running in both directions: the interface was built primarily to present the assistant’s work. Every carousel was headed by the query that produced it and candidates sat in the order they were retrieved. Search terms and raw result sets are an agent’s working material. Presenting the results directly is the interface equivalent of printing a stack trace. The assistant deferred just as readily to the interface, working one item at a time because a stack of per-item carousels was what the screen could show. Each had been designed around the other’s limitations rather than around the consumer.
The lesson was not that reusing existing components was wrong; the familiarity they carry is exactly why the assistant feels like DoorDash. We had simply built the wrong container for them. The container is what decides the order they appear in, what they sit next to, and what a consumer can do with them. Above all else, a consumer needs a shopping surface rather than a conversation interface. We had built a conversation interface that happened to contain shopping components. Component reuse gave us the vocabulary. Composing it into a surface was the harder part.
What did survive the hackathon was the foundation: the same streaming infrastructure it established is what the experience runs on today. Meanwhile, the decoding layer that turned tool results into UI did not last. The assistant no longer surfaces tool results at all. Instead, we invested time to build out an artifact architecture that separates out consumer requirements from storage and agent requirements. In other words: we stopped decoding the agent’s working material for display and started to build around the needs of the different components and consumers of the system.

Figure 1: The same grocery request, a year apart. The hackathon build showed a carousel per item, each headed by the query that produced it; selection was left to the consumer. Today, the assistant returns each list as a card in the conversation, with top items already picked.
One artifact, three lenses
A shopping list has three readers: the service that stores it, the agent that revises it, and the person shopping. Each one needs to see something different. Keeping them separate is what lets a consumer’s edit appear immediately and stay authoritative without costing another model turn.
The stored form is the system of record: a JSON blob held as an artifact in Managed Agent Services, described in the platform post. It has to be complete, and it is the only authoritative copy.
The agent does not read that blob directly. An interactive shopping list carries far more detail than a model needs for most follow-ups. Repeating every product name, price, quantity, substitution, and piece of display metadata on every turn would compete with what the consumer actually asked for. So when an agent emits a widget, what stays in conversation context is a compact summary: the store, and what the list contains. When a turn needs exact state, the agent reads the artifact and gets back a reduced view with the display metadata stripped out. That view is unambiguous and small enough to reason over cheaply. A median list persists as 40 to 60 KB of JSON. What stays in the conversation context is a single line of about 240 characters, roughly 250 times smaller.
What the consumer sees is different again, and it takes more than one form. In the conversation the list renders as a compact widget: the store, the name of the list, the ETA, a few item photos, and an “Add to Cart” button carrying the estimated total. That is enough to judge the list at a glance without leaving the chat. A “View List” button opens the full-page view, which presents the complete list and everything needed to work through it in detail. Both views are built to be grasped immediately and acted on directly.
One detail shows how far these views diverge. When the agent chooses a product for an item on the list, it keeps the other products that matched the same search, and the consumer can swap between them with a tap. It keeps ten candidates per item, and 99.7% of items have enough matches to fill that out, so nearly every line in the consumer’s view has one selection and nine alternates behind it. The consumer’s view carries every one of those alternates. The agent’s view carries none of them, because choosing between them is the consumer’s job (unless the assistant is explicitly asked to).

Staying grounded
Against a cart holding raspberry yogurt, tapping “Recommend cart pairings” returns two suggestions: granola, “because it goes with your yogurt”, and blueberries, “to add fruit to your yogurt” (see Figure 3). The contrast case already exists in our own product, on the same screen: the cart page carries a complementary-item carousel that offers items a consumer may want to add, with no reason attached. Our cart agent could have returned exactly that, a list of plausible items. The items might well have been the same either way. The rationale is what tells the consumer the suggestion was reasoned rather than merely plausible, and since every widget is also a place to act, that distinction does real work.
That is what grounding buys: the difference between a compelling shopping experience and a novelty. If the output of the assistant is not tangible, consumers have little reason to shift away from the rich app experience DoorDash already provides. Text still does the work, but in a supporting role. We deliberately put the assistant’s rationale inside the widgets and around them, instead of showing item cells exactly as they appear elsewhere in the app.
Grounding also has to hold at the level of the data, and there the work is split. An agent chooses a supported content type and supplies the commerce data for it; the iOS client owns presentation and interaction, mapping that content onto native DoorDash design system components. A grocery request produces an editable shopping list with quantities, substitutions, and a subtotal. A restaurant request can produce store cards, item cards, or cart suggestions.
That split is what grounds the data itself. The commerce values in a widget do not come from the model. Prices, inventory, store hours, and cart contents are read from the same systems of record that the rest of the app uses, so a price shown in Ask matches the price on the store page at that moment. Typed content also gives the backend and client a stable contract. Supporting a new widget takes more than a prompt change. The agent has to know when to use it and what to put in it, and the client needs a named schema plus the code to decode and render it. Past that, a new widget reaches the consumer over the same conversation and streaming machinery as every other one.

Widgets as I/O channels
Each new widget we have integrated has made the assistant feel more functional. The reason has as much to do with input as with output. One boundary governs all of them: a deterministic edit stays in the client and applies to the artifact directly, while a change that requires judgment starts an agent turn. The examples below are organized around where that line falls.
The hackathon build asked which store the consumer wanted, then waited for them to type it. It had no way to choose for itself; today that choice is automatic. Plenty of other questions still need the consumer, and direct text can be a poor way to collect the answers. Text entry is a single-threaded constraint: the consumer may know every answer, but can only supply them as fast as they can type or speak.
This is where widgets earn their place. A consumer may have ten operations to apply to a shopping list, each of them simple. Individually they are trivial; described in prose they become a complex task. It is the difference between looking over someone’s shoulder and telling them which buttons to press, and pressing the buttons yourself.
Structured clarifying questions are the clearest case, and the pattern is borrowed from coding agents: Claude Code’s AskUserQuestion tool does the same job for developers. We ship a widget that interrupts the consumer with a small set of questions (multiple choice and multi-select), letting them steer the conversation at genuinely open-ended points. It has become a core pillar of the interaction model in the grocery agent, and is now being adopted in the restaurant agent to simplify the same class of exchange. We integrate with our consumer memory platform, which narrows choices significantly, but knowledge gaps still remain that need consumer input. Asking is unavoidable. But we can ask in a way that is easy and efficient for the consumer to respond to.
The full-page list view is where the input/output collaboration becomes concrete. A consumer can change the quantity of an item, delete an item, swap an item for one of the alternates the assistant found alongside it, and add the finished list to their cart. Every one of those actions manipulates the artifact directly. None requires the assistant to act on the consumer’s behalf, and none costs an LLM round trip. The swap case puts the hackathon build’s candidate sets to better use. The alternatives a consumer chooses between are the other candidates the assistant retrieved when it selected the item. The list of all options available to the consumer previously filled a carousel. Now they sit one tap behind a decision instead of spread out in front of the consumer as an open question.
Changing the store is the exception, and the exception is the interesting part. It is not a mutation of the existing list. It escalates into an assistant request that rebuilds the list at the alternative store. This interaction is also rare: it was the first action in 0.1% of engaged sessions, which is roughly what we would expect if the line is drawn in about the right place. Put that line in the wrong place and the consumer either waits for the assistant to do something they could have done with a tap, or taps their way through something that could have benefited from the assistant’s judgment.
The artifact makes the split safe, and the two sides write to it differently. A quantity change, a removal, or a swap takes the short path: the client updates the list in place and the edit appears immediately. A store change starts an agent turn instead, because the whole list needs rematching against a different catalog. An agent revision does not overwrite what was there; it produces a new list derived from the previous one.
The effect is that a direct edit is authoritative. Before a turn changes the list, the agent reads the current one rather than working from the transcript, so if the consumer removes oat milk, the next turn begins from a list without oat milk.
The measured behavior over the same period shows where that line sits. Immediately after the first request, nearly three quarters of first next actions came through a component. From the second message onward it inverts, and typed follow-ups make up the majority at every depth that we measured. Controls absorb the immediate, structured refinements; language takes over when the change is larger or harder to express as a tap.

Every assistant revision stays live
When the assistant revises a shopping list, the version it replaces stays on screen and stays usable.
That was not our first implementation. The first version hid superseded lists: once a revision arrived, the list behind it could no longer be viewed or edited. One current list, no clutter. It seemed like tidier behavior. What it did instead was make revision feel expensive. If asking for a change might erase a list you were happy with, the safe move is to stop asking, and that undercuts the whole point of a collaborative flow.
So every assistant revision now produces a new artifact derived from the previous one, and each stays where it was created in the conversation. Scroll back and the earlier versions are all still there, still live: a consumer can open an earlier list, edit it, or add it to their cart instead of the latest one. Asking for a change costs nothing, because what they had a moment ago is one scroll away.
Not every list the assistant builds becomes a version the consumer sees. When it assembles a candidate list and concludes the store does not carry what was asked for, that list is never rendered. The consumer is told in text that the store was missing items and that a new list is on the way. It is one of the few places where prose does the work outright, and for the reason behind everything else in this post: interaction is used to simplify, and text is used to justify.
Where a list becomes a cart
An assistant that edits the cart directly can remove an item the consumer wanted, or add one they never confirmed, without being asked. One misstep there is enough to lose a consumer’s trust, because the failure is silent and it lands on the thing they are about to pay for.
So we instead present a shopping list as a proposal. It can hold dozens of items and go through several rounds of revision, whether that means removing a pantry item the consumer already has, changing a quantity, swapping a product, or regenerating part of a recipe, and none of it touches the cart. Committing takes an explicit “Add to Cart”, which is the simplest structure we found that gives the consumer real control without making them supervise every step.
The two objects still have to be reconciled, and the interesting choice is who does it. The assistant reads the cart while building the list, but it does not quietly adjust the list to match: if a recipe needs two bananas, the list asks for two, whether or not there are already four in the cart. Building the list for exactly what was requested, and reconciling it against the cart as a separate step, keeps one decision in one place.
That step surfaces at commit time. When the list and the cart overlap, the consumer is shown the duplicates and chooses whether to replace those quantities or add to them. It is a small moment, but small moments like this are where trust is won or lost.
Carrying context across DoorDash
It is not enough for the assistant to live in one location; it has to be reachable from anywhere in the app. Opening the assistant is already a strong signal of intent, but an ambiguous one. A consumer at that moment may be ordering from a favorite restaurant, buying groceries for the week, shopping for a new pair of headphones, or looking for support on a past order. A consumer who has navigated to a particular grocery store is showing a much sharper signal: it is probable they intend to build a cart at that store. Getting that context to the assistant is what lets it focus on the likely intent instead of asking.
The scope system we designed for this spans the client and the Gateway. The client attaches key pieces of entry context to each request: the topic the consumer entered under (grocery, restaurant), the store they are looking at, and a few related identifiers. The Gateway uses the topic to decide which agent the turn routes to, and the rest to give tools the identifiers they need.
Scope is deliberately a flat set of key-value pairs rather than a typed schema, and the client decides what goes into it. Unrecognized keys are carried through rather than rejected, so a new surface can start sending context without waiting on a Gateway change. The exception is the topic itself: adding a new one means teaching the Gateway which agent it routes to. So, while a new surface is cheap, a new domain is not.
The topic is also the part that applies only to the first message of a context. That gives the assistant solid ground to work from without being overly prescriptive, and it is what lets consumers break out of a scope when they explicitly choose to. A consumer using the assistant inside a restaurant can leave the restaurant scope entirely by asking it to “find me a recipe for this dish, and build me a cart, so I can make it at home.” That request is not free, but the cost is a hop inside the turn rather than a dead end: the restaurant agent has to recognize the request as outside its domain and hand it back before another agent picks it up.
In certain cases, scopes can also allow us to skip the model entirely. Take the “Reorder my last cart” entry point. It could simply send that phrase to the assistant as a message, and while that would work, attaching a scope lets us be more targeted. The “reorder” case is effectively deterministic, so we can bypass the LLM and hand back a shopping list almost immediately. What comes back is an ordinary shopping list widget: the consumer can change quantities, swap products, or ask the assistant to revise it in the chat, exactly as they would with any other. Note that the bypass only holds if the store still carries everything in the previous cart; if an item is unavailable, the request goes to the grocery agent, which rebuilds the list the same way it would from a typed request.
Scoping is also what makes a single continuous chat across an app session possible, but it only works because scope, agent pinning, and session state are kept separate. We treat scope as per-turn context. The pin, described earlier in the blog post series, keeps follow-up turns with the agent that resolved the previous turn. Both the pin and the transcript (the actual conversation) are held against the chat rather than the scope, so a consumer walking from one store to another re-points the pin at a new domain agent without discarding what was said. Several agents specialize underneath, and the whole thing still behaves as one assistant.

Conclusion
Agent integration into consumer products is still early. However, the foundations are becoming clear. Widgets are where consumers do the work and the consumer/agent conversation is what steers them. There are currently three readers of our shopping list artifact, each needing a different view of it. We use the user-facing part of this to ground the consumer in real products and to make common modifications one-tap actions. As agents take on more of the work, we expect what a consumer needs to see and what an agent needs to reason over will continue to change, and the line between an app interface and a chat interface will become harder to draw.


































