Skip to content

Analysis: Reservation Layer Gaps for AI-Agent Integration #37

@gustvcz

Description

@gustvcz

While evaluating open-source restaurant platforms for AI-agent (voice bot / WhatsApp assistant / chatbot) use cases, I ran KitchenAsty through a maturity analysis against production-grade systems like SevenRooms, Zenchef, and TheFork.

KitchenAsty is already in a strong position — the DevOps posture, the TypeScript monorepo, the existing reservations layer, and the Automation Rules engine are all well-suited to this use case. The gaps I found are targeted and additive: none require a rewrite, and none break existing consumers.

I am sharing the full findings here in case the team or any contributor finds them useful.


Summary of Findings

The table below rates the current state of the reservation system against the requirements of a multi-channel AI-agent deployment.

Dimension Current Level Key Gap
API Coverage (reservations) 🟡 Partial No cancel-with-reason endpoint, no no-show endpoint, no server-side state machine
Agent-Readiness 🔴 Critical gap No hold/blockage mechanism — race conditions guaranteed under concurrent load
Idempotency 🔴 Critical gap No Idempotency-Key support — agent retries create silent duplicate reservations
Webhooks 🟡 Partial Only reservation.created covered — no modified, cancelled, no-show, reminders
Auth (external channels) 🟡 Partial JWT only — no per-channel API Keys with scopes for autonomous agents
CRM 🟡 Partial No staff notes, VIP tags, or GDPR erasure
Payments / Deposits 🟡 Partial No reservation deposits or no-show penalties
Ops / DevOps 🟢 Mature Docker Compose, stateless design, Prisma migrations ✅

The Two Most Critical Issues

If the community were to prioritise anything from this analysis, these two items have the highest production risk:

1. Missing Hold/Blockage — Race Condition Under Concurrent Load

The problem: Between GET /availability and POST /reservations, there is no atomic slot reservation. If two agents (or two browser tabs) query simultaneously, both see the same slot as available, and both successfully create overlapping reservations. There is no error — just silent overbooking.

The pattern to solve it: A two-step commit with a TTL-bounded hold:

GET  /api/reservations/availability   →  see available slots
POST /api/reservations/hold           →  atomically lock a slot (TTL: 5 min)
POST /api/reservations/confirm        →  convert hold → confirmed reservation

Requires a new ReservationHold Prisma model and a cron job to expire stale holds. Availability queries must subtract active holds from visible capacity.

model ReservationHold {
  id             String   @id @default(cuid())
  locationId     String
  date           String
  time           String
  partySize      Int
  channel        String
  idempotencyKey String   @unique
  expiresAt      DateTime
  createdAt      DateTime @default(now())

  location       Location @relation(fields: [locationId], references: [id])
}

2. Missing Idempotency Keys — Silent Duplicate Reservations on Retry

The problem: AI agents operate over unreliable networks. If a POST /reservations request times out before the agent receives a response, the agent will retry. Without idempotency, the retry creates a duplicate reservation — the customer receives two confirmation messages for the same booking.

The pattern to solve it: Support Idempotency-Key: <uuid-v4> on all write endpoints. Cache {status, responseBody} in Redis with a 24h TTL. On a repeated request with the same key, return the cached response without re-executing.

// Middleware sketch — packages/api/src/middleware/idempotency.ts
const idempotencyMiddleware = async (req, res, next) => {
  const key = req.headers['idempotency-key'];
  if (!key) return next();

  const cached = await redis.get(`idempotency:${key}`);
  if (cached) {
    const { status, body } = JSON.parse(cached);
    return res.status(status).json(body);
  }

  const originalJson = res.json.bind(res);
  res.json = (body) => {
    redis.setex(`idempotency:${key}`, 86400, JSON.stringify({ status: res.statusCode, body }));
    return originalJson(body);
  };

  next();
};

This is also the simplest item to ship — no schema change, no new endpoints, and it immediately protects the existing POST /reservations as well.


Full Analysis (Additional Items)

Beyond the two critical gaps above, the full analysis also covers:

  • RFC-03: Complete reservation lifecycle endpoints — DELETE /:id with reason codes, POST /:id/no-show, and a server-side state machine that prevents invalid transitions (e.g. COMPLETED → CONFIRMED)
  • RFC-04: Extending Automation Rules to cover all reservation lifecycle events (reservation.modified, reservation.cancelled, reservation.no_show, reservation.reminder_24h, reservation.reminder_2h) with HMAC-SHA256 signing and retry logic on failed deliveries
  • RFC-05: A per-channel API Key system with scopes (e.g. availability:read, reservations:write) so that a WhatsApp agent, a voice agent, and a web widget each have independent, auditable, revocable credentials

For each of these, the full technical specification — Prisma models, API contracts, error codes, and acceptance criteria — was documented during the analysis. I am happy to paste any of it here on request.

Thanks for building KitchenAsty. 🙏

Metadata

Metadata

Assignees

Labels

enhancementNew feature or request

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions