Your Agent Has One manage_calendar Tool. That's Why It Deleted the Wrong Meeting.

A single do-everything tool looks tidy in the schema and hides four separate problems: guessed actions, unvalidated payloads, permissions that can't tell reading from deleting, and traces that flatten three risk levels into one name. Here's where to cut instead — and where splitting makes things worse.

Your Agent Has One manage_calendar Tool. That's Why It Deleted the Wrong Meeting.

A personal-productivity agent with one tool called manage_calendar looks elegant. One schema, one integration, one thing to document. Then someone types "clear my Thursday afternoon," and a recurring 1:1 that had been on the books for two years disappears.

The model did not malfunction. The tool schema gave it no way to be careful.

🔍 The tool that does everything

The fat tool almost always looks like this:

{
  "name": "manage_calendar",
  "description": "Create, move, delete or look up calendar events.",
  "parameters": {
    "action": { "type": "string" },
    "payload": { "type": "object" }
  }
}

Two fields, four capabilities. That compression costs you four separate things:

  • The action is a guess. "delete", "remove", "cancel" — the model picks a string and hopes your handler agrees. A typed enum would have made the valid set explicit; a free-form string makes it a vocabulary quiz.
  • The payload is unvalidated. "type": "object" tells the model nothing about which fields a delete needs versus a create. It will happily send {"day": "Thursday", "time": "afternoon"} to a code path expecting an event id.
  • Permissions collapse. You cannot allow reads and gate deletes, because there is only one tool to allow or deny. Every approval prompt is all-or-nothing, so it gets approved once and then always.
  • Traces lie. Your logs show forty calls to manage_calendar. Thirty-eight were lookups. You cannot tell at a glance which two were destructive.

The same request, with the tool split

Split it along what the calls actually do:

find_events(query, time_range)      -> read
create_event(title, start, end)     -> write
move_event(event_id, start, end)    -> write
delete_event(event_id)              -> destructive

Now replay "clear my Thursday afternoon."

delete_event requires an event_id. The model does not have one. It cannot fabricate its way past a required, typed field the way it can stuff a vague object into payload, so it calls find_events first. That returns three events — a 1:1, a dentist appointment, a focus block.

The whole failure has been converted into something visible. Instead of one opaque destructive call, you have a lookup whose results a human can see, followed by three explicit deletes. And because delete_event is now its own tool, the confirmation gate can sit on exactly that one name, while lookups stay frictionless.

The fat tool didn't just make the mistake more likely. It made the mistake unreviewable.

⚠️ But don't split everything

The opposite failure is real and easier to fall into than people expect. Forty micro-tools produce their own problems: the model spends attention selecting rather than doing, every schema eats context on every turn, and near-duplicate names (get_event, fetch_event, lookup_event) become a coin flip.

Granularity is a cut, not a slider you push to maximum. Cut where a cut buys you something:

Cut on permission boundary. If reading and deleting would ever get different answers from a human, they are different tools. This is the single highest-value cut.

Cut on argument shape. If half the parameters only make sense for one branch, that branch is a separate tool. A schema where fields are conditionally required is a schema the model will fill in wrong.

Cut on reversibility. Undoable and un-undoable operations belong apart even when their arguments look identical, because your monitoring needs to count them separately.

Don't cut on your own service boundaries. If your backend has three microservices behind one user-visible action, that is your architecture, not the agent's problem. Expose the action.

Merge what's always sequential. If find_events is followed by delete_event in a fixed pattern every single time, and no human ever inspects the middle, that pair is a candidate for one cancel_matching_events(query) tool — with the match list returned in the result so the step stays auditable. Merging is legitimate; merging silently is not.

A checklist before you ship a tool

  • ✅ Every parameter is typed, and the enums are real enums, not strings you hope for
  • ✅ Any destructive operation takes an id it must have obtained from a prior read
  • ✅ Each tool name appears alone in a trace and tells you the risk level
  • ✅ You can allow one tool and deny another without editing code
  • ❌ A parameter named action, mode, operation, or command
  • ❌ A parameter of type object with no described properties
  • ❌ Two tools whose descriptions you'd struggle to tell apart in one sentence

The action-parameter smell is the one to internalize. When a tool takes a verb as an argument, you have moved routing from your schema — where it is typed, gated, and logged — into the model's free-text output, where it is none of those things.

The seam is where meaning lives

Tool boundaries are not plumbing. They are the vocabulary the model reasons in. An agent holding find_events and delete_event is thinking about looking and removing as separate acts. An agent holding manage_calendar is thinking about calendar stuff, and will act accordingly.

Design the seams for the moment something goes wrong: which call do you want to see in the trace, and which one do you want a human standing in front of? Answer that first, and the granularity falls out on its own.

Keep reading

Similar posts

Matched on shared tags and category — the more bars, the stronger the overlap with what you just read.