Agents and tool calling
Agents are LLMs that take actions through tools. Instead of one response, they run a loop: decide, call a tool, read the result, decide again. The staff decision for any workflow is how much of that loop you let the model control.
The agent loop
Deterministic vs autonomous workflows
| Deterministic | Autonomous |
|---|---|
| Fixed sequence of tools | Model decides the order |
| Predictable, easy to test | Adapts to context |
| Fewer model calls, lower cost | More calls, higher cost |
| Use for: pipelines, ETL, payments | Use for: open-ended investigation |
Default to deterministic. Add autonomy only when evals support it. Most business workflows are a fixed sequence with one or two steps that need judgment. Put the model only at those steps.
Tool calling basics
Tools are functions the model can call, described by a name, a description, and an input schema:
// Tool definition (Anthropic format)
const tools = [{
name: "get_order_status",
description: "Look up the current status of an order by its ID",
input_schema: {
type: "object",
properties: {
order_id: { type: "string", description: "Order ID, e.g. ord_123" }
},
required: ["order_id"]
}
}];
// Implementation: your code, your permissions
async function get_order_status({ order_id }) {
const order = await db.orders.findById(order_id);
return { status: order.status, updated_at: order.updatedAt };
}Deterministic workflows with tools
# Deterministic pipeline: code controls the order, the model does one creative step
async def process_order(order_id: str):
order = await tools.fetch_order(order_id)
validation = await tools.validate_order(order)
if not validation.valid:
return {"error": validation.errors}
pricing = await tools.calculate_pricing(order)
payment = await tools.process_payment(order, pricing)
# The only model call
confirmation = await llm.generate(f"Write a friendly order confirmation for {order}")
return {"status": "success", "message": confirmation}Hybrid approach: constrained autonomy
Let the model choose tools, but only from the set that fits the current phase:
TOOLS_BY_PHASE = {
"research": ["web_search", "read_file", "query_database"],
"implement": ["write_file", "run_tests", "lint_code"],
"review": ["read_file", "static_analysis", "security_scan"],
"deploy": ["build", "deploy_staging", "run_smoke_tests"],
}
response = await llm.chat(messages, tools=TOOLS_BY_PHASE["implement"])Tool design best practices
| Do | Don't |
|---|---|
Descriptive names (create_github_pr) | Vague names (do_thing) |
| Clear parameter descriptions | Assume the model knows your schema |
| Return structured data | Return unformatted strings |
| Include error states in the result | Let tools throw unhandled errors |
| Idempotent operations | Side effects without confirmation |
| Least privilege per tool | One tool that can do anything |
Least privilege is the security model. An agent with a write-anything tool is one bad prompt, or one prompt injection, away from an incident.
Error handling in tool calls
Return errors as data, so the model can recover instead of the loop crashing:
async function risky_tool(params) {
try {
const result = await doRiskyOperation(params);
return { success: true, data: result };
} catch (error) {
return {
success: false,
error: error.message,
suggestion: "Check the ID format and try again"
};
}
}Trace every tool call with inputs, outputs, latency, and cost. You can't debug, secure, or bill what you can't see.
Example prompts for agent development
Deterministic pipeline:
"Build an invoice processor that runs in this exact order:
1. extract_pdf 2. validate_invoice 3. check_duplicates 4. insert_record
5. confirmation email (the only model step)
No deviation. Return an error if any step fails."
Tool design:
"Design tool schemas for our inventory system: get_product(sku),
update_stock(sku, quantity), reserve_stock(sku, quantity, order_id).
Idempotent. Structured errors."
Constrained agent:
"Build a support agent that can ONLY use: lookup_order, check_shipping_status,
create_support_ticket, escalate_to_human. It must never have refund or
cancellation tools."Standardize tool contracts across teams: shared definitions and error shapes, so agents compose instead of each team rebuilding the same tools slightly differently.