All articles

AI engineering

Building AI Agents That Use Tools Reliably

A production-minded guide to tool contracts, permissions, validation, retries, and observability for AI agents that act on external systems.

9 min read
Building AI Agents That Use Tools Reliably

Connecting an AI model to a tool is easy. Giving an agent permission to search a database, update a ticket, send an email, or change a production system safely is a much larger engineering problem.

The model is only one part of the system. Reliable agents need clear tool contracts, controlled permissions, deterministic validation, recovery behavior, and enough observability to explain what happened after a failure.

The goal is not to make the model appear autonomous. The goal is to make every action understandable and bounded.

Begin with a narrow job

“Build an agent that manages customer support” is not a useful starting scope. It combines classification, retrieval, policy decisions, communication, and external side effects.

Start with a workflow whose success can be measured. For example:

  • Read a support conversation and retrieve the relevant account information.
  • Suggest the next action and cite the evidence behind it.
  • Draft a response but require a person to approve sending it.

This separates reasoning from execution and makes failures easier to locate. Once the read-only path is reliable, add carefully selected write actions.

Treat tools as public APIs

A tool name and description are part of the agent's interface. They should make the correct action obvious and make incorrect use difficult.

Prefer a small tool with a strict schema:

{
  "name": "create_support_note",
  "description": "Add an internal note to an existing support ticket. This does not contact the customer.",
  "parameters": {
    "type": "object",
    "properties": {
      "ticket_id": { "type": "string" },
      "note": { "type": "string", "maxLength": 2000 }
    },
    "required": ["ticket_id", "note"],
    "additionalProperties": false
  }
}

Avoid a generic tool such as execute_action with a free-form payload. It pushes important application rules into model interpretation and makes authorization difficult.

Tool descriptions should explain side effects, prerequisites, and what the tool does not do. Validate every argument in application code even when the model provider supports strict schemas. The OpenAI function calling guide describes the model-side tool interface; your service still owns the security boundary.

Separate selection from authorization

A model may decide that a tool is useful. That does not mean the action is authorized.

The application should calculate permissions from the authenticated user, tenant, role, resource, and current state. Do not expose tools the user cannot use, and check authorization again inside the tool handler.

It helps to classify tools by risk:

  • Read-only: search, retrieve, calculate, summarize.
  • Reversible write: create a draft, add a label, schedule a non-final task.
  • External communication: send a message, publish content, invite a user.
  • High-impact write: delete data, change permissions, issue a refund, deploy code.

As risk increases, require stronger confirmation, narrower limits, and better audit records. The agent should never be the only layer deciding whether a consequential action is allowed.

Design the execution loop explicitly

A tool-using agent usually follows a loop:

  1. Receive the user's goal and relevant context.
  2. Decide whether a tool is needed.
  3. Produce a structured tool call.
  4. Validate permissions and arguments.
  5. Execute the tool with a timeout.
  6. Return a structured result to the model.
  7. Continue, recover, or present the result to the user.

Implement a hard limit on steps, elapsed time, and cost. Without limits, a poorly described tool or ambiguous result can create repeated calls that consume resources without making progress.

Return errors in a form the agent can act on. “Request failed” is not enough. Distinguish invalid input, missing permission, temporary unavailability, rate limiting, and a conflict with current state.

Make writes idempotent

Networks fail, models retry, and users refresh pages. A tool that creates an invoice or sends a notification must not perform the side effect twice because the same logical action was repeated.

Use an idempotency key for important writes. Persist the relationship between the key, the request, and the result. If the same request arrives again, return the original result instead of executing it again.

For multi-step workflows, store explicit state outside the model conversation. A database record or durable job should know what completed, what is pending, and what requires attention. Conversation history is useful context, but it is not a transaction log.

Keep tool results structured and small

Tool output becomes model input. Returning a large raw API response increases cost and gives irrelevant fields an opportunity to influence the next decision.

Create a stable result shape with the data the next step needs:

{
  "ok": true,
  "ticket_id": "T-1842",
  "status": "note_created",
  "created_at": "2026-09-26T14:20:00Z"
}

Include stable identifiers so later calls can refer to the same resource. If the output is too large, store it and return a handle or a paginated subset.

Protocols such as Model Context Protocol can standardize how an application discovers tools and resources. They improve interoperability, but they do not replace good contracts, authorization, or product-specific safeguards.

Add observability before autonomy

Every agent run should produce a trace that answers:

  • What goal did the run receive?
  • Which tools were available?
  • Which tool did the model select, and with what arguments?
  • What did authorization and validation decide?
  • How long did execution take?
  • What result or error came back?
  • Why did the loop stop?

Redact secrets and sensitive content, but preserve enough structured information to debug behavior. Track tool success rate, retries, latency, approval rate, and the percentage of runs that reach a useful outcome.

Do not evaluate only the final prose. A response can sound convincing while using the wrong account, repeating a write, or ignoring a failed call.

Test scenarios, not prompts

Prompt snapshots are not enough for an agent that acts on changing systems. Build evaluation cases around situations:

  • The requested record does not exist.
  • Two tools have similar descriptions.
  • The user lacks permission for the best action.
  • A tool times out after completing the write.
  • The result is partial or stale.
  • A destructive action requires confirmation.
  • The model tries to call a tool with an unexpected field.

Each case should define the acceptable actions and the conditions that must never occur. Run these evaluations when prompts, models, tool schemas, or business rules change.

Reliability is a system property

A reliable agent is not a clever prompt attached to many integrations. It is a controlled software system in which the model proposes actions and deterministic code enforces the boundaries.

Start narrow. Make tools explicit. Keep permissions outside the model. Design retries before you need them. Trace every step. Add autonomy only after the lower-risk workflow is observable and consistently correct.

That approach may look less magical in a demo, but it is far more useful in production.

Ahmed Reda