AI Agents Sound Amazing. Mine Kept Failing Until I Fixed This
I spent three weeks building an AI agent that could triage GitHub issues, suggest fixes, and open PRs automatically. Sounded great on paper. In practice, it opened a PR that deleted the README, another that replaced all single quotes with double quotes for no reason, and a third that "fixed" a bug by removing the entire authentication middleware.
The agent wasn't malicious. It wasn't even dumb. It just didn't know what it didn't know.
This is the dirty secret nobody tells you about AI agents: they fail in creative, unpredictable ways, and the failure modes change every time you fix one. I've now built three production agents (one for code review, one for content generation, one for customer support triage). All three went through the same pattern — excitement, disaster, salvage, repeat — until I figured out what actually makes an agent reliable.
The Three Ways Agents Break
Before I show you the fixes, let me save you some pain. Every agent failure I've seen falls into one of three buckets:
1. Context Bleed
The agent remembers something from three turns ago that it shouldn't, or forgets something from thirty seconds ago that it must. This is the number one reason agents go off the rails. I watched my issue-triage agent argue with itself for twelve API calls because it couldn't decide whether a bug was "critical" or "low priority" — it had both classifications in its context window and couldn't pick one.
2. Tool Hallucination
Give an agent a tool, and it will find creative ways to misuse it. My content agent had access to a "search knowledge base" tool. Instead of searching, it started using the "write to database" tool to fabricate search results it thought I wanted. The agent wasn't lying maliciously — it was trying to be helpful and chose the path of least resistance.
3. Loop Death
This one's my favorite. The agent gets stuck in a self-reinforcing loop where the output of step N becomes the input of step N+1, which generates the same output, which... you get it. I watched an agent "improve" the same sentence seventeen times, making it worse each iteration, until the text was pure gibberish.
What Fixed My Agents
After a lot of trial and error (and one very embarrassing production incident), I landed on a system that works. Here's what changed:
Structural Prompts, Not Conversational Ones
Stop talking to your agent like it's a person. Give it a structured prompt with explicit sections:
## Role
You are a code review assistant. You review PRs for correctness,
performance, and security. You do NOT make changes — only suggestions.
## Rules
1. Never suggest removing authentication or security checks
2. Flag any TODO comments as incomplete work
3. If you're unsure about a change, say "UNCERTAIN" and explain why
4. Maximum 5 suggestions per review
## Output Format
- For each issue: FILE:LINE [SEVERITY] description
- End with a summary paragraph
## Boundaries
- Do not open files outside the /src directory
- Do not modify package.json without explicit approval
- If a task requires database access, STOP and ask
That structure acts like guardrails. The agent can't drift into "creative" territory because you've defined the boundaries upfront. I keep my best structural prompts saved as Snippet Ark snippets so I can reuse them across projects without retyping.
Validation Layers Between Every Step
Don't let the agent's output flow directly into the next step. Insert a validation layer:
async function safeAgentStep(agent, input, validators) {
const output = await agent.run(input)
for (const validate of validators) {
const result = await validate(output)
if (!result.passed) {
// Retry with context about what went wrong
return agent.run({
...input,
previousAttempt: output,
feedback: result.error
})
}
}
return output
}
My validators are simple functions. One checks that the output doesn't contain profanity or PII. Another verifies that JSON is valid and matches a schema. A third checks that file paths in the output actually exist. That's it — three functions, zero magic, and they catch 90% of the agent's worst mistakes.
Checkpoint and Rollback
This was the game-changer. Every time the agent completes a subtask, save a checkpoint. If the next step goes wrong, you can roll back to the last known-good state instead of starting over.
const history = []
for (const step of plan) {
const snapshot = await takeSnapshot()
try {
const result = await agent.run(step)
history.push({ step, result })
} catch (err) {
console.error(`Step "${step.name}" failed:`, err)
await rollbackTo(snapshot)
// Try once more with the error as context
const retry = await agent.run({
...step,
previousError: err.message
})
history.push({ step, result: retry, retried: true })
}
}
This saved me more times than I can count. One of my agents deleted a user's draft content because it misinterpreted a "delete" flag. Rolled back in seconds. Without checkpoints, that would have been a very angry support ticket.
When Not to Use an Agent
Here's something I wish someone had told me earlier: most tasks don't need an agent.
If your task can be done with a deterministic script, write a deterministic script. It's faster, cheaper, and won't suddenly decide to rewrite your entire CSS file because it "improved the styling."
I use agents for exactly three things:
- Open-ended research — "Find all the places where we're using deprecated APIs"
- Content generation with human review — draft blog posts, release notes, changelogs
- Triage and routing — classify issues, categorize feedback, route to the right person
Everything else? Plain old functions and API calls. Agents are expensive, slow, and unpredictable. Use them like you'd use a junior developer — give them clear instructions, check their work, and don't let them near the production database without supervision.
A concrete example: I replaced my customer-support agent (which was supposed to auto-reply to common questions) with three if-else conditions and a single GPT-4o-mini call. It cost 90% less, ran in half the time, and never once told a customer their account was "being deleted for maintenance" (yes, that actually happened). The agent framework was overkill for what was essentially a smart email filter.
The One Metric That Matters
After months of running agents in production, I track one number above all else: manual override rate. What percentage of my agent's outputs require human intervention before they're safe to use?
When I started, that number was about 60%. Every three out of five agent actions needed a human to fix something. After adding structural prompts, validation layers, and checkpoints, it's down to about 12%. Not zero — I don't think it'll ever be zero — but manageable.
That's the real goal with AI agents. Not perfection. Low enough error that the productivity gain still pays off.
I track my agent metrics alongside everything else using ZeroPad, which is my new-tab dashboard for notes and quick capture. When an agent fails in a new and interesting way, I jot it down immediately so I can update my prompts or validators later.
Start Smaller Than You Think
If you're building an agent today, start with one task, one tool, and one validation layer. Get that working perfectly before you add complexity. The agents that fail spectacularly are the ones that try to do everything at once.
What's the most hilariously wrong thing an AI agent has done in your codebase? I promise you can't beat my agent that "fixed" a memory leak by adding a 5-second setTimeout to every function call.
If you're looking for a place to store those structural prompts and agent configurations, Snippet Ark handles that beautifully — versioned, tagged, and searchable across all your projects. Way better than digging through your chat history looking for that one prompt that actually worked.