What If Your AI Agent Doesn't Need Better Prompts — Just Better Tools? A developer argues that AI agents often fail not due to poor prompts but due to a lack of real tools, and demonstrates how equipping an agent with simple functions like running tests can dramatically improve performance. The post includes a code example and practical advice for building tool-using agents. I rewrote the system prompt fourteen times. Version 3 was "be thorough." Version 7 was a 900-word manifesto about edge cases. Version 12 threatened the model if it hallucinated a file path. Version 14 still booked a meeting on the wrong calendar and cheerfully summarized the disaster. The agent wasn't dumb. It was unarmed . If you've been stuck in prompt-tweaking hell with an AI agent that keeps almost-working, this is the post I wish I'd read first. The fix usually isn't another paragraph of instructions. It's giving the thing a real tool. A prompt is a pep talk before a job interview. A tool is the résumé, the laptop, and the door badge. You can coach someone forever on "be professional and double-check details." If they can't open the calendar API, they will invent a meeting time that sounds right. LLMs are the same — world-class at sounding competent, terrible at touching the real world unless you wire them up. That's the agentic AI shift in one sentence: stop asking the model to pretend it did something. Give it a function it can actually call. My agent had one job: when a GitHub issue is labeled needs-repro , clone the repo, run the failing test, and paste the output back. I tried better prompts first — "always clone before you speculate," "never invent stack traces," "if you're unsure, say so." It invented a beautifully formatted stack trace from a file that didn't exist. From the model's point of view, a plausible stack trace is the assignment when the only tool it has is "generate text." Then I registered three boring tools: clone repo url , run tests path , comment on issue number, body . No poetry. Next run: clone → run → paste real output. The intelligence didn't jump. The surface area of reality did. python import json, subprocess from dataclasses import dataclass from typing import Callable @dataclass class Tool: name: str description: str schema: dict handler: Callable dict , str def run tests args: dict - str: path = args "path" Never let the model shell out raw — pin the command. result = subprocess.run "pytest", path, "-q" , capture output=True, text=True, timeout=120 return result.stdout or result.stderr -4000: TOOLS = { "run tests": Tool name="run tests", description="Run pytest on a path; return the tail of output.", schema={ "type": "object", "properties": {"path": {"type": "string"}}, "required": "path" , }, handler=run tests, , } def tools for llm - list dict : """Send names/descriptions/schemas — not handlers.""" return { "type": "function", "function": { "name": t.name, "description": t.description, "parameters": t.schema, }, } for t in TOOLS.values def dispatch tool name: str, arguments json: str - str: tool = TOOLS tool name return tool.handler json.loads arguments json Loop: messages + tools for llm tool call - dispatch - append result - repeat stop on normal message or max-steps guard Notice what the prompt no longer has to say: "please don't invent pytest output." The model can't invent what run tests already returned. Reality is a function return value, not a writing style. 1. If the model must touch the world, it needs a tool. Email, APIs, files, tests, databases — tools, not adjectives in a system prompt. 2. Prefer boring, narrow tools over one god-tool. run tests path beats do whatever shell command . Narrow tools are easier to log and sandbox. God-tools are how you wake up to a deleted directory and a polite apology. 3. Put constraints in code, not in prose. Timeouts, allow-lists, max bytes, path sandboxes belong in the handler. The model is a creative writer under deadline. Your handler is the adult in the room. 4. Log tool calls like production traffic. Name, args, duration, success/fail. When an agent goes weird, you won't debug prompt vibes — you'll debug the trace: it called clone repo with a typo URL three times. Imagine a pizza shop where the only employee is a poet. Hand them a long script about greeting customers and using the oven. Without an oven dial, a ticket printer, and a delivery map, you don't have a pizza shop — you have spoken-word night with cheese anxiety. Agents are the same. Prompting is coaching the poet. Tool-use is installing the oven. Multi-agent setups multiply the moral: researchers need search, coders need file/test tools, reviewers need diffs. More agents with no tools is just a group chat with confidence issues. Prompts are great at role "prefer failing closed" , output shape "short summary after tools" , and tie-breaking "prefer the read-only tool" . They're bad at physics. "Don't hallucinate test output" is physics. Solve physics with a tool. Pick one task your current bot fakes: fetch invoice , list open prs , tail logs .If success jumps, you didn't find a better spell. You handed the wizard a screwdriver. Agentic AI feels magical in demos because demos hide the plumbing. In production, the magic is mostly tool surface area + boring guardrails + a short honest prompt . Stop asking your agent to role-play competence. Give it hands. Keep the prompt short enough that you can still read it without scrolling past your own despair. Your turn: What's one tool you wish your agent had yesterday — the specific function, not a vibe? Drop the name and two args in the comments. I'm collecting a hall-of-fame of "why didn't I add this sooner" tools.