ChanlChanl
Side Hustle

Build a Restaurant AI That Remembers Every Regular

Build an AI phone agent for a local restaurant that takes orders, answers menu questions, and remembers regulars. A developer side hustle worth $400/month per client.

DGDean GroverCo-founderFollow
March 27, 2026
14 min read
Warm watercolor illustration of a cozy Italian restaurant kitchen with a phone ringing on the counter

Maria runs Bella Notte, a 40-seat Italian place in the suburbs. She's got a problem she can't hire her way out of: between 5:30 and 8:30 PM, her three-person kitchen staff is plating entrees, not answering phones. She loses 15 to 20 phone orders a week. She knows this because her voicemail fills up every Friday night.

Online ordering apps would solve the phone problem. But DoorDash and Uber Eats take 30%. On a $30 pasta dinner, that's $9 gone. Multiply by 80 takeout orders a week and Maria's handing over $700 a month to a middleman.

Then there's Tony. Tony calls every Thursday and says "the usual." Whoever picks up doesn't know Tony. Tony has to recite "large pepperoni, extra cheese, side of garlic knots, Diet Coke" every single time. Tony's been coming here for six years. He shouldn't have to do that.

Maria would pay $400 a month for something that answers the phone during rush, takes orders, knows the menu well enough to answer "is the eggplant parm gluten-free?", and remembers that Tony wants the usual. That's the agent we're going to build.

What Are You Actually Selling?

A phone agent that takes real orders, answers real questions about allergens and specials, and recalls customer preferences across visits. One agent serves the phone line and the restaurant's website. Maria pays you monthly, makes more money than she spends, and you get recurring revenue from every client.

The architecture is simple. You build the backend in Chanl: the menu knowledge base, the ordering tools, the customer memory. Then you connect a voice platform (VAPI, Retell, or any provider) to Chanl via MCP. The voice platform handles the call. Chanl handles everything the agent needs to know and do.

text
Phone rings → VAPI/Retell answers
              → Connects to Chanl MCP endpoint
              → Gets: menu knowledge, ordering tools, customer memory
              → Agent answers questions, takes orders, remembers Tony

Five stages: Build the backend, Connect the voice agent, Test with realistic scenarios, Deploy to a real phone number, Monitor every conversation.


Build: Turn the Menu into a Knowledge Base

Every restaurant question starts with the menu. "What's in the vodka sauce?" "Do you have anything vegan?" "How much is the chicken parm?" The agent needs to know the menu cold.

This is what a knowledge base does. You upload the menu, the system chunks it into searchable pieces, and when someone asks a question the agent searches for the most relevant chunks using semantic similarity. "Anything without dairy" matches the grilled salmon entry even though the word "dairy" doesn't appear on the menu.

bash
npm install @chanl/sdk
typescript
import { Chanl } from '@chanl/sdk'
import { readFileSync } from 'fs'
 
const chanl = new Chanl({ apiKey: process.env.CHANL_API_KEY })
 
// Upload Maria's menu PDF
const menuPdf = readFileSync('./bella-notte-menu.pdf')
const { data: menuEntry } = await chanl.knowledge.createFromFile(
  menuPdf,
  'bella-notte-menu.pdf',
  { title: 'Bella Notte Full Menu' }
)
 
console.log(`Menu uploaded: ${menuEntry.id}`)
console.log(`Processing status: ${menuEntry.processingStatus}`)

The PDF gets processed automatically: parsed, chunked into overlapping segments, and embedded for vector search. Once it's done, the agent can search it. But menus aren't the only knowledge a restaurant agent needs. Maria's got a rotating specials board, allergy notes that aren't on the printed menu, and holiday hours. Add those as text entries:

typescript
await chanl.knowledge.create({
  title: 'Weekly Specials Rotation',
  source: 'text',
  content: `Monday: Half-price appetizers after 7 PM
Tuesday: Buy one get one pasta (dine-in only)
Wednesday: Wine flight special - 3 glasses for $18
Thursday: Family meal deal - large pasta, salad, bread for $45
Friday-Saturday: Chef's special (changes weekly, ask staff)
Sunday: Brunch menu 10 AM - 2 PM, regular menu after 3 PM`
})
 
await chanl.knowledge.create({
  title: 'Allergen and Dietary Notes',
  source: 'text',
  content: `Gluten-free pasta available for any pasta dish (+$3).
Eggplant parm uses regular breadcrumbs (NOT gluten-free).
Vodka sauce contains heavy cream (not dairy-free).
Grilled salmon and grilled chicken are naturally gluten-free.
Vegan options: marinara pasta (request no cheese), garden salad,
bruschetta (request no cheese), minestrone soup.
All fried items share a fryer with gluten-containing foods.`
})

Quick test to make sure the search actually works before we go further:

typescript
const { data: results } = await chanl.knowledge.search({
  query: 'Is the eggplant parmesan gluten-free?',
  limit: 3
})
 
results.results.forEach(r => {
  console.log(`[${r.score.toFixed(2)}] ${r.content.slice(0, 100)}...`)
})
// [0.92] Eggplant parm uses regular breadcrumbs (NOT gluten-free)...

If a search returns low scores or wrong results, the fix is almost always better content. Add more detail to your knowledge entries. Break up dense paragraphs. The knowledge base is only as good as what you put in it.

Build: Give the Agent Tools to Take Orders

Knowing the menu is half the job. The other half is taking orders. When someone calls and says "I'd like a large pepperoni with extra cheese," the agent needs to actually record that order. A knowledge base answers questions. Tools take actions.

You define tools in Chanl with a name, description, and input schema. When the voice agent connects via MCP, it discovers these tools automatically. The LLM reads the description and decides when to call each one.

typescript
await chanl.tools.create({
  name: 'add_to_order',
  description: 'Add a menu item to the current phone order. Call this when a customer wants to order something. Include any modifications they mention (extra cheese, no onions, gluten-free pasta, etc).',
  type: 'http',
  inputSchema: {
    type: 'object',
    properties: {
      item: {
        type: 'string',
        description: 'Menu item name (e.g., "Large Pepperoni Pizza")'
      },
      quantity: {
        type: 'number',
        description: 'How many (default 1)'
      },
      modifications: {
        type: 'array',
        items: { type: 'string' },
        description: 'Special requests: "extra cheese", "no onions", "gluten-free pasta"'
      }
    },
    required: ['item']
  },
  configuration: {
    http: {
      method: 'POST',
      url: 'https://your-backend.com/api/orders/items',
      headers: { 'Authorization': 'Bearer {{secret:RESTAURANT_API_KEY}}' }
    }
  }
})

Notice two things. First, the description is long and specific on purpose. The LLM uses it to decide when to call the tool and what to pass in. A vague "manages orders" will confuse the model. Second, the {{secret:RESTAURANT_API_KEY}} syntax. Secrets are resolved server-side by Chanl, never exposed to the LLM or the voice platform.

A complete ordering flow needs two more tools: get_order_summary (reads back what's in the cart) and submit_order (finalizes the order for the kitchen). Same structure. For submit_order, add a guardrail in the description: "Only call this AFTER reading the full order back to the customer and getting their confirmation." That's prompt engineering at the tool level.

Where do the HTTP endpoints point? Whatever the restaurant already uses:

  • Square, Toast, or Clover POS: They all have ordering APIs. Map your tool endpoints to their API.
  • Simple webhook to a kitchen display: A Raspberry Pi running a receipt printer. Your endpoint formats the order and sends it.
  • Google Sheet: For a 40-seat restaurant, a Google Sheets API endpoint works fine as a starting point.

Start with whatever exists. You can upgrade the backend later. The agent's tool definitions stay the same.

Build: Remember Every Regular

This is the part Maria's going to love. And the part that makes this agent fundamentally different from a phone tree or a chatbot with a script.

Memory is different from the knowledge base. The knowledge base stores facts about the restaurant (menu, hours, policies). Memory stores facts about individual customers (what they order, their preferences, allergies). Knowledge is shared. Memory is personal.

Here's the key: memory builds itself from conversations. You don't manually create an entry for every customer. The agent extracts facts automatically after each call. Watch how it works across Tony's first few calls.

First call: Tony is a stranger

Tony calls on a Thursday evening. The agent has never heard of him. It takes his order normally: large pepperoni with extra cheese, side of garlic knots, Diet Coke. Pickup at 6:30.

After the call ends, memory.extract() auto-runs on the transcript. It pulls out the important facts and stores them:

text
Auto-extracted after call #1:
  → "Tony Martinez. Orders large pepperoni pizza with extra cheese,
     garlic knots, Diet Coke. Thursday evening customer.
     Prefers pickup around 6:30 PM. Phone: 555-0147."

Nobody wrote that. Nobody clicked "save customer." The agent listened to the conversation and stored what mattered.

Second call: Tony is recognized

Next Thursday, Tony calls again. Before the agent says a word, it loads his memory by caller ID. Now it opens with:

"Hey Tony, the usual? Large pepperoni with extra cheese, garlic knots, and a Diet Coke, pickup around 6:30?"

Tony says "yeah, perfect." Thirty-second call. The agent adds another memory entry noting he ordered the same thing again, reinforcing the pattern.

Over time: memory compounds

A few weeks later, Tony mentions he's ordering for the whole family and his daughter has a peanut allergy. The agent stores that automatically via the memory_add tool during the call. Next time Tony orders for the family, the agent reminds him: "Just a heads up, the Thai chicken pasta uses peanut oil. Want me to swap that for something peanut-free for your daughter?"

That's the lifecycle:

  1. Call starts -- agent auto-loads memory by caller ID
  2. During the call -- agent stores new facts via memory_add as they come up
  3. Call ends -- memory.extract() runs on the full transcript, captures anything missed

The memory compounds over weeks and months. First-time callers become recognized regulars without anyone doing data entry.

Seeding memory for existing regulars

The auto-extraction handles new customers. But Maria already knows her regulars. She can tell you that Tony gets pepperoni every Thursday and Mrs. Chen always orders the family meal deal on Sundays. You can seed that knowledge before the agent takes its first call:

typescript
// Seed existing regulars from what Maria already knows
await chanl.memory.create({
  entityType: 'customer',
  entityId: 'tony-martinez-555-0147',
  content: 'Regular Thursday customer. Usually orders: large pepperoni pizza with extra cheese, side of garlic knots, Diet Coke. Prefers pickup around 6:30 PM.',
})
 
await chanl.memory.create({
  entityType: 'customer',
  entityId: 'chen-family-555-0283',
  content: 'Sunday regular. Orders the family meal deal (large pasta, salad, bread). Two kids. Prefers mild sauce options.',
})

This is a one-time setup for known customers. After that, the agent maintains memory on its own. Every call adds context. Every preference, allergy, and habit gets captured automatically.

The entityId ties memories to a specific person. For phone orders, the caller's phone number is the natural key. You can verify what the agent has learned:

typescript
const { data: tonyMemories } = await chanl.memory.search({
  entityType: 'customer',
  entityId: 'tony-martinez-555-0147',
  query: 'order preferences',
})
 
tonyMemories.memories.forEach(m => {
  console.log(`[${m.score.toFixed(2)}] ${m.content}`)
})
// [0.94] Regular Thursday customer. Usually orders: large pepperoni...
// [0.91] Ordered same items again on 3/20. Confirmed pattern.
// [0.88] Daughter has peanut allergy. Always confirm no peanut oil for family orders.

Notice those last two entries. You didn't write them. The agent did, from real conversations.


How Does a Voice Platform Connect to All of This?

This is where MCP ties everything together. You've built the backend: knowledge base, tools, customer memory. Now a voice platform needs to use all of it during live calls.

Chanl exposes an MCP (Model Context Protocol) endpoint for each agent. When a voice platform like VAPI or Retell connects to that endpoint, it automatically discovers every tool, can search the knowledge base, and can read and write customer memory. No custom integration code. No webhook wiring. One connection.

text
Your Chanl agent MCP endpoint:
https://{workspace-slug}.chanl.dev/mcp

Here's what the voice agent sees when it connects:

MCP ToolWhat It DoesWhen the Agent Calls It
kb_searchSearch the menu knowledge baseCustomer asks about allergens, prices, ingredients
add_to_orderAdd item to the current orderCustomer says "I'd like a large pepperoni"
get_order_summaryRead back the current cartBefore confirming the order
submit_orderSend order to the kitchenAfter customer confirms
memory_searchLook up customer preferencesCall starts, caller ID recognized
memory_addStore a new customer fact mid-callCustomer mentions a preference or allergy
memory_extractAuto-extract facts from transcriptRuns automatically after every call ends

The voice platform handles speech-to-text, text-to-speech, and the phone connection. Chanl handles everything the agent needs to know and do. Clean separation.

For VAPI, the configuration looks like this: you create a VAPI assistant and point its tool source to your Chanl MCP endpoint. VAPI's docs cover the MCP setup. For Retell, it's a similar pattern: point the LLM's tool configuration to the MCP URL. The specific setup depends on which voice platform you choose, but the Chanl side is the same for all of them.

The same MCP endpoint works for web chat too. Embed a chat widget on the restaurant's website, point it at the same agent. Same knowledge, same tools, same memory. Maria's customers can call or chat. Tony calls on Thursday, but the college student down the street orders from their phone at midnight.


Test: Simulate Real Customers Before Going Live

You don't want the first time the agent encounters "can I get a pizza with half pepperoni half mushroom" to be a live call from a paying customer.

Scenario testing lets you simulate realistic conversations before launch. Create test personas that represent Maria's actual customers:

PersonaWhat They Test
The RegularSays "the usual." Agent must recall their order from memory.
The Allergy ParentAsks detailed allergen questions. Agent must answer accurately, never guess.
The Indecisive Caller"What do you recommend?" Agent needs to guide, not recite the full menu.
The Large OrderOffice lunch for 12. Item-by-item ordering with modifications.
The Edge CaseAsks about catering, delivery (they don't do it), or items not on the menu.
typescript
// Run the "regular customer" scenario
const { data: execution } = await chanl.scenarios.run('scenario_thursday_regular', {
  mode: 'text'
})
 
console.log(`Score: ${execution.overallScore}`)
console.log(`Steps passed: ${execution.stepResults.filter(s => s.passed).length}`)

Grade results with scorecards. Did the agent correctly identify the menu item? Did it read back the order before submitting? Did it admit uncertainty on allergen questions instead of guessing? Score each dimension separately: accuracy, tone, order completeness, memory usage. A passing score on accuracy with a failing score on tone tells you exactly what to fix.

Run all five personas. Fix what breaks. Run them again. When every persona passes, you're ready for real customers.

Deploy: Go Live on a Real Phone Number

Deployment is straightforward. You've tested the agent, the knowledge base is loaded, the tools work, memory is wired up.

  1. Phone: Assign a local phone number through your voice provider (VAPI, Retell, Twilio). Set up call forwarding from the restaurant's existing number during rush hours (5:30-8:30 PM), or run the AI 24/7 and forward to staff during slow periods.
  2. Web chat: Embed the chat widget on the restaurant's website. Same agent, same MCP endpoint.
  3. Hours: Configure when the agent is active. During off-hours, it can take messages or inform callers of opening times.

Start with phone only for the first week. Monitor closely. Expand to chat once you're confident.

Monitor: Catch Problems Before Maria Does

After launch, monitoring is what separates a $400/month recurring client from a one-month churn.

Score every conversation automatically. Set up alerts for:

  • Low accuracy scores: Agent gave wrong allergen info or wrong price. Update the knowledge base.
  • Failed tool calls: Order didn't submit to the POS. Check the HTTP endpoint.
  • Memory misses: Agent didn't recall a regular. Check that entityId matches the caller's phone number.
  • Unhandled questions: Caller asked something the agent couldn't answer. Add it to the knowledge base.

Fix problems before the restaurant owner even knows they exist. That's how you keep a $400/month client for years instead of months.


What Does the Business Math Look Like?

Let's talk money, because the math is what makes this a real side hustle.

Your costs per restaurant client:

ItemMonthly Cost
LLM API (GPT-4o-mini, ~500 calls)$8-15
Voice provider (phone minutes)$15-30
Platform hosting$10-15
Total$33-60

What you charge:

ItemAmount
Setup fee (one-time)$500
Monthly service$400

Client ROI for the restaurant:

Maria loses 15-20 phone orders per week. Say the agent captures just 10 of those. At an average order of $25, that's $250 per week, or $1,000 per month in recovered revenue. She pays you $400. She nets $600. Easy yes.

Your math at scale:

ClientsMonthly RevenueMonthly CostsNet Profit
1$400$50$350
5$2,000$250$1,750
10$4,000$500$3,500

Ten restaurant clients is $3,500 per month in mostly-passive income. "Mostly" because you'll spend 2-3 hours per month per client on menu updates, monitoring, and prompt tuning. That's 20-30 hours a month for $3,500.

How Do You Find Your First Client?

Don't cold-email restaurant owners. Call them. During dinner rush. If you wait on hold for 4 minutes, you've found your prospect.

Here's the pitch, delivered in person over a meal at the restaurant (yes, go eat there first):

"I noticed when I called Thursday to place an order, the phone rang for a while. I build AI phone agents for restaurants. It answers during rush hour, takes orders, and knows your full menu. One of my other clients was losing about 15 phone orders a week. The agent catches most of those now. It costs $400 a month. Want to try it for two weeks free?"

The free trial is important. Restaurant owners are skeptical of technology pitches. They've been burned by online ordering apps that promised everything and delivered fees. Two weeks of "watch your phone orders go up" is more convincing than any slide deck.

During the trial, track everything. How many calls the agent handled. How many orders it took. What questions it couldn't answer (so you can update the knowledge base). At the end of two weeks, show Maria the numbers: "Your agent took 38 orders this week that would have gone to voicemail. At your average ticket, that's $950 your kitchen would have missed."

That's not a pitch. That's a receipt.

What You Built

Let's trace the whole thing from Tony's Thursday call:

  1. Phone rings. VAPI answers, connects to Chanl's MCP endpoint.
  2. Memory loads. Agent calls memory_search by caller ID. Finds Tony's preferences (auto-extracted from previous calls): large pepperoni, extra cheese, garlic knots, Diet Coke. Thursday regular.
  3. "The usual?" Tony confirms. Agent calls add_to_order four times via MCP.
  4. Order summary. Agent calls get_order_summary. Reads back: "Large pepperoni with extra cheese, garlic knots, and a Diet Coke. $34.50. Pickup at 6:30?"
  5. Submit. Tony says yes. Agent calls submit_order. Kitchen gets the order.
  6. Memory grows. After the call, memory.extract() runs on the transcript. Confirms the pattern, notes anything new Tony mentioned.

Tony's experience: a 30-second phone call where the restaurant knew who he was and what he wanted. Maria's experience: an order she would have missed, automatically sent to the kitchen.

You built that. And tomorrow, you can build it again for the Thai place down the street, the HVAC company that misses after-hours calls, the dental clinic that loses patients to voicemail, the auto repair shop drowning in service calls, or the real estate agent who can't qualify leads fast enough. Same architecture. Different knowledge base. Different tools. Same recurring revenue.

Build Your First Restaurant Agent This Weekend

Upload a menu, wire up ordering tools, add customer memory. Your voice platform connects via MCP. One agent, every channel.

Start Building
DG

Co-founder

Building the platform for AI agents at Chanl — tools, testing, and observability for customer experience.

Learn Agentic AI

One lesson a week — practical techniques for building, testing, and shipping AI agents. From prompt engineering to production monitoring. Learn by doing.

500+ engineers subscribed

Frequently Asked Questions