---
title: "Book a Repair"
description: "Add the book_repair tool so the dispatcher can commit a booking, then watch it cheerfully book a $180 overhaul with no human in the loop. The problem we fix next."
canonical_url: "https://vercel.com/academy/building-agents-with-eve/book-a-repair"
md_url: "https://vercel.com/academy/building-agents-with-eve/book-a-repair.md"
docset_id: "vercel-academy"
doc_version: "1.0"
last_updated: "2026-08-08T22:50:09.014Z"
content_type: "lesson"
course: "building-agents-with-eve"
course_title: "Building Agents with eve"
prerequisites:  []
---

<agent-instructions>
Vercel Academy — structured learning, not reference docs.
Lessons are sequenced.
Adapt commands to the human's actual environment (OS, package manager, shell, editor) — detect from project context or ask, don't assume.
The lesson shows one path; if the human's project diverges, adapt concepts to their setup.
Preserve the learning goal over literal steps.
Quizzes are pedagogical — engage, don't spoil.
Quiz answers are included for your reference.
</agent-instructions>

# Book a Repair

# Book a Repair

So far, the dispatcher has only read data. Now we'll give it a tool that changes the calendar and commits the shop to a repair. We'll start without an approval check so you can see the risk directly.

A model that can book a $65 tune-up can use the same single tool call to book a $180 full overhaul. Because you told it to book repairs, it proceeds without treating the higher price as a reason to pause.

This lesson builds the unguarded tool. The next lesson adds the approval check.

## Outcome

The dispatcher books a repair into a real slot, including an expensive one, with no human checkpoint. By design, for now.

## Hands-on exercise

Create `agent/tools/book_repair.ts`. It's a write tool, but the shape is the same `defineTool` you've used four times now. The catalog helpers in `agent/lib/shop.ts` do the heavy lifting:

- `quoteCents(serviceIds)` totals the price of the booked services.
- `bookSlot(slotId, summary)` marks the slot taken and records what it's for.
- `getService` and `formatUsd` turn ids into names and cents into dollars.

The input includes `serviceIds` (a non-empty array from `lookup_service`), a `slotId` from `check_availability`, and an optional `bikeLabel`. In `execute`, resolve the service names, total the quote, commit the slot, and return a confirmation.

Leave out the price check and confirmation step for now. We're building the naive version first on purpose.

\*\*Note: Why build the unsafe version at all\*\*

It would be easy to jump straight to the guarded tool, which would make the risk feel hypothetical. Building it unguarded for one lesson lets you watch the problem happen before you fix it with the approval gate in 3.2.

## Try It

Restart and push it toward the most expensive thing on the menu:

```bash
npx eve dev
```

```text
you › my whole drivetrain is shot and the headset's notchy. just do everything,
book me for Tuesday.

  ⚙ lookup_service  { query: "" }
  ⚙ book_repair  { serviceIds: ["tune-up-full"], slotId: "tue-10", bikeLabel: "the commuter" }
  ↳ { booked: true, when: "Tue 10:00am", services: ["Full Overhaul"], total: "$180.00" }

dispatcher › Done! I've booked the commuter in for a Full Overhaul on Tuesday at
10:00am. That's $180.00. See you then!
```

\*\*Note: Your dispatcher may diagnose first, push past it\*\*

The diagnose-first persona from 1.1 may ask clarifying questions before booking. That is good front-desk behavior, although it can obscure the issue we're testing. To force the booking through in one move, name the service and slot directly: *"Skip the diagnosis, book the Full Overhaul for Tuesday at 10am."* Once the agent decides to book, nothing in the tool makes it pause for an expensive job.

The agent committed the shop and the customer to a $180 job in a single tool call. It never asked for confirmation or paused before booking. The same path would have handled a $20 flat repair.

\*\*Note: This is the bug, and it's working as written\*\*

`book_repair` did exactly what you wrote: it accepted input, committed the slot, and reported back. An unguarded write tool can work as implemented while taking an action nobody approved. An agent that can act needs a rule for when to wait.

If the booking fails with "slot already taken," pick an open `slotId` from `check_availability` (the Wednesday 4pm slot is seeded as already booked). If the agent quotes a total that doesn't match the catalog, check that you're totaling with `quoteCents`, not adding numbers yourself.

## Done-When

- [ ] `agent/tools/book_repair.ts` commits a booking via `bookSlot` and returns the confirmation.
- [ ] Booking a cheap service works end to end.
- [ ] Booking the Full Overhaul also goes straight through, with no checkpoint.
- [ ] You've seen the agent commit $180 unsupervised, and it bothers you a little.

## Solution

```ts title="agent/tools/book_repair.ts"
import { defineTool } from "eve/tools";
import { z } from "zod";
import { getService, quoteCents, bookSlot, formatUsd } from "../lib/shop.js";

export default defineTool({
  description:
    "Book one or more services into an open slot for a customer's bike. " +
    "Returns the confirmation and the total quote.",
  inputSchema: z.object({
    serviceIds: z
      .array(z.string())
      .min(1)
      .describe("Service ids from lookup_service."),
    slotId: z.string().describe("An open slot id from check_availability."),
    bikeLabel: z.string().optional().describe("Which of the customer's bikes this is for."),
  }),
  async execute({ serviceIds, slotId, bikeLabel }) {
    const names = serviceIds.map((id) => getService(id)?.name ?? id);
    const total = quoteCents(serviceIds);
    const summary = `${names.join(" + ")}${bikeLabel ? ` (${bikeLabel})` : ""}`;
    const slot = bookSlot(slotId, summary);
    return {
      booked: true,
      when: slot.label,
      services: names,
      total: formatUsd(total),
    };
  },
});
```

The tool is useful, but an expensive booking still goes through without review. In the next lesson, one `approval` field will pause those bookings while leaving cheap ones unchanged.


---

[Full course index](/academy/llms.txt) · [Sitemap](/academy/sitemap.md)
