Skip to content
Carlo
All work
In production

Hanami Hair Studio

One digital system for a hair salon: a site that answers the static questions and sends every call to action into an Instagram DM, where a multi-agent assistant books, reschedules and cancels against the salon's real calendar.

Client
Hanami Hair Studio
Year
2025–2026
Role
UI/UX design, frontend, local SEO, bot architecture & implementation
  • n8n
  • Multi-agent
  • Gemini · RAG
  • Instagram

Context

Hanami is a hair salon in the historic center of Zacatecas with a clear philosophy: natural, inclusive styling. Clients discover it on Instagram and arrive on their phones — first to look, then to ask by DM.

The problem

Most conversations started with the same questions — prices, how long a session takes, what's needed before a color service, the deposit — before reaching the one that matters: is there a slot?

A generic chatbot wasn't enough. Answers had to match the salon's real prices and policies, and availability had to match the owner's real calendar. In booking, a wrong answer is worse than no answer: a client who shows up for a slot that doesn't exist is a client you lose.

Architecture

The system has two surfaces with a clear split. The site answers the static questions (prices, policies, what to expect). The bot handles the dynamic ones (availability and booking). They meet in one place: every "book" button on the site opens an Instagram DM with the bot.

HanamiBot

Rendering diagram…
HanamiBot architecture
  • Channel. Instagram DMs reach n8n through ManyChat, and replies go back the same way.
  • Pre-processing. Voice notes are transcribed and images described with Gemini, and a 20-second buffer merges messages that arrive in a burst, so the agent answers the whole thought instead of every fragment.
  • Classifier and router. A Gemini classifier labels each conversation's intent and a router hands it to one specialist agent.
  • Booking agent. Books, reschedules and cancels in the owner's Google Calendar, and answers questions about services, prices and policies from a knowledge base in Postgres with pgvector.
  • Color agent. Color services need a consultation, so this agent collects a five-point checklist about the client's hair, saves it and hands the conversation to the owner. It never quotes a final price.
  • Free-slot calculation. The only source of availability. See the hard part for why that's code, not a prompt.
  • Voice. A last step rewrites each reply in the salon's tone before it's sent.

The website

hanamihairstudio.com is a React and TypeScript single page built with Vite and Tailwind, prerendered to static HTML at build time, so the content is in the page before any JavaScript runs.

  • Booking information up front. Prices, session length, opening hours, payment methods and the deposit policy for color services are on the page before anyone gets in touch. The goal: clients reach the DM already qualified, without repeating questions.
  • Every call to action opens the bot. "Book" buttons are Instagram DM deep links, so the site hands off to the bot instead of to a contact form.
  • Before/after gallery. A scroll-snap carousel on mobile and a grid on desktop, with its animation skipped for visitors who prefer reduced motion.
  • Local SEO. Schema.org HairSalon structured data with the address, opening hours, price range and a catalog of services.

QA and monitoring

Agents fail quietly — a confident wrong answer doesn't throw an error — so the bot has workflows around it whose only job is to watch it.

  • Error handler (running). Every failed execution in the Hanami workflows is saved to an error log and raises an alert on Telegram.
  • Per-turn telemetry. Each turn records the intent, latency, the tools used and whether it fell back — including a flag for mentioned a time without checking the calendar. In the newest version it's written before the DM is sent, so a failed send can't erase the record.
  • Nightly evaluator and daily digest (built for the v3 cut-over, currently in staging). At 2 a.m. an LLM judge scores the day's conversations on tone, resolution, price hallucination, handoff and close; at 9 a.m. a digest with technical health, a per-agent breakdown and business numbers goes to Telegram.

Key decisions & trade-offs

Decision

One system, two surfaces

Static questions are answered on the site, where they're free and instant. The bot only spends conversation turns on what a page can't answer: availability and booking.

Trade-off · Salon information lives in two places (the site and the knowledge base) and has to be kept in sync.

Decision

Availability is computed by code, never by the model

The model is good at conversation and bad at date arithmetic it can't see. Free slots are calculated deterministically and handed to the model as a closed list; the model only phrases the answer.

Trade-off · Every change to the salon's hours is a code change, and the bot can't offer a slot when the calendar is unreachable.

Decision

A router plus specialist agents instead of one large agent

Booking and color consultations need different rules — one closes a sale, the other must not quote a price. Separate agents with short prompts are easier to test and change without breaking each other.

Trade-off · More workflows to maintain, and the classifier becomes a critical piece.

Decision

Prerender the site to static HTML

Visitors arrive from Instagram on their phones, and search engines need to read prices and hours. Prerendering puts all the content in the HTML while keeping the site's React components.

Trade-off · A build step that renders every page, and the page still downloads React to become interactive.

Decision

Monitoring is part of the system, not an add-on

Error alerts and per-turn telemetry are separate workflows with their own tables. It's the difference between a demo and a system a business can depend on.

Trade-off · More workflows to build and keep running around a single bot.

The hard part

The agent offered appointment times that didn't exist.

How it was detected. The salon's owner reported it: the bot got the month wrong and offered slots that weren't actually free, then had to take them back.

Root cause. The earlier version handed the model raw calendar events and asked it to work out the free slots itself — subtracting busy events from opening hours, handling the time zone, and even keeping to a month that was hard-coded in the prompt. That's arithmetic, and a language model does arithmetic by guessing what looks right.

The fix. The model no longer computes anything about time:

  1. A code step calculates the free 60-minute slots in the America/Mexico_City time zone: opening hours minus busy events, with a 24-hour minimum notice, over the next 14 days.
  2. The list goes into the prompt with exact ISO timestamps and today's date, under one rule: you may only offer times that appear in this list.
  3. To book, the agent copies the ISO value of the chosen slot into the booking tool — it never types a date on its own.

The calculation later became a reusable sub-workflow — the single source of truth for availability — and telemetry now flags any turn that mentions a time without having checked the calendar.

The lesson generalizes to any booking agent: the calendar is the source of truth, and the LLM is only the interface to it.

Results

Handling the salon's Instagram DMs since
Dec 2025
Book, reschedule and cancel, directly in the owner's calendar.
Major versions in production
3
The third made availability deterministic and added parameterized SQL and payload validation.
Website · Lighthouse SEO (mobile)
100 / 100
Lighthouse 13, 3 runs, September 2026.
Website · Lighthouse Best Practices (mobile)
100 / 100
Same runs.

Stack

  • n8n
  • Gemini (agents, classifier, embeddings)
  • ManyChat · Instagram
  • Google Calendar
  • PostgreSQL + pgvector
  • Airtable
  • Telegram (alerts)
  • React · TypeScript · Vite
  • Tailwind CSS
  • Schema.org