# AI Agent Tool Design: Boundaries and Confirmations

URL: https://www.devopsness.com/blog/ai-agent-tool-design-boundaries-confirmations
Author: Kiril Urbonas
Published: 2026-05-02
Updated: 2026-10-31
Categories: AI
Tags: AI, Agents, LLM, Tool Use, Security

Summary: When LLMs can call tools that change real state, the design decisions that matter most are about what's gated, what's automatic, and what triggers a human checkpoint.

---
# AI Agent Tool Design: Boundaries and Confirmations

Make agent tools safe by splitting reads from writes: expose read tools freely, gate every write behind a dry-run default or a propose-then-confirm step, and route money or destructive actions to a human. Validate arguments server-side and enforce authorization outside the model. The LLM is the least important part of the safety story.

> [!VERIFIED]
> Model Context Protocol specification 2026-07-28 (tools), Anthropic tool use docs, against the MCP spec, platform.claude.com "Define tools" and OWASP LLM06:2025 Excessive Agency. Checked October 2026.

This is what we landed on after a year of shipping agents that read and write real state, a few near-misses, and one real incident.

## The shape of the problem

An LLM agent gets a task ("update the customer's mailing address"), decides which tool to call, and the tool changes something. The model isn't deterministic, the task isn't always well-specified, and the input might be adversarial. Two failure modes we've seen:

- **The wrong tool gets called.** The model misreads intent and calls `delete_customer` instead of `archive_customer`.
- **The right tool gets called with wrong arguments.** The model fills in a parameter from context that doesn't match what the user meant.

Both happen in a small percentage of runs, and at scale a small percentage is real customer impact. The design problem is making these failures cheap enough to live with. OWASP files this under LLM06:2025 Excessive Agency, with three root causes: too much functionality, too many permissions, too much autonomy.

## Split read tools from write tools

The single most important design decision: **separate read tools from write tools and gate them differently**.

Read tools (`get_customer_profile`, `list_recent_orders`) we expose freely; the worst case is wasted tokens.

Write tools (`update_address`, `cancel_subscription`, `issue_refund`) are gated: different prompt, different invocation flow, different audit trail, sometimes a different process entirely.

With reads and writes in the same prompt, models readily call writes for tasks that only need a read. Splitting them makes writes deliberate instead of incidental.

## Patterns for gating writes

Three patterns earn their place:

**Two-step: propose then confirm.** The agent runs the write tool in "propose" mode, which returns a description of what would change. A human, or a second agent with a separate prompt, confirms before the write executes. We use this for most consequential writes. It adds latency and safety in equal measure.

**Dry-run by default.** Some tools default to dry-run, and the agent has to pass `confirm=true` to execute. The safe path is the default.

**Read-only mirror in test mode.** For some internal tools we run the agent against a nightly read-only copy of production data, and only switch to live writes after we've validated its behavior.

The right pattern depends on reversibility. Adding a tag to a record: just do it. Issuing a $500 refund: propose, confirm, then execute.

## Who confirms what

Once writes need confirmation, the question is who confirms.

**Agent, then user, then agent.** The agent proposes to the user in the UI ("I'm about to cancel your subscription, confirm?"), the user clicks, the agent executes. Good for user-driven flows, useless for backend agents.

**Agent, then a second agent, then execution.** The reviewer agent has a prompt focused on policy. It is cheaper than human review at scale and fine for "does this look like a normal operation?" checks, but not enough for high-stakes operations.

**Agent, then an on-call human, then execution.** For anything that touches money or destroys data.

We layer these: the agent proposes, a safety-review agent checks against policy, green goes ahead, anything questionable pages on-call. The MCP specification (revision 2026-07-28) points the same way: it says there "SHOULD always be a human in the loop with the ability to deny tool invocations," and that clients should prompt for confirmation on sensitive operations.

## Tool schemas matter a lot

Vague tool descriptions give vague behavior. A bad schema:

```json
{
  "name": "update_user",
  "description": "Updates the user record.",
  "parameters": {"user_id": "string", "data": "object"}
}
```

A good one, in the Anthropic `input_schema` shape:

```json
{
  "name": "update_user_address",
  "description": "Updates only the mailing address on a user record. Use this for moving-address requests. Do NOT use this to update email, phone, or name; those have separate tools. Returns the updated address.",
  "input_schema": {
    "type": "object",
    "properties": {
      "user_id": {"type": "string", "description": "The internal user ID (not their email)"},
      "address": {
        "type": "object",
        "properties": {
          "street": {"type": "string"},
          "city": {"type": "string"},
          "postal_code": {"type": "string"},
          "country": {"type": "string", "description": "ISO 3166-1 alpha-2 country code"}
        },
        "required": ["street", "city", "country"]
      }
    },
    "required": ["user_id", "address"]
  }
}
```

Anthropic's docs call detailed descriptions "by far the most important factor in tool performance" and suggest at least 3-4 sentences per tool. They also recommend consolidating related operations into fewer tools. We follow that for reads, but we deliberately keep writes narrow: "update the user" became separate address, email, phone and name tools, because each write needs its own gate, validation and audit line, and a narrow write is easier to confirm.

## Validate every argument server-side

Never trust the schema to constrain the model's output. We've seen country codes that aren't ISO codes, user IDs that look right but don't exist, negative or absurd currency amounts, and dates slightly off-format. Even with strict tool use guaranteeing schema-valid inputs, a schema can't know whether a user ID exists or a refund is allowed.

The validator rejects the call with a structured error, and the agent usually recovers: it retries with a fix or asks the user. MCP formalizes this as tool execution errors (`isError: true`) that the model can self-correct from. But the safety boundary is the server-side check, not the model's compliance. OWASP calls this complete mediation: implement authorization in downstream systems "rather than relying on an LLM to decide if an action is allowed or not."

## Log every call

Every tool invocation logs the task and agent run ID, tool name, full arguments (with sensitive fields redacted), result or error, latency and timestamp. We've reconstructed incidents weeks later from that log. Ours lives in S3 with one-year retention.

For the most consequential operations we also keep the model's thinking output, where the provider returns it. Reading what the model believed about the user's intent is sometimes the only way to understand why it picked the wrong tool.

## The incident that taught us this

Anonymized: the agent had a `complete_task` tool. It correctly decided a task was done, then called the tool with the wrong task ID, one belonging to a different customer. The wrong task got marked complete.

What we changed:

- `complete_task` now requires both `task_id` and a sanity-check field (the customer's email). A mismatch is rejected.
- The logged trace shows which task the agent thought it was working on.
- A nightly audit flags completed tasks that don't match expected patterns.

None of this is clever. Boring engineering is what makes agents safe to ship.

## What we don't do

**Letting agents create new tools.** Some frameworks let agents write code and execute it. We don't: the blast radius is too large to audit. If you must, isolate it the way [sandboxed environments for AI agents](/blog/docker-cloud-sandboxes-ai-agent-isolation) do.

**Agents calling agents recursively.** Unbounded recursion means unbounded cost and blast radius. We allow one level of agent-to-agent calls.

**Tools that take freeform SQL or code.** The model loves writing creative SQL. We expose query builders with constrained parameters instead, which is also OWASP's advice to avoid open-ended extensions.

**Skipping confirmation "because the user is impatient."** If confirmations annoy users, fix the UX, not the safety layer.

## Questions people ask

### Should AI agents ask for confirmation before every tool call?

No, only before writes that are hard to reverse. Confirming every read trains users to click through prompts without reading them. Expose read tools freely, default write tools to dry-run, and require explicit confirmation for anything that moves money, deletes data or contacts a customer. The MCP specification recommends a human able to deny tool invocations and confirmation prompts for sensitive operations.

### Is a JSON schema enough to keep an agent's tool calls safe?

No. A schema, even with strict tool use, only guarantees the arguments have the right shape. It cannot know whether a user ID exists, whether the caller may refund that order, or whether an amount is sane. Validate every argument in the tool implementation, check authorization in the downstream system, and return structured errors the model can recover from.

### How do you stop an AI agent from deleting production data?

Don't give it a delete tool, or give it one that only proposes. Scope the agent's credentials to the minimum the task needs, require human approval for destructive operations, and keep an audit log of every call. Most agent incidents come from excessive permissions rather than clever attacks, which is why [AI agent security](/blog/ai-agent-security) starts with credentials, not prompts.

## The call we'd make

Read tools wide open, write tools heavily gated, many narrow writes rather than one broad one, server-side validation always, every call logged, and a two-step propose-and-confirm on anything consequential. Then tabletop the failure modes: walk through what happens if the agent calls each tool wrong, and the exercise tells you which tools need more gating. Agent tool design is mostly software engineering, and the teams that ship agents responsibly treat tool use as a security boundary, not a convenience.

## Sources

- [Model Context Protocol specification (2026-07-28): Tools](https://modelcontextprotocol.io/specification/2026-07-28/server/tools)
- [Anthropic Docs: Define tools](https://platform.claude.com/docs/en/agents-and-tools/tool-use/define-tools)
- [OWASP GenAI Security Project: LLM06:2025 Excessive Agency](https://genai.owasp.org/llmrisk/llm062025-excessive-agency/)

