Skip to content

Build an Agentic Commerce Assistant

The Agentic Commerce Assistant is a complete reference application, not a chat wrapper around a product API. It retrieves through Samesake, revalidates commercial facts through Porulle, pauses before checkout through Kuralle, and sends only a tokenized payment method to Stripe.

SubstrateWhat it owns
Kuralle CoreTyped tools, policy, durable run state, approval/resume, replay, and traces
Pi DriverPi's provider-native model/tool loop behind Kuralle's runtime contract
cf-agentOne Durable Object coordination atom per shopper session
Durable Object SQLiteConversation-local messages, cart state, approval interrupt, and effect journal
PostgreSQL + pgvectorShared Porulle records, Samesake indexes, and Node/Bun sessions
SamesakeCandidate generation, hybrid retrieval, filters, grounding, and constraint traces
PorulleCurrent catalog entity, price, inventory, cart, order, and checkout truth
StripeTokenized payment confirmation
AI GatewayOne governed route for OpenAI chat and embeddings
Queue + WorkflowBuffered catalog events and retryable multi-step indexing
HyperdriveCloudflare-to-PostgreSQL connection pooling
Signed shopper identityHTTP-only cookie, server-owned session namespace, and attributed approval actor

This boundary is why @earendil-works/pi-agent-core fits without becoming the application framework. Pi runs the model/tool turn protocol. Kuralle still intercepts tool calls, applies policy, stores the resumable run, records the human decision, and guards effect replay. The flow would become unsafe if a direct Pi tool callback bypassed that Kuralle boundary.

Uber Cart Assistant separates ambiguous cart planning and relevance judgments from deterministic retrieval, pricing, eligibility, quantity validation, constraints, and cart assembly. This example keeps the same probabilistic-versus-deterministic boundary, but extends it through approval and payment: Samesake retrieves candidates, Porulle revalidates every cart line and owns checkout, and Kuralle prevents the model from crossing the consequential-action boundary by itself.

Amazon Rufus demonstrates why shopping answers need retrieval from trusted catalog and Store API evidence rather than model memory. Shopify Sidekick shows the operational value of keeping tool responsibilities focused and evaluating agent changes against real task behavior. Here those lessons become typed tools, a narrow Porulle client, retrieval constraint traces, deterministic money handling, focused contract tests, and an end-to-end approval exercise.

Clone and install Kuralle and Porulle:

Terminal window
git clone https://github.com/kuralle/kuralle-agents.git
git clone https://github.com/asyncdotengineering/porulle.git
cd kuralle-agents && bun install
cd ../porulle && bun install

Create a shared PostgreSQL database:

Terminal window
createdb kuralle_agentic_commerce
psql kuralle_agentic_commerce -c 'CREATE EXTENSION IF NOT EXISTS vector'

Configure and seed the Porulle origin:

Terminal window
cd porulle/apps/agentic-commerce-origin
cp .env.example .env
bun run db:push
bun run seed
bun run start

Before seeding, set STRIPE_SECRET_KEY to your account's test-mode secret and keep PUBLIC_URL=http://localhost:4000. The seed output includes a scoped storefrontKey; handle it as a secret.

In another terminal, configure the Kuralle app:

Terminal window
cd kuralle-agents/apps/examples/agentic-commerce-assistant
cp .env.example .env

Set the shared DATABASE_URL, your Cloudflare account/gateway details, the Porulle URL and scoped key, an application-specific Samesake key, and a random COMMERCE_IDENTITY_SECRET of at least 32 characters. Use pm_card_visa only in Stripe test mode.

Generate a local Cloudflare token with:

Terminal window
wrangler auth token

Verify the gateway independently:

Terminal window
curl --request POST \
"https://api.cloudflare.com/client/v4/accounts/$CLOUDFLARE_ACCOUNT_ID/ai/run" \
--header "Authorization: Bearer $CLOUDFLARE_API_KEY" \
--header "cf-aig-gateway-id: $CLOUDFLARE_GATEWAY_ID" \
--header 'Content-Type: application/json' \
--data '{"model":"openai/gpt-4.1","input":{"messages":[{"role":"user","content":"Reply with gateway-ok"}],"max_tokens":32}}'

The probe uses Cloudflare's account-level AI Run API. The application uses AI Gateway's provider-native OpenAI endpoint because Pi's adapter needs that streaming protocol; embeddings travel through the same gateway.

Bootstrap Samesake and start the portable server:

Terminal window
bun run bootstrap
bun run node

Open http://localhost:8787 and ask:

Find a weatherproof daypack under $120, add the best match, show my cart, and check out.

Discovery is retrieval-led. cart_add reads Porulle again, and create_order pauses. Only an explicit approval resumes checkout.

Use a dedicated Neon database or branch and enable vector. Apply the Porulle schema, seed the catalog, and run the Samesake bootstrap against the Neon connection string. Direct Node/Bun connections should use sslmode=verify-full.

Create the edge resources:

Terminal window
bunx wrangler hyperdrive create kuralle-agentic-commerce-db \
--connection-string='your_neon_connection_string'
bunx wrangler queues create kuralle-commerce-events
bunx wrangler queues create kuralle-commerce-events-dlq

Put the Hyperdrive ID into each wrangler.jsonc, replace example URLs and account values, then upload secrets interactively:

Terminal window
cd porulle/apps/agentic-commerce-origin
bunx wrangler secret put STRIPE_SECRET_KEY
bunx wrangler secret put STRIPE_WEBHOOK_SECRET
bun run worker:deploy
cd ../../../kuralle-agents/apps/examples/agentic-commerce-assistant
bunx wrangler secret put CLOUDFLARE_API_KEY
bunx wrangler secret put SAMESAKE_API_KEY
bunx wrangler secret put PORULLE_STOREFRONT_KEY
bunx wrangler secret put ADMIN_TOKEN
bunx wrangler secret put COMMERCE_IDENTITY_SECRET
bun run cf:deploy

The deployment provisions the Durable Object and catalog Workflow from configuration. The Queue must exist before deployment. Hyperdrive supplies the PostgreSQL connection to Workers without putting the database password in Worker variables. Approved checkout remains in the resumed agent turn: the Durable Object serializes the effect and Porulle receives the stable content key as its idempotency key.

If you want Stripe to reconcile asynchronous payment events, register https://your-porulle-origin/api/payments/webhook as a Stripe test-mode webhook and use its endpoint signing secret for STRIPE_WEBHOOK_SECRET. Porulle verifies the signature, deduplicates event IDs, and applies payment_intent.succeeded only to the order identified by Stripe metadata.

Both runtimes expose:

POST /api/chat
POST /api/chat/approval

Send { conversationId, message } to /api/chat and retain the signed, HTTP-only cookie returned by the server. When checkout pauses, preserve pendingApproval.requestId; send it with the same conversationId, cookie, and an approve or deny decision to /api/chat/approval.

The browser-supplied conversation ID is only a label. The server namespaces the real session or Durable Object key under the verified shopper identity, binds the object to that identity, creates the signal ID, and records that identity as the approval actor. The Durable Object journals the pending interrupt independently of the HTTP response, so a reconnect surfaces the same approval while a different identity cannot resume it. Both runtimes reject an absent or mismatched pending request with HTTP 409 before entering the model loop.

Terminal window
cd kuralle-agents
bun run --cwd apps/examples/agentic-commerce-assistant typecheck
bun run --cwd apps/examples/agentic-commerce-assistant test
bun run --cwd apps/examples/agentic-commerce-assistant cf:check
cd ../porulle
bun run --cwd packages/core check-types
bun run --cwd packages/adapter-stripe test
bun run --cwd apps/agentic-commerce-origin check-types
bun run --cwd apps/agentic-commerce-origin test
bun run --cwd apps/agentic-commerce-origin worker:check

Read the example's full operating guide for the environment reference, direct HTTP examples, catalog synchronization, and production-hardening checklist.