Harness Engineering

Designing Tools for AI Agents: 8 Rules From Production

By Expedey Engineering · Updated · 3 min read

Short answer

Give agents narrow, well-described tools with typed and validated arguments. Return errors the model can act on, keep results small, separate read tools from write tools, enforce business rules inside the tool and make writes idempotent. Most "the agent picked the wrong action" problems are fixed in the tool layer, not the prompt.

Why tools decide how good an agent is

An agent can only act through its tools. When a tool is vague, overlapping or fragile, the model guesses, and guesses look like hallucinations or wrong actions. The rules below come from building agents that call real APIs, and each one is implemented in our open-source agent harness template.

The eight rules

  1. One tool, one action. lookup_order and issue_refund, not manage_orders(action=...). Narrow tools are easier for the model to choose and easier for you to permission.
  2. Describe tools for the model. The description should say what the tool does, when to use it and what it returns. It is part of the prompt, so write it as carefully.
  3. Type and validate every argument. Declare a schema with formats and limits (an order ID pattern, a positive amount) and validate before running anything.
  4. Return errors the model can act on. "No order found with ID A9999. Ask the customer to check it." is useful; a stack trace is not, and can leak internals.
  5. Keep results small and relevant. Return the fields the agent needs, as compact JSON, and truncate long output. Every extra token is cost and noise in later steps.
  6. Separate read from write. Reading is cheap to get wrong; writing isn't. Write tools should be clearly marked and, by default, require approval.
  7. Enforce rules inside the tool. If refunds are only allowed within 30 days, the refund tool checks that itself. The prompt should state the policy, but it must never be the only check.
  8. Make writes idempotent. Retries happen, from the model and from your own retry logic. An idempotency key or a check-before-write stops a retry from refunding twice.

What it looks like in code

Adapted from the template's order support example: a typed read tool, and a write tool that needs approval, re-checks the refund policy itself and passes an idempotency key to a (stand-in) payments client.

from pydantic import BaseModel, Field
from harness import ToolError, ToolRegistry

tools = ToolRegistry()

class LookupOrderArgs(BaseModel):
    order_id: str = Field(pattern=r"^A\d{4}$", description="Order ID, e.g. A1001")

class RefundArgs(BaseModel):
    order_id: str = Field(pattern=r"^A\d{4}$")
    amount: float = Field(gt=0, description="USD, at most the order total")
    reason: str = Field(min_length=5)

@tools.tool(LookupOrderArgs, "Look up an order's status, items, total and delivery date.")
def lookup_order(args: LookupOrderArgs) -> dict:
    order = ORDERS.get(args.order_id)
    if order is None:
        raise ToolError(f"No order found with ID {args.order_id}. Ask the customer to check it.")
    return {"order_id": args.order_id, **order}

@tools.tool(RefundArgs, "Refund a delivered order. Requires human approval.", writes=True)
def issue_refund(args: RefundArgs) -> dict:
    order = ORDERS.get(args.order_id)
    if order is None or order["status"] != "delivered":
        raise ToolError("Only delivered orders can be refunded.")
    if args.amount > order["total"]:
        raise ToolError(f"Amount exceeds the order total of {order['total']:.2f}.")
    return refunds.create(args.order_id, args.amount, idempotency_key=f"refund-{args.order_id}")
Validation errors and ToolErrors go back to the model as messages, so it can correct itself instead of crashing the run.

How many tools should an agent have?

There's no magic number, but choice quality drops as tools overlap. Keep each agent's set small and distinct. When the list grows, split the work into specialist agents, each with its own tools, and route between them.

Frequently Asked Questions

Should agent tools return JSON or plain text?

Compact JSON for data, so the model can read fields reliably, and short plain sentences for errors and confirmations. Avoid returning whole records or long documents; return what the next step needs.

Where should business rules live, in the prompt or the tool?

Both. The prompt tells the agent the policy so it rarely tries a forbidden action; the tool enforces it so a mistake, or a prompt injection, can't get through.

Want a Second Pair of Eyes on Your Agent?

Expedey is a team of forward-thinking individuals working and servicing clientele across the globe for their software development requirements.

Our Offices

Contact Us

© 2024 Expedey. All Rights Reserved.