Install
openclaw skills install personal-shopperPersonal Shopper — multi-agent product/service research and recommendation for Saudi Arabia. USE WHEN: - User asks to find, compare, recommend, or buy a product or service - "what's the best X", "compare X vs Y", "find me a good X" - "أبغى أشتري", "وش أفضل", "قارن لي", "ابحث لي عن" - User asks "is this a good deal" or "should I buy X or Y" - Product comparison by specs, price, or value DON'T USE WHEN: - Market analysis for business entry → use mckinsey-research - Comparing companies as businesses (not products) → use mckinsey-research - Price tracking over time or deal alerts → not supported - Reviewing/troubleshooting a product they already own → answer directly - Simple factual question about a product ("how much RAM does iPhone have") → answer directly - Order placement, returns, or refunds → not supported EDGE CASES: - "أبغى أشتري لابتوب" → this skill - "أبغى أفتح متجر لابتوبات" → mckinsey-research (business, not purchase) - "وش أفضل شاشة" → this skill - "وش حجم سوق الشاشات" → mckinsey-research - "هل السعر هذا حلو على أمازون" → this skill - "حلل لي سوق التجارة الإلكترونية" → mckinsey-research - "قارن لي بين منتجين" → this skill - "قارن لي بين شركتين" → mckinsey-research INPUTS: Product type or name, budget (optional), use case (optional), preferences (optional) TOOLS: sessions_spawn (sub-agents), web_fetch, web_search, camofox_* (with strict limits per agent) OUTPUT: HTML report saved to shopping-reports/{date}-{slug}.html (Arabic, RTL, mobile-friendly) SUCCESS: User gets 3 ranked options with verified prices, source URLs, coupons, and a clear recommendation
openclaw skills install personal-shopperAn agent orchestration skill. The main assistant acts as Router/Orchestrator, spawning sub-agents to research products or services, then scoring and rendering a final Arabic HTML report.
Reference files in
references/provide supplementary detail. If any reference file contradicts this file, follow SKILL.md.
[User Request]
|
[Router] ← main assistant, NOT a sub-agent
|
┌────┼──────────┐
│ │ │
Simple Standard Service
Scout A+K Finder
│ (parallel) │
│ │ │
│ Bargain Verifier
│ (sequential) │
└────┼──────────┘
|
[Court]
|
[Renderer → HTML Report]
The main assistant classifies every request. Do not spawn a sub-agent for routing.
{
"category": "electronics|grocery|medicine|clothing|furniture|services|automotive|toys",
"type": "product|service",
"complexity": "simple|standard|service",
"search_language": "both|ar_only",
"stores_tier1": ["..."],
"stores_tier2": ["..."],
"mainstream_brands": ["brand1", "brand2"],
"query_en": "English search query",
"query_ar": "استعلام بحث عربي"
}
| Path | Trigger | Agents | Token Budget |
|---|---|---|---|
| Simple | ANY 2 of: commodity item, est. price < 50 SAR, exact product specified, fungible | Scout → Court → Renderer | ~115K |
| Standard | Meaningful product differentiation (electronics, furniture, clothing, appliances) | Advocate + Skeptic ‖ → Bargain Hunter → Court → Renderer | ~235K |
| Service | Services (massage, salon, restaurant, repair, delivery) | Finder → Verifier → Court → Renderer | ~155K |
| Category | Language |
|---|---|
| Electronics, Clothing, Furniture | both (EN + AR queries) |
| Grocery, Medicine, Services | ar_only |
Router identifies the top 2-3 dominant brands in the category and passes them as mainstream_brands. These are banned for the Skeptic agent. Examples:
| Category | Stores |
|---|---|
| Electronics | amazon.sa, noon.com, jarir.com, extra.com |
| Grocery | nana.sa, danube.com.sa, carrefourksa.com |
| Medicine | nahdi.sa, al-dawaa.com |
| Clothing | namshi.com, noon.com, 6thstreet.com |
| Furniture | ikea.sa, homebox.sa, noon.com, homezmart.com |
| Services | Google Maps, fresha.com |
| General | noon.com, amazon.sa |
| Category | Stores |
|---|---|
| Electronics | aliexpress.com, ubuy.com.sa |
| Furniture | pan-home.com, abyat.com |
| General | haraj.com.sa, Facebook Marketplace |
| Store | Method | Notes |
|---|---|---|
| amazon.sa | web_fetch ✅ | |
| noon.com | web_fetch ✅ | |
| jarir.com | camofox ⚠️ | JS-heavy |
| extra.com | web_fetch ✅ | |
| nana.sa | camofox ⚠️ | JS-heavy |
| danube.com.sa | camofox ⚠️ | JS-heavy |
| Google Maps | camofox ⚠️ | Or Google Local Pack via DDG |
| All others | web_fetch first, camofox fallback |
This is the most critical section. Token overflow is the #1 cause of agent failure.
PRIMARY — DuckDuckGo Lite via web_fetch
web_fetch("https://lite.duckduckgo.com/lite/?q=YOUR+QUERY+HERE")
Returns ~5K tokens (titles + URLs + snippets). Then web_fetch on promising result URLs for details (~10K tokens each).
SECONDARY — Camoufox (fallback for JS-heavy sites only)
TERTIARY — web_search (Brave API, if available)
1. DDG Lite search (query_ar) → scan results → pick 3-5 URLs
2. DDG Lite search (query_en) → scan results → pick 3-5 URLs [if language=both]
3. web_fetch each promising URL → extract product name, price, specs
4. If a store page fails (JS-required) → camofox_create_tab + camofox_snapshot (max 2)
5. If web_search is available → use it for supplementary queries
Each agent is spawned as a sub-agent with a specific task prompt, input data, and output schema.
When: Simple path selected by Router.
Task prompt:
Find the top 3 options for a commodity product in Saudi Arabia (Riyadh). Focus on availability and price. Use DuckDuckGo Lite as primary search.
Input from Router:
{
"query_ar": "...",
"query_en": "...",
"search_language": "ar_only|both",
"stores_tier1": ["..."],
"category": "..."
}
Instructions:
query_ar (and query_en if search_language=both)web_fetch calls totalcamofox snapshot (only if critical store is JS-blocked)camofox_screenshot and save to shopping-reports/screenshots/{date}-{slug}.png. Include path in output.Output schema:
{
"candidates": [
{
"name": "Product Name",
"brand": "Brand",
"price_sar": 29.99,
"store": "noon.com",
"source_url": "https://...",
"price_from_page": true,
"screenshot_path": "shopping-reports/screenshots/2026-02-19-product-name.png",
"notes": "Free delivery, in stock"
}
],
"search_summary": "Searched 3 stores, found 5 listings, selected top 3 by price"
}
Token budget: 60K
When: Standard path. Runs in parallel with Skeptic.
Task prompt:
Find the BEST product in this category regardless of price. Prioritize quality, build, real user reviews, and long-term value. The goal is the best possible product for the user.
Input from Router:
{
"query_ar": "...",
"query_en": "...",
"search_language": "both",
"stores_tier1": ["..."],
"category": "..."
}
Instructions:
web_fetch calls, max 2 camofox snapshotscamofox_screenshot immediately and save to shopping-reports/screenshots/{date}-{brand-model-slug}.png. Create the folder if it doesn't exist. Include path in output.Output schema:
{
"candidates": [
{
"name": "Product Name",
"brand": "Brand",
"price_sar": 599,
"store": "amazon.sa",
"source_url": "https://...",
"price_from_page": true,
"screenshot_path": "shopping-reports/screenshots/2026-02-19-brand-model.png",
"quality_evidence": "4.6★ on 2,300 reviews, recommended by rtings.com",
"why_best": "Highest color accuracy in price range, 3-year warranty"
}
],
"search_summary": "..."
}
Token budget: 60K
When: Standard path. Runs in parallel with Advocate.
Task prompt:
Find alternatives the mainstream ignores. BANNED from recommending these brands: {mainstream_brands}. Find genuinely different products — not variations of popular ones. Check Tier 2 stores. Look for underdog brands with real quality.
Input from Router:
{
"query_ar": "...",
"query_en": "...",
"search_language": "both",
"stores_tier1": ["..."],
"stores_tier2": ["..."],
"mainstream_brands": ["Samsung", "LG"],
"category": "..."
}
Instructions:
mainstream_brandsweb_fetch calls, max 2 camofox snapshotscamofox_screenshot immediately and save to shopping-reports/screenshots/{date}-{brand-model-slug}.png. Include path in output.Output schema: Same as Advocate, plus:
{
"candidates": [
{
"name": "...",
"brand": "...",
"screenshot_path": "shopping-reports/screenshots/2026-02-19-brand-model.png",
"why_different": "Chinese brand with 90% of Samsung quality at 60% price, popular on r/monitors"
}
]
}
Token budget: 60K
When: Standard path. Runs AFTER Advocate and Skeptic complete.
Task prompt:
Given a list of products already researched, find the best LOCAL price for each, check for coupons/cashback/installments, and advise on timing. Do NOT search for new products.
Input: Combined candidate list from Advocate + Skeptic (deduplicated).
Instructions:
web_fetch calls (hard cap — plan carefully)camofox unless absolutely necessary (max 1)Output schema:
{
"price_checks": [
{
"candidate_name": "...",
"best_price_sar": 499,
"best_store": "noon.com",
"source_url": "https://...",
"price_from_page": true,
"coupon": "SAVE50 on almowafir.com (-50 SAR)",
"cashback": "Al Rajhi 5% on noon.com",
"installments": "Tamara 4x125 SAR",
"effective_price_sar": 424
}
],
"timing": {
"recommendation": "buy_now|wait|unclear",
"reason": "Ramadan sale expected in 3 weeks, historically 20-30% off electronics on noon",
"wait_until": "2026-03-10"
}
}
Token budget: 60K
When: Service path selected.
Task prompt:
Locate and rank local services in Riyadh, Saudi Arabia. Use Google Local Pack results via DuckDuckGo. Focus on: rating, review count, price range, location.
Input from Router:
{
"query_ar": "مساج رياض",
"category": "services",
"stores_tier1": ["Google Maps", "fresha.com"]
}
Instructions:
{query_ar} الرياض and {query_ar} site:fresha.comweb_fetch on top results for prices and detailsweb_fetch calls, max 2 camofox snapshotsOutput schema:
{
"candidates": [
{
"name": "Spa Name",
"rating": 4.7,
"review_count": 342,
"price_range": "200-400 SAR",
"address": "حي العليا، الرياض",
"source_url": "https://...",
"hours": "10AM-12AM",
"notes": "Highly rated for deep tissue"
}
]
}
Token budget: 60K
When: Service path. Runs after Finder.
Task prompt:
Given the Finder's top 2 service picks, verify they are real, open, and accurately described. Check reviews for authenticity, confirm prices, confirm operating hours.
Input: Finder's top 2 candidates.
Instructions:
web_fetch each candidate's source URL — confirm it loads, info matches"{service name}" review الرياض)web_fetch callsOutput schema:
{
"verifications": [
{
"candidate_name": "...",
"verified": true,
"price_confirmed": true,
"still_open": true,
"review_authenticity": "high|medium|low",
"red_flags": [],
"notes": "Reviews look genuine, mix of 3-5 stars, specific details mentioned"
}
]
}
Token budget: 40K
When: All paths, after research agents complete.
Task prompt:
Score all candidates using the weighted scoring framework. Do NO searching. Only judge based on data provided. Be strict. Apply all rules.
Input: All candidate data + Bargain Hunter/Verifier data (if applicable) + scoring weights for category.
Instructions:
web_fetch one source_url, confirm product/service existsCourt Rules:
source_url → score capped at 30 (effectively eliminated)price_from_page: false → Source Trust capped at 60; if estimated → capped at 40Output schema:
{
"rankings": [
{
"rank": 1,
"name": "...",
"brand": "...",
"score": 82,
"breakdown": {
"value": 25,
"quality": 22,
"availability": 9,
"source_trust": 13,
"deal_quality": 13
},
"source_url": "...",
"screenshot_path": "shopping-reports/screenshots/2026-02-19-brand-model.png",
"price_sar": 499,
"effective_price_sar": 424,
"verdict": "Best overall value with strong reviews and active coupon"
}
],
"spot_check": {
"url": "...",
"result": "pass|fail",
"notes": "Product page exists, price matches"
},
"fallback_needed": false,
"fallback_instruction": null
}
Token budget: 30K
When: All paths, after Court completes.
Task prompt:
Build an Arabic HTML report from the Court's output using the جاك العلم brand system. Output must be RTL, mobile-friendly (Telegram-width), visually polished.
Input: Court rankings + all metadata (timing, coupons, etc.)
⚠️ CRITICAL — Brand Files (READ BEFORE GENERATING):
references/brand-guideline.md — colors, typography, card design, brand voicereferences/html-template.md — the exact HTML template to useInstructions:
screenshot_path exists, read the file and base64-encode it. Embed as <img src="data:image/png;base64,{b64}"> inside the product card. If file missing or unreadable, skip gracefully (no broken image icon).shopping-reports/{date}-{query_slug}.htmlScreenshot embedding code (Python):
import base64, os
ALLOWED_DIR = os.path.abspath("shopping-reports/screenshots")
def embed_screenshot(path):
if not path:
return None
abs_path = os.path.abspath(path)
# Only read files inside the allowed screenshots directory
if not abs_path.startswith(ALLOWED_DIR):
return None
if not abs_path.endswith(".png"):
return None
if os.path.exists(abs_path) and os.path.getsize(abs_path) < 5_000_000:
with open(abs_path, 'rb') as f:
return base64.b64encode(f.read()).decode()
return None
Report Sections (in order):
| # | Section | Content |
|---|---|---|
| 1 | الغاية | What the user asked for |
| 2 | الطريقة | Which path was used, how many agents, stores checked — البلاسيبو: اعرض عدد المصادر + خطوات البحث |
| 3 | المصادر | List of stores/URLs consulted |
| 4 | العرض | 3 product/service cards — use card template from brand-guideline.md |
| 5 | رأي المحكمة | Court's verdict, scoring breakdown (collapsible <details>) |
| 6 | السعر | Price comparison table, effective prices after coupons |
| 7 | التوصيل | Delivery info per store |
| 8 | التوقيت | Timing recommendation (buy now / wait / unclear + reason) |
| 9 | التوصية | Final recommendation — one clear pick with reasoning, brand voice |
Design Rules (from references/brand-guideline.md):
#F8F7F4 (Canvas) — NOT #f5f5f5Token budget: 35K (includes reading brand-guideline.md + html-template.md)
Worked Example — Kill Doubt Text:
Good: "نفس شريحة M4 اللي في MacBook Pro بس بسعر أقل بـ 40%. الفرق الوحيد حجم الشاشة. لو شغلك مو على شاشة خارجية هذا الخيار الأذكى"
Bad: "نوصي بشدة بهذا المنتج الرائع الذي يتميز بمواصفات عالية الجودة"
The first kills doubt. The second is generic AI filler. Always write like the first.
| Criterion | Electronics | Grocery | Clothing | Furniture | Medicine | General |
|---|---|---|---|---|---|---|
| Value (price/perf) | 30% | 40% | 25% | 30% | 40% | 30% |
| Quality Signal | 25% | 15% | 20% | 25% | 20% | 20% |
| Availability | 10% | 20% | 15% | 10% | 20% | 15% |
| Source Trust | 15% | 15% | 15% | 15% | 15% | 15% |
| Deal Quality | 20% | 10% | 25% | 20% | 5% | 20% |
| Criterion | Weight |
|---|---|
| Rating | 30% |
| Review Volume | 15% |
| Price | 25% |
| Location (Riyadh proximity) | 15% |
| Verification | 15% |
Value (price/performance): How much you get per SAR. Cheapest ≠ best value — a 500 SAR item lasting 5 years beats a 200 SAR item lasting 1 year.
Quality Signal: Review scores (weighted by count), expert reviews, build materials, warranty length. Community evidence (Reddit, forums) > marketing specs.
Availability: In stock? Local delivery? Same-day/next-day? International-only → capped at 30.
Source Trust: Known store? Price verified on page? Secure checkout? source_url required or score capped at 30. price_from_page: false → capped at 40.
Deal Quality: Active coupons, cashback, installment options, bundle deals. Higher = more savings available right now.
When a store is unreachable or returns no results:
1. web_fetch fails → retry once with different URL pattern
2. Still fails → try camofox (if within budget)
3. camofox fails → mark store as "غير متاح" and move to next store
4. If ALL Tier 1 stores fail → switch to Tier 2 stores
5. If ALL stores fail → return partial results with clear note: "تعذر الوصول لبعض المتاجر"
6. Never hallucinate prices or availability from failed fetches
Court returns fallback_needed: true
→ Router reads fallback_instruction
→ Max 1 retry
→ Retry MUST change something:
- Different query terms
- Different stores (add Tier 2)
- Different language (try EN if was AR-only)
→ Re-run the same path with changes
→ If still < 2 results after retry:
- Generate "limited results" report
- Include manual search suggestions
- Be honest: "لم نجد خيارات كافية"
Every candidate in every path must include:
| Field | Required | Effect if Missing |
|---|---|---|
source_url | Yes | Score capped at 30 |
price_from_page | Yes | If false → Source Trust capped at 40 |
store | Yes | Used for delivery/trust assessment |
Court spot-check: The Court web_fetches 1 random source_url per run to confirm the product/service exists and price is approximately correct.
1. User: "أبي شاشة كمبيوتر 27 بوصة للتصميم"
2. Router classifies:
- category: electronics
- type: product
- complexity: standard
- search_language: both
- stores_tier1: [amazon.sa, noon.com, jarir.com, extra.com]
- stores_tier2: [aliexpress.com, ubuy.com.sa]
- mainstream_brands: [Samsung, LG]
- query_en: "27 inch monitor for design color accurate"
- query_ar: "شاشة 27 بوصة للتصميم دقة ألوان"
3. Router spawns Advocate + Skeptic IN PARALLEL:
- Advocate gets: query, language=both, tier1 stores
- Skeptic gets: query, language=both, tier1+tier2 stores, banned=[Samsung, LG]
4. Both complete → Router collects results → deduplicates
5. Router spawns Bargain Hunter SEQUENTIALLY:
- Input: deduplicated candidate list from step 4
- Checks prices, coupons, timing
6. Bargain Hunter completes → Router spawns Court:
- Input: all candidates + bargain data + scoring weights for electronics
7. Court scores, ranks, spot-checks → output top 3
8. Router spawns Renderer:
- Input: Court output + all metadata
- Generates HTML report → saves to shopping-reports/
9. Router sends report to user
1. User: "أبي بطاريات AA"
2. Router classifies:
- commodity ✓, price < 50 SAR ✓ → Simple path
- category: grocery (general)
- search_language: both
- stores: [noon.com, amazon.sa, nana.sa]
3. Router spawns Scout only → finds 3 options
4. Router spawns Court → scores
5. Router spawns Renderer → HTML report
1. User: "أبي مساج في الرياض"
2. Router classifies:
- type: service → Service path
- search_language: ar_only
- stores: [Google Maps, fresha.com]
3. Router spawns Finder → finds 5 services
4. Router spawns Verifier → verifies top 2
5. Router spawns Court → scores (service weights)
6. Router spawns Renderer → HTML report
Use the platform's sub-agent mechanism. Each agent gets:
shopping-{agent_name} (e.g., shopping-advocate)web_fetch, web_search, camofox_* (with limits stated per agent)Advocate and Skeptic can run simultaneously. Spawn both, wait for both to complete before spawning Bargain Hunter.
Simple: Scout → Court → Renderer
Standard: [Advocate ‖ Skeptic] → Bargain Hunter → Court → Renderer
Service: Finder → Verifier → Court → Renderer
Standard path runs 5+ sequential agent steps and can exceed 200K tokens. Design for continuity:
previous_response_id when continuing multi-step orchestration in the same thread.null and continue with available data.shopping-reports/ directory.shopping-reports/{date}-{query_slug}.htmlshopping-reports/screenshots/{date}-{brand-model}.pngshopping-reports/ directory (reports and screenshots)references/ within this skill, and shopping-reports/screenshots/*.png for base64 embeddingThese are hard-won. Violating any of these will produce bad results.
| Don't | Why | Do Instead |
|---|---|---|
| Don't tell Skeptic to "look for alternatives" | Produces the same mainstream products with different wording | Ban specific brands: mainstream_brands: ["Samsung", "LG"] |
| Don't use Camoufox for search result pages | 50K tokens per snapshot, overflows context | Use DDG Lite (~5K tokens) for search. Camofox only for specific product pages |
| Don't pass raw HTML to Court | Court crashes or hallucinates from unstructured data | Always pass structured JSON summaries from research agents |
| Don't spawn Bargain Hunter before researchers finish | Missing candidate data causes empty price checks | Enforce sequential: Advocate+Skeptic complete → then Bargain Hunter |
| Don't add mainstream_brands after Skeptic starts searching | Bans are ineffective retroactively | Router must pass brands in the initial spawn payload |
| Don't assume web_search is available | Brave API key may be missing | DDG Lite is the guaranteed fallback. Always try it first |
| Don't skip timing advice | Users overpay by 30-40% buying before sales | Bargain Hunter always checks: Ramadan, White Friday, 11.11, back-to-school |
| Don't trust marketing specs over community reviews | Specs lie. Real users don't | Agents prioritize Reddit, forums, real-user reviews over product page claims |
| Don't use Standard path for batteries or USB cables | Wastes ~120K tokens on commodity items | Use Simple path when ANY 2 of: commodity, <50 SAR, exact product specified, fungible |
| Don't use Advocate+Skeptic for services | Services need location, ratings, hours — not specs and builds | Use Finder+Verifier path for services |
| Don't skip screenshots when using Camofox | Screenshots are free (0 tokens) and make reports trustworthy | Always camofox_screenshot right after opening a product page |
| Component | Simple | Standard | Service |
|---|---|---|---|
| Router | 5K | 5K | 5K |
| Scout | 60K | — | — |
| Advocate | — | 60K | — |
| Skeptic | — | 60K | — |
| Bargain Hunter | — | 60K | — |
| Finder | — | — | 60K |
| Verifier | — | — | 40K |
| Court | 30K | 30K | 30K |
| Renderer | 35K | 35K | 35K |
| Total | ~130K | ~250K | ~170K |