Pawlo Nuance-as-a-Service — Design Document
Date: 2026-02-24 Status: Approved Author: Eric Yeung + Claude
1. Strategic Thesis
Section titled “1. Strategic Thesis”SaaS is dead. The cost to create software is effectively zero — any operator with a Claude or Gemini subscription can generate personalized tooling on demand. Software is no longer defensible.
What is defensible: structured business intelligence that no scraper can get.
Deal readiness. Buyer preferences. Hidden differentiators. Referral maps. Motivated inventory. The nuance that lives inside a sales manager’s head and never makes it onto AutoTrader or a company website.
Pawlo is building nuance-as-a-service — a structured intelligence layer that AI agents consume on behalf of humans making buying decisions. The product is not software. The product is data.
2. The Two-Sided Marketplace
Section titled “2. The Two-Sided Marketplace”Supply side: Real-world businesses with high-value, time-sensitive inventory signals and structured profiles.
Demand side: AI agents acting on behalf of humans researching and executing buying decisions.
Pawlo: The MCP server sitting in the middle — the structured context layer that demand agents query.
Why performance-based monetization wins
Section titled “Why performance-based monetization wins”Dealers do not want another SaaS tool. They want to sell cars. Asking them to pay a monthly subscription fee before they’ve seen a single result makes Pawlo look like every other software vendor they’ve said no to.
Dealers already pay for leads — AutoTrader, CarGurus, Kijiji. They understand the model. By making onboarding free and charging only on transaction, Pawlo removes all supply-side friction and delivers leads that are categorically better than anything they’re currently buying: pre-qualified by intent, matched to specific inventory, ready to move.
The business model:
- Supply onboarding: free
- Demand queries: free during validation
- Charge: $100 per qualified lead delivered (validation phase). Raises significantly once proven.
3. Supply Tiers
Section titled “3. Supply Tiers”Tier 1: Short-Term Supply (Ephemeral Deal Signals)
Section titled “Tier 1: Short-Term Supply (Ephemeral Deal Signals)”High-urgency, time-sensitive inventory signals that are never published anywhere. The data that disappears when the car sells or the week ends.
Examples:
- “Moving 3 F-150s by Friday. Will take $2,000 off.”
- “2023 Explorer sitting 90 days. Motivated. Call me.”
- “Overstocked on Tacomas. Mid-trim. Move them this month.”
Properties:
- TTL-based: expires at dealer-specified date or 7 days by default
- Automatically drops from queryable layer on expiry
- High match value — buyer agents looking for “ready to move” signals find these first
Tier 2: Long-Term Supply (Inventory + Profile)
Section titled “Tier 2: Long-Term Supply (Inventory + Profile)”Persistent structured intelligence about the dealership: who they are, what they carry, what kind of buyers they want, what makes them different.
Populated through:
- Initial onboarding questionnaire (5 questions via SMS conversation)
- Ongoing profile updates the dealer texts in
- Eventually: DMS integration for live inventory feed (future phase)
Properties:
- Persists until explicitly updated or overwritten
- Powers the structured dealer profile in the MCP
- Enables specific queries: “black Explorer RS trim, this weekend, Calgary”
4. SMS Intake Architecture
Section titled “4. SMS Intake Architecture”Why SMS over Telegram
Section titled “Why SMS over Telegram”Telegram requires downloading an app and creating an account. SMS requires nothing. Dealers text all day. Zero friction means supply flows immediately.
Single Twilio Number Per City
Section titled “Single Twilio Number Per City”One local number covers all dealers in a city. A Calgary 403 number builds trust and costs $1.50/month. No per-dealer phone provisioning.
Routing is solved through the First-Time Handshake.
First-Time Handshake
Section titled “First-Time Handshake”Setup: Each onboarded dealership receives a unique invite code generated at onboarding.
- Bob’s Ford →
BOBS-VIP - Calgary Honda →
HONDA-VIP
Flow for a known sender:
Dealer texts → Gateway checks phone number → known → route to dealer context → extract deal signal → validate → write to data storeFlow for an unknown sender:
Dealer texts "I have an F-150 ready to go for $3k off" → Gateway checks phone number → unknown → Save original message to KV pending:{phone} (1hr TTL) → Reply: "Welcome to Pawlo. This phone isn't linked yet. Reply with your Dealership Invite Code." → Dealer replies: "BOBS-VIP" → Gateway: verify code → link phone to Bob's Ford → pull pending KV → process held message → delete pending KV → Reply: "You're linked to Bob's Ford. Your F-150 deal is now live on the network."Critical: The original message is saved to KV with a 1hr TTL and processed after the handshake completes. The dealer never has to re-send it. If the original message is dropped, you’ve introduced friction at the worst possible moment — first contact.
Multiple senders per dealership: A GM, a sales manager, and a floor rep can all register their personal numbers under the same dealership by using the same invite code. Each phone gets linked individually. All messages route to the same dealer context.
5. Event-Driven Gateway (Option D)
Section titled “5. Event-Driven Gateway (Option D)”The Gateway is the central nervous system. It sits between the SMS intake and the data store and ensures only clean, validated data enters the system.
Why not write directly from the agent to the database
Section titled “Why not write directly from the agent to the database”If the agent writes directly (Option A), hallucinations and bad formatting corrupt the data store. If you pull on a schedule (Option C), you lose the real-time window on ephemeral deals. The Gateway gives you both: instant writes of clean data.
Infrastructure: Cloudflare (fully serverless)
Section titled “Infrastructure: Cloudflare (fully serverless)”The entire system runs on Cloudflare. No VPS. No server management. Scales automatically, globally distributed, costs near-zero at validation scale.
| Concern | Cloudflare Primitive |
|---|---|
| Phone → dealer_id routing | KV namespace: phone_links (permanent) |
| Invite code → dealership mapping | KV namespace: invite_codes (permanent) |
| Pending message during handshake | KV namespace: pending (1hr TTL) |
| Deals + profiles (queryable) | D1 (SQLite at the edge) |
| Gateway + MCP logic | Workers |
Why not Cloudflare Queues: Queues are built for background jobs, not conversational state. A Queue fires immediately and cannot pause mid-conversation waiting for a human to text back an invite code minutes or hours later. KV with TTL is the correct primitive — it holds state across two separate Twilio webhook events without any background job complexity.
Gateway Responsibilities
Section titled “Gateway Responsibilities”Twilio webhook fires → Gateway Worker → KV phone_links lookup (phone → dealer_id)
→ UNKNOWN sender: Save original message to KV pending:{phone} (TTL: 1hr) Reply: "Welcome to Pawlo. Reply with your Dealership Invite Code."
→ Invite code received (new webhook, same unknown phone): Look up code in KV invite_codes → get dealer_id Write phone → dealer_id to KV phone_links (permanent) Pull original message from KV pending:{phone} Delete KV pending:{phone} Process original message (continue to extraction below) Reply: "You're linked to [Dealership Name]. Your deal is now live on the network."
→ KNOWN sender (or after handshake completes): AI extraction: natural language → structured JSON payload Handles three intents: - new_deal: extract and write to D1 - cancel_last: delete most recent deal for this dealer - none: ask them to be more specific Schema validation (new_deal only): - price is a number - make/model are valid strings - expiry date is a real YYYY-MM-DD in Mountain Time (America/Edmonton) - required fields present Valid → write to D1 (millisecond latency) Invalid → reply asking for clarification ("What's the price you're offering?")Structured JSON Payload (Short-Term Supply)
Section titled “Structured JSON Payload (Short-Term Supply)”{ "dealer_id": "bobs-ford-calgary", "type": "ephemeral_deal", "make": "Ford", "model": "F-150", "year": 2024, "trim": null, "units_available": 3, "discount": 2000, "bottom_line_price": null, "expires_at": "2026-02-28", "notes": "Moving 3 units by Friday", "raw_message": "Moving 3 F-150s by Friday. Will take $2,000 off.", "created_at": "2026-02-24T14:32:00Z"}D1 Data Store
Section titled “D1 Data Store”Two tables:
deals — ephemeral deal signals, expires_at indexed, expired rows excluded from MCP queries
profiles — persistent dealer records, updated in place
Both are queryable by the MCP Worker with zero delay via D1 SQL.
6. Pawlo MCP Server
Section titled “6. Pawlo MCP Server”The MCP is the read layer. Demand-side agents query it using the Model Context Protocol. It never handles writes — that is the Gateway’s job.
Architecture: Two-Step Query (Context7 Pattern)
Section titled “Architecture: Two-Step Query (Context7 Pattern)”The MCP forces a strict two-step routing process rather than a single generic search. This is what makes the system sector-agnostic and infinitely scalable. The agent cannot call fetch_deals directly — it must resolve the sector first.
Step 1 → Resolver → Step 2 → Fetcher
Just as Context7 forces agents to call resolve-library-id before query-docs, Pawlo forces agents to call resolve_sector_id before fetch_deals or fetch_profiles. This separation of concerns keeps the query layer clean regardless of how many sectors the marketplace covers.
Tools Exposed
Section titled “Tools Exposed”resolve_sector_id ← Step 1, always call first
Input: { intent: "Find me a deal on an F-150" } { intent: "My dog needs emergency teeth cleaning in Calgary" } { intent: "/retail/auto" } ← direct sector ID also accepted
Output: { sector_id: "/retail/auto", label: "Auto Dealership", description: "..." } { error: "Could not resolve", available_sectors: [...] } ← if ambiguousResolves a buyer’s fuzzy intent to a canonical Pawlo sector ID. Uses keyword matching — fast, no AI call needed. Returns the full sector list if intent is ambiguous so the calling agent can choose.
fetch_deals ← Step 2a (use after resolve_sector_id)
Input: { sector_id: "/retail/auto", location: "Calgary", attributes: { make: "Ford", model: "F-150" } } { sector_id: "/services/veterinary", location: "Calgary", attributes: { animal_type: "dogs" } }
Output: array of non-expired deal signals with profile contextFilters by sector_id first, then location, then flexible attributes. The attributes parameter accepts any key-value pairs — make/model for auto, animal_type/service_type for vet, trade/availability_window for home services. No schema changes required when adding a new sector.
fetch_profiles ← Step 2b (use for capability search, not just live deals)
Input: { sector_id: "/retail/auto", location: "Calgary", attributes: { specialties: "trucks" } } { sector_id: "/services/veterinary", attributes: { emergency: true } }
Output: array of matching business profiles, ordered by live_deal_count DESCReturns profiles enriched with their current live deal count. Use when the buyer wants a specific type of business, not necessarily a business with an active deal.
Sector Registry (built-in, v1)
Section titled “Sector Registry (built-in, v1)”| Sector ID | Label | Example Attributes |
|---|---|---|
/retail/auto | Auto Dealership | make, model, year, trim, units_available |
/services/veterinary | Veterinary Clinic | animal_type, service_type, emergency |
/services/grooming | Pet Grooming | animal_type, breed_specialty |
/services/home | Home Services | trade, service_type, availability_window |
/food/restaurant | Restaurant | cuisine_type, occasion, capacity |
/health/physiotherapy | Physiotherapy | specialty, accepts_direct_billing |
Adding a new sector requires: one new entry in the SECTORS constant, one new keyword block in SECTOR_KEYWORDS. No schema changes. No new tools.
Authentication
Section titled “Authentication”All MCP requests require Authorization: Bearer <MCP_API_KEY>. The key is a hardcoded secret set via wrangler secret put MCP_API_KEY. Requests without a valid key receive a 401 Unauthorized. This is a single shared key for the validation phase — per-agent keys come later when demand-side billing is introduced.
What the MCP Does Not Do
Section titled “What the MCP Does Not Do”- No writes (Gateway owns writes)
- No buyer identity (demand side is anonymous at this phase)
- No payment processing (out of scope)
- No real-time inventory feed (long-term supply phase)
7. Validation Experiment
Section titled “7. Validation Experiment”The Pitch to First 5 Dealers
Section titled “The Pitch to First 5 Dealers”“I have a private network of buyers. I’m giving you a VIP text number. When you have a car you need to move, text the details and your bottom-line price to this number. If I send you a buyer who takes the deal, you pay me $100. If I send no one, you pay nothing.”
What We’re Testing
Section titled “What We’re Testing”Supply validation: Do dealers actually text the bot? Are the signals real and high-value? If no one texts, the data pipeline fails before the MCP matters.
Demand validation (manual): We do not have a buyer agent network yet. For the first 5 deals, we manually source or identify the buyer. This is the Wizard of Oz phase — fake the automation, prove the transaction.
Willingness to pay: When a buyer shows up and closes the deal, does the dealer pay the $100 gladly? If yes, the model works and the fee can be raised significantly.
Success Criteria
Section titled “Success Criteria”- At least 3 of 5 dealers text a real deal signal within 7 days of onboarding
- At least 1 transaction closes through a Pawlo-sourced match
- Dealer pays without friction
What Happens After Validation
Section titled “What Happens After Validation”- Raise the lead fee (market rate for a pre-qualified buyer is far above $100)
- Expand to 20 dealers in Calgary
- Build the automated demand side (buyer agents querying the MCP)
- Introduce long-term supply (inventory profile onboarding)
8. Out of Scope (This Phase)
Section titled “8. Out of Scope (This Phase)”- Buyer-facing product — consumer AI agent, buyer app, buyer onboarding
- DMS integration — live inventory feed from dealer management systems
- Payment infrastructure — automated billing, Stripe, invoicing
- Per-agent MCP keys — single shared key for validation; per-agent billing keys come after demand-side monetization is introduced
- Multi-city expansion — prove Calgary first
- Telegram — replaced by SMS
- Per-dealer sandboxed OpenClaw agents — replaced by single Gateway with per-dealer context records
9. Architecture Diagram
Section titled “9. Architecture Diagram”[Dealer's Phone] │ SMS ▼[Twilio 403 Local Number] │ Webhook ▼[Gateway Worker] ──────────────────────────── Cloudflare │ │ ├── KV: phone_links (phone → dealer_id) │ ├── KV: invite_codes (code → dealer_id) │ ├── KV: pending:{phone} (original msg, 1hr TTL) │ │ │ ├── UNKNOWN? → save to pending KV → request code │ ├── INVITE CODE? → link → pull pending → process │ ├── KNOWN? → AI extraction → schema validation │ └── Valid → write to D1 │ │ │ ▼ │ [D1 Database] │ ├── deals (expires_at indexed) │ └── profiles (persistent) │ │ │ ▼ │ [MCP Worker] │ ├── search_deals │ ├── find_inventory │ ├── get_dealer_profile │ └── match_buyer │ │ ─────┘ ▼ [Demand-Side AI Agents] (acting on behalf of human buyers)Hosting: Entirely Cloudflare. Two Workers (Gateway + MCP), one D1 database, three KV namespaces. No VPS, no servers, no ops overhead.