Weekly Merchandising
The platform’s first deployed automation. One agent, three read-only tools, runs every Monday at 06:00 UTC, produces a Markdown report. Deliberately simple — no delegation, no memory, no events, no async. The kind of thing that can ship before the infrastructure for those features exists.
This is what shipping looks like at the start. Complexity gets added when scenarios demand it.
The setup
Section titled “The setup”A small e-commerce store has dozens of products, occasional inventory issues, and an operator who’d benefit from a weekly “here’s where things stand” snapshot. Pulling that snapshot by hand takes 15 minutes; the operator skips weeks; signal degrades.
The merchandising agent runs every Monday morning, reads the store’s current state from Shopify, and produces a Markdown report covering inventory, last week’s activity, and one suggested promotion. The report appears in the Worker logs; the operator reads it; they decide what to do with it.
Read-only. The agent cannot create campaigns, modify products, or change anything in Shopify. The output is a recommendation a human reviews and acts on. This is the scope locked in ADR-0025.
The flow
Section titled “The flow”A weekly run, end to end:
Cloudflare cron triggers the Worker:
- Cloudflare cron fires the Worker’s
scheduledhandler on the configured schedule (0 6 * * MON— every Monday at 06:00 UTC) - Worker binds a fresh logger with a
job_idand a single-use Shopify client; constructs the agent definition and the runtime
The agent assembles its facts:
- Agent →
get_shop_infotool: confirms the shop name, currency, and locale. (One Shopify GraphQL call.) - Agent →
list_productstool: retrieves the current catalog with title, type, status, inventory, tags, and variant pricing. (Default limit: 50 products.) - Agent →
recent_orderstool withdaysBack=7: lists orders from the past week with line items and totals.
The agent produces the report:
- Agent runs an LLM turn, composes the Markdown report following the prescribed structure
- Agent → Worker: returns the report as
AgentReport.summary - Worker logs the report at
INFOlevel with structured fields (shop_name,report_length,tool_calls,cost)
The operator reads the log. That’s the deployment. No queue, no event bus, no email — the report appears in Cloudflare’s tail logs and (when configured) in any log sink the Worker is wired to.
What this demonstrates
Section titled “What this demonstrates”| Platform feature | How this scenario uses it |
|---|---|
| Cron-triggered execution | Worker’s scheduled handler invoked by Cloudflare cron; no HTTP path needed |
| Per-job agent construction | Each cron firing builds a fresh agent + Shopify client; no connection pooling |
| Custom tools | get_shop_info, list_products, recent_orders — each wraps one Shopify GraphQL call |
| Hard constraints in prompts | ”Never invent data,” “output ONLY the Markdown report,” “recommendations must reference specific products” — the LLM enforces them |
| Single-agent, no delegation | sub_agents: [], max_delegation_depth: 0 — proves the platform doesn’t require multi-agent setups |
| No long-term memory | long_term_enabled: false — proves the memory subsystem is opt-in per agent |
| Stable Markdown output | The system prompt locks the report structure; reports are diff-able week to week |
The thing this scenario doesn’t exercise is also worth naming: no delegation, no memory, no events. It’s the smallest possible useful agent on the platform. If a Phase 4 customer asks “can I just have a single agent that runs on a schedule?” the answer is “yes, here’s how” — and the merchandising agent is the example.
The artifact
Section titled “The artifact”Merchandising predates the YAML loader
(ADR-0031),
so its definition lives in TypeScript at
apps/worker/src/merchandising.ts. Future migration to YAML
is straightforward (the schema is identical) but hasn’t been
prioritized — it works.
apps/worker/src/merchandising.ts (excerpt)
Section titled “apps/worker/src/merchandising.ts (excerpt)”export function createMerchandisingAgent(): AgentDefinition { return { metadata: { id: 'agent-merchandising' as AgentId, name: 'merchandising_assistant', version: '0.1.0', role: 'main', tags: ['ganimarka', 'merchandising'], }, core_context: { system_prompt: MERCHANDISING_SYSTEM_PROMPT, identity: 'a merchandising assistant for a small e-commerce store', hard_constraints: [ 'never invent data — only use what tools return', 'if recent_orders returns no orders, say so explicitly; never imply false sales', 'output ONLY the Markdown report — no preamble, no meta-commentary', 'recommendations must reference specific products from list_products', ], }, characteristics: { personality: 'pragmatic and concrete', decision_style: 'balanced', tone: 'concise, business-direct', }, tools: ['get_shop_info', 'list_products', 'recent_orders'], sub_agents: [], memory_config: { working_memory_window: 10, long_term_enabled: false, shared_context_scopes: [], }, autonomy: { max_delegation_depth: 0, requires_human_approval: [], allowed_sub_agents: [], }, escalation_rules: [], model_tier: 'main', };}The system prompt (excerpt)
Section titled “The system prompt (excerpt)”You are a merchandising assistant for a small e-commercestore. Each week you produce one report.
Use your tools to gather facts FIRST:1. Call get_shop_info to confirm shop name, currency, and locale.2. Call list_products to see the current catalog.3. Call recent_orders with daysBack=7 to see last week's activity.
Then produce a Markdown report with EXACTLY these sections,in this order:
# Weekly Merchandising Report — {Shop name}, week of {ISO date}
## Inventory snapshotA short paragraph listing how many products are active, howmuch total inventory is on hand, and which products arerunning low (under 5 units) or out of stock.
## Last week's activityIf there were orders, summarize: how many, total revenue,what sold. If there were no orders, say so explicitly. Donot pad.
## This week's recommendationOne concrete action: a promotion, a featured product, acampaign angle. Tie it to specific products from the catalog.Provide:- A one-sentence rationale- A draft banner headline (max 8 words)- A draft landing page subhead (max 20 words)- A suggested discount or angle
## Notes for the operatorAnything that doesn't fit above. Bullets, max 5 items.
Constraints:- Do not invent inventory numbers, order counts, or product details. Use only what the tools return.- Keep the entire report under 600 words.- Use plain Markdown.The system prompt is the agent’s identity. Output structure is prescribed; constraints are enforced; the LLM fills in the specifics from tool output.
A real report
Section titled “A real report”Here’s a real report from a deployed run, lightly redacted (real shop name and product titles). It’s the kind of thing that lands in the operator’s inbox / log every Monday morning:
# Weekly Merchandising Report — Ganimarka, week of 2026-04-27
## Inventory snapshot
42 active products, 1,247 total units on hand. Three productsare running low: "Iznik Cotton Towel — Sand" (4 units), "SultanLinen Robe — XL" (3 units), "Bursa Silk Scarf — Indigo"(2 units). One product is out of stock: "Ottoman Hammam Towel —Burgundy."
## Last week's activity
12 orders, 18 units total, revenue 4,892 SEK. The top performerwas "Iznik Cotton Towel — Sand" (4 units sold). Noout-of-the-ordinary geographic clustering; orders shipped to6 different countries.
## This week's recommendation
Featured product: "Bursa Silk Scarf — Indigo." Inventory isnearly depleted (2 units) and the SKU isn't currentlydiscounted. A "last-2-units" urgency banner converts well atthis price point.
- **Rationale:** scarcity-driven banner on a low-stock, full-margin SKU- **Draft banner headline:** "Only 2 left — Bursa Silk Scarf"- **Draft landing page subhead:** "Hand-loomed indigo silk, finished in Bursa. When they're gone, they're gone."- **Suggested angle:** scarcity, no discount
## Notes for the operator
- "Ottoman Hammam Towel — Burgundy" has been out of stock for 3 weeks. Consider removing from the homepage or restocking.- Three product titles use inconsistent casing: "Iznik Cotton Towel" vs "iznik cotton towel — sand." Worth normalizing.The full report is ~280 words, under the 600-word cap. Cost per run: ~$0.012 (one Sonnet call, three tool roundtrips). Wall time: ~8 seconds.
Run it yourself
Section titled “Run it yourself”# 1. Deploy the Worker (one-time)cd apps/workerpnpm wrangler deploy
# 2. Trigger the merchandising cron manuallypnpm wrangler triggers deploycurl -X POST https://<your-worker>.workers.dev/admin/run-merchandising \ -H "Authorization: Bearer $WORKER_AUTH_TOKEN"
# 3. Tail the logs to see the reportpnpm wrangler tailThe cron itself fires automatically every Monday at 06:00 UTC
once the Worker is deployed. The /admin/run-merchandising
endpoint exists for manual testing — same code path as the
cron firing.
What’s next
Section titled “What’s next”The merchandising scenario is feature-complete for Phase 1. Phase 2 doesn’t change it. Phase 3 (or 4) might:
- Multi-store merchandising — the same agent against multiple Shopify stores, results aggregated
- Memory-aware merchandising — recall last week’s recommendation, note whether it was acted on, adjust this week’s accordingly
- Action-emitting merchandising — emit
shopify_actionsevents for the recommended promotions instead of just reporting on them
None of these are committed to a roadmap. The agent works as designed; a Phase 4 customer who wants more can configure more.
Where to next
Section titled “Where to next”- Order Triage — the multi-agent flagship scenario
- B2B SaaS hypothetical — what a different vertical looks like on the same platform
- ADR-0025 — the original scope decision for this scenario