WAYS OF WORKING · DEMO 16 NINO CHAVEZ
The agent that
asks twice.
Ask BC is a commerce copilot with 29 tools against a live store — 22 that read, 7 that can change it. The hard design problem was never wiring the write tools. It was making sure the model never used one without a human saying yes, on a separate turn.
Turn 1 · confirmed: false
Will set product ${product_id} inventory to ${inventory_level}. Ask the merchant to confirm.
→ or scroll to advance · ← to go back
02 WHAT ASK BC IS
Chat with the store's actual data
Ask BC answers questions against a real store's orders, products, customers, promotions, and inventory — live at askbc.ninochavez.co, and embedded directly in the BigCommerce admin as an "Ask BC" panel on the Orders and Products pages. It runs on the same live sandbox store the Kibble & Co. fleet runs on (demo 14) — real inventory and order history, nothing fabricated for the demo.
Next.js on Vercel
Handles BigCommerce OAuth install, the admin iframe shell, and App Extension registration. Auto-deploys on push to main.
Cloudflare Worker
The agent runtime itself: one Durable Object per store, built on Cloudflare's Think and Codemode libraries.
Direct WebSocket
The browser connects straight to the Worker. There is no Vercel proxy anywhere in the chat path.
03 THE WRITE PROBLEM
Seven of twenty-nine tools can change the store
Read tools run inside a sandboxed script and can chain freely — list products, join them to orders, page through results, all with zero mutation risk. Write tools sit outside that sandbox as separate top-level tools, and each one carries a confirmed boolean the model cannot route around.
"Write tools have a confirmed parameter. You MUST call them TWICE […] Never call a write tool with confirmed: true on the first mention. Always preview first. This is a security requirement — the merchant must explicitly confirm before the store is mutated." the system prompt taught to the model, workers/agent-runtime/src/index.ts:745-754
Every write also logs to the store's Durable Object SQLite, confirmed or not, and the tool set itself is filtered by the store's actual OAuth scope first — a token that can't perform an operation never gets offered the tool for it.
04 GENERATIVE UI
The answer is a component, not a paragraph
The model emits a fenced block code block containing JSON — a component name and its props. The Next.js client's markdown renderer detects the fence, parses the JSON, and mounts a real React component inline in the chat, in place of a wall of prose. Seven component types cover the answer shapes: KPICard, DataTable, ProductCard, OrderTimeline, InventoryBar, SparklineChart, ErrorCard.
{
"type": "KPICard",
"props": {
"label": "Total Revenue (5 orders)",
"value": "$5,344.68",
"trend": { "direction": "up", "label": "+12% vs last week" }
}
}
The block schema catalog is the single source of truth on both sides: the Worker references it when writing the system prompt, the Next.js app references it when deciding what to render. Add a component in one file, wire a matching React component, and both sides stay in sync without a shared build step.
05 MODEL ROUTING
Haiku by default, Sonnet when something breaks
"Default model: Haiku 4.5 — ~2.6× faster and ~3× cheaper than Sonnet 4.6, handles 95% of real merchant questions on the first try." getModel(), workers/agent-runtime/src/index.ts:937-944
Every fresh turn starts on Haiku. A "continuation" — most importantly, a retry after a tool error — upgrades that single step to Sonnet 4.6, then the next fresh turn drops back to Haiku. The upgrade is scoped to the failure, not sticky for the rest of the conversation.
"A continuation is anything after the first step in a turn […] That's exactly where Sonnet's deeper reasoning and error-recovery instincts earn their extra cost." beforeTurn(), workers/agent-runtime/src/index.ts:947-951
06 THE PATTERN SPREAD
A second assistant, same runtime, different data plane
The subscriptions platform from demo 14 later built its own merchant copilot — ADR-0083: "subs-assistant — merchant copilot on the codemode agent runtime" — on the identical substrate: a Durable Object per store, Cloudflare's Think and Codemode libraries, the same two-model split. It names ask-bc as its reference implementation outright.
"The reference implementation is ask-bcADR-001[…] ask-bc's Phase-0 measurements […] motivated adopting the same substrate rather than re-deriving it."ADR-0083, wip/bc-subscriptions/docs/decisions/0083-subs-assistant-codemode-runtime.md
Ask BC's reads
Call BigCommerce's public REST APIs directly — products, orders, customers, the storefront's own data plane.
Subs-assistant's reads
"Reads pull, never port. Every codemode.* tool traces to a real route in apps/api/openapi.yaml and calls it through a service binding […] generated code sees only RPC stubs." Its own internal API, not BigCommerce's.
Writes kept the identical shape too — pause and skip actions take a confirmed boolean, a false call previews with zero mutation, true executes only after the merchant clicks Confirm. Same store, two assistants, no collision: one speaks for BigCommerce's inventory and orders, the other for the subscription engine sitting beside it. Full build: demo 14, Kibble & Co.
07 WHAT WASN'T RIGHT THE FIRST TIME
A debug route with no lock on the door
One commit, still in the history: a Next.js API route built for local development proxied straight to the Worker's /smoke endpoint with no auth check at all — reachable in production the moment the matching env var was ever set. Alongside it, a non-httpOnly cookie stored decoded BigCommerce JWT payload keys where any script running in the admin iframe could read them.
The commit that closed it
security: remove debug cookies and unauthenticated worker-proxy route
It wasn't the only one. The same repo's history carries a run of similarly-prefixed commits closing similar gaps one at a time: a per-store rate limit on the WebSocket path, write tools filtered by the store's actual OAuth scope, single-use agent JWTs, the dev credential fallback gated behind a DEV_MODE check. Each commit message names the specific attack it closes, not a failing test — evidence of a deliberate pass over the attack surface, not a suite that happened to catch it.
08 WHAT TRANSFERS
Three habits that outlived the first build
Confirmation is a protocol
Put the gate in the tool's own execute function, keyed on an explicit boolean the model must set — not a line in the prompt asking it nicely to check first. A prompt is advice. A required second call with a different argument is a mechanism.
Route models by task
Cheap model by default, upgrade only on the step that actually needs it — a retry, an error, a harder reasoning chain. The upgrade should be scoped to that step, not sticky for the rest of the conversation.
Answers are components
A shared schema of typed component props, referenced by the system prompt and the renderer alike, beats asking the model to format a table in markdown and hoping it holds.
The pattern is the reusable part. The store is the fixture.
09 CLOSE
Two assistants. One rule that didn't change.
Ask BC shipped first, alone, against one store. Months later a second product — the subscriptions platform from demo 14 — took its ADR as the reference, kept the runtime, kept the two-turn write shape, and pointed the reads somewhere else entirely. The part that mattered wasn't the store, the tool count, or even the model. It was the one rule that traveled unmodified: nothing mutates on the first call.
ADR-0083's title and quoted decision text, and the debug-route/cookie security fix — was read directly from the ask-bc and bc-subscriptions repositories in this session, not from a prior summary; sources are cited in an HTML comment at the top of this file. An independent demonstration by Nino Chavez — not a BigCommerce product. Demo 16 in the ways-of-working series — the subscriptions build this pattern spread into is demo 14; the discipline of committing a verdict before you look is demo 13.