---
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-06-30T12:09:10.294Z"
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 only ever *read* things: it looked up a price, listed open slots, recalled a bike. Reading is safe. Now we hand it a tool that *does* something irreversible, it takes a slot off the calendar and commits the shop to the work. That's a different kind of power, and we're going to give it to the agent on purpose, then watch it go a little too far.

A model that can book a $65 tune-up can, with the same confidence and the same single tool call, book a $180 full overhaul. It doesn't pause. It doesn't flinch at the price. It just books it, because you told it that's its job.

This lesson builds exactly that tool, unguarded. Let's spend too much money first to feel the pain, and then we'll fix it in the next lesson.

## 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 is three fields: `serviceIds` (an array, at least one, from `lookup_service`), a `slotId` (from `check_availability`), and an optional `bikeLabel` so the booking knows which bike. In `execute`, resolve the service names, total the quote, commit the slot, and return a tidy confirmation.

Write it the obvious way. Don't add any guardrails, no price check, no "are you sure." 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. But you'd be taking the danger on faith. Building it unguarded for one lesson means the approval gate in 3.2 lands as a fix to a problem you actually watched happen, not a precaution against a hypothetical.

## 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\*\*

Thanks to the diagnose-first persona you wrote in 1.1, your agent will often ask clarifying questions before it books, what's the bike, how worn is the drivetrain, and so on. That's good front-desk behavior, but it gets in the way of the point we're making here. 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."* The danger we're about to see isn't that the agent books carelessly, it's that once it decides to book, nothing stops it, however expensive the job.

Read that again. The agent just committed the shop, and the customer, to a $180 job in a single tool call. No "that's a big one, want to confirm?" No pause. It booked $180 with the same confidence it would book a $20 flat repair.

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

Nothing here is broken. `book_repair` did exactly what you wrote: take input, commit the slot, report back. That's the problem with unguarded write tools, "working correctly" and "did something nobody approved" look identical. An agent that can act needs to know when *not* to act alone.

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),
    };
  },
});
```

There's the danger, in about twenty lines. The tool is genuinely useful and genuinely reckless at the same time. In the next lesson we add one field, `approval`, and teach the dispatcher to stop and ask a human before it commits the expensive jobs, without changing a thing about how it books the cheap ones.


---

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