Created and presented by Nino Chavez.

Session S16 · 9 chaptersScroll to read · use ← → to step between chaptersSkip to the record

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.

✓ turn 2, after the merchant says yes: the same tool is called again with confirmed: true. Only then does it write, and only then does it log to the store's audit table.

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 tool
Turn 1 — confirmed: false
Turn 2 — confirmed: true
updateProductInventory
Returns a preview, no mutation
Sets the inventory level, logs the write
deleteCoupon
Preview states "cannot be undone"
Deletes the coupon, logs the write
updateOrderStatus
Preview shows the new status label
Changes the status, logs the write
"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.

createCouponupdateProductInventorysetProductVisibility updateProductPricedeleteCouponupdateOrderStatuscreateProduct

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-bc ADR-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

✓ fixed: the proxy route deleted outright; the debug cookies deleted from the OAuth middleware. The fix landed a full month before the app's custom domain went live.

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.

confirm before mutate cheap model until something breaks the pattern outlived the first build
Colophon. Every figure on these slides — the 22/7/29 tool split, the confirmed-boolean and system-prompt text, the write-audit and OAuth-scope-filter behavior, the model-routing quotes, the seven block types and the KPICard example, 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.

What happened

The Agent That Asks Twice

Ask BC is a production agent with 29 tools against a live commerce store — 22 that read, 7 that can change it — deployed split across a Next.js app and a Cloudflare Worker with a Durable Object per store. Every mutation needs two turns: a preview, then an explicit confirm. Months later, a sibling platform copied the runtime wholesale for its own merchant copilot — same Durable Object, same codemode sandbox, same two-turn writes — and pointed it at a different API entirely.

Collection
Ways of Working
Record
S16
Format
Session
01 / Reader

Who this is for

Anyone deciding how much autonomy to hand an agent that can mutate a real system, and anyone wondering whether an agent pattern built once actually gets reused, or just described as reusable.

02 / Evidence

What the session shows

The two-turn confirmation pattern read straight from the tool code — nothing mutates on the first call, only on an explicit second call with the confirm flag set to true; the generative-UI block protocol that turns tool output into an inline React component instead of a wall of markdown; the Haiku-default, Sonnet-on-retry model split; a real security gap caught and fixed a month before the app's custom domain went live; and the decision record where a second product adopted the identical Durable-Object-plus-codemode runtime for its own copilot, pointed at an entirely different data plane.

03 / Practice

What to reuse

Put the confirmation gate inside the tool's own execute function, keyed on an explicit boolean the model must set — not a line in the prompt. Route the cheap model by default and upgrade only the step that needs it. When a pattern proves itself once, let the next build copy the runtime and swap only the data plane.