Agentic Instincts: Coding Agents and their love for Pattern Matching
A case study in why AI agents need eval-first engineering and how "coding agents" should code "agents"
The agent worked perfectly, right up until the user said the same thing differently, a failure mode I’ve seen cropping up one-too-many times when building ‘agents’ with ‘Coding agents’ (ouroboros reference :D)
Take for instance a “workflow agent” that is supposed to understand a user, collect missing information, use tools, retrieve a file, create a case, or route work to a human when policy requires it. The first version mostly works. Then a real user phrases something in an unexpected way. The agent fails.
So a coding agent is asked to fix it.
Too often, the “fix” is a regex. Or a keyword list. Or a substring check. Or a tiny field resolver for one specific case. Then tests are added to prove those strings match.
The tests pass. The demo looks calmer. The architecture gets worse.
To be clear, this is not a rant against regex. It is excellent for machine-owned formats, particularly IDs, timestamps, provider status codes, JSON envelopes, and known protocol strings. The problem starts when regex is used to decide what a human means.
At that point, the agent has quietly become a brittle parser with an LLM attached to it.
The Parser Trap
Here is the anti-pattern, and what coding agents tend to generally follow when a feedback loop via a stack trace is provided to it that “hey this field isnt being parsed correctly from the user message by our agent”:
if (/invoice|contract|file/i.test(userMessage)) {
return sendRequestedFile();
}
The exact form changes. Sometimes it is fuzzy matching. Sometimes it is token overlap. Sometimes it is a list of field names. Sometimes it is a transcript check like “if the bot said this phrase, mark the flow complete.”
It all comes from the same instinct, convert an open-ended language problem into a deterministic string problem.
That instinct is understandable. A lot of normal engineering rewards it. Narrow the input. Define the branch. Add the test. Turn ambiguity into code.
But agent workflows exist because the input is not stable enough to enumerate. Users misspell words, switch languages, interrupt themselves, refer to previous turns, attach files without explaining them, ask side questions, and use phrases no engineer predicted.
Once we add one parser branch per failure, every new use case becomes code. Ten fields become ten resolvers. Ten case types become ten keyword maps. Fifty files become fifty fragile branches. The agent’s intelligence is now fenced in by whatever phrases we remembered to hardcode.
The Self-Fulfilling Test
The bigger problem is the test that grows around the parser.
Runtime code says:
message contains “invoice” => file workflow
The test says:
expect(message).toContain(”invoice”);
expect(response).toMatch(/file/i);
Now the test and the implementation share the same false assumption. The system passes because both sides believe the user will say the magic word.
That is a self-fulfilling prophecy.
A unit test can prove deterministic code obeys a contract. A string-matching “agent test” usually proves only that the parser recognizes phrases we already anticipated.
It does not prove the agent understood the user. It does not prove the right tool was used. It does not prove the business operation happened. It does not prove the answer was grounded in real state.
For agents, transcript matching is often the lowest-signal kind of confidence.
Why Coding Agents Reach For This
I do not think coding agents do this because they are incapable. They do it because the local reward landscape reinforced through the RL post-training points them there.
They are handed a concrete failure: “the flow got stuck when the user asked for a file” or “this form field was not filled.” The smallest patch is to recognize that phrase and force the desired branch. The smallest test is to assert that branch.
It looks like engineering condition, handler, test, green check. The good ol’ TDD loop.
But in an agent system, that patch bypasses the part of the system that should do semantic work, the model operating inside a harness with tools, schemas, durable state, and feedback.
Test-first habits are not wrong. They are just incomplete for agent behavior. TDD was built for deterministic contracts. Agents are probabilistic, tool-using, stateful, and often open-ended.
If we do not give coding agents an eval-first instinct, they often translate an agent failure into a parser task because parser tasks are easier to make green.
TDD Is Not The Enemy
The lesson is not “stop writing tests.” The lesson is:
Test deterministic parts deterministically. Evaluate agentic parts agentically.
There is still plenty of ordinary software in an agent system:
Schema validation
Tool input validation
Authorization
Idempotency
Database writes
Queue transitions
API adapters
Payload construction
Error normalization
Those should have normal unit and integration tests.
But semantic behavior needs evals. If the question is “did the agent understand the user and take the right next step?”, the eval should run the actual agent path and inspect model decisions, tool calls, state transitions, final operations, and user-visible output.
The unit test asks “did this deterministic function obey its contract?”
The eval asks “did the agent, in this environment, with these tools and this state, solve the task in a way we would accept in production?”
Those are different questions.
A Safer Boundary
A robust workflow agent should not ask application code to infer user intent from raw language, a suggested better split would be:
human language
-> model semantic decision through structured output or tool call
-> deterministic validation against schemas, catalogs, policy, and state
-> deterministic execution
-> grounded response from the actual operation result
The model decides what the user likely means. Code decides whether that decision is valid and executable.
For a file request, the backend should build a catalog:
type FileCatalogItem = {
id: string;
label: string;
metadata: Record<string, unknown>;
canDeliver: boolean;
};
The model receives the user request plus the catalog and returns structured output:
type FileSelection = {
selectedIds: string[];
confidence: number;
clarificationQuestion?: string;
reason: string;
};
Then code validates selected IDs against the catalog and sends only files that actually exist and are deliverable. No hardcoded file type names. New file types require better metadata, not a new parser branch.
The same idea applies to dynamic forms. The backend provides the current field schema and known facts. The model maps messy user language into structured fields. Code validates field IDs, option values, attachments, and payload shape. If something is missing, the workflow asks for it. If the user asks “what does this field mean?”, the agent answers without abandoning the flow.
The scalable unit is not “one resolver per field” but rather a typed field schema plus a field-collection workflow that can handle N fields.
Memory Means Durable State
People often talk about agent memory as if it means personality. In production workflows, memory is more basic: durable state.
The harness needs to know:
Which workflow is active
Which phase it is in
Which fields are collected
Which fields remain missing
Where each value came from
Which tool calls already happened
Which errors are retryable
Which action is awaiting confirmation
Which payload hash was confirmed
Whether a human route was already queued
If this lives only in a process-local variable, the agent is fragile. A restart, deploy, duplicate webhook, or second server instance can erase the flow. Then the user answered, but the agent forgot. The tool succeeded, but the response says it failed. The confirmation happened, but it is not tied to the payload.
Durable state is the agent’s working “memory”. A scratchpad is not a luxury, but how the harness prevents the model from re-solving the same problem every turn.
Good memory is structured, not an ever-growing transcript blob:
type WorkState = {
activeUseCase: string;
phase: “routing” | “collecting” | “answering_side_question” | “awaiting_confirmation” | “executing” | “done”;
collectedFacts: Array<{ fieldId: string; value: unknown; source: string }>;
missingFields: string[];
pendingAction?: { kind: string; payloadHash: string };
lastToolResults: Array<{ tool: string; status: “ok” | “retryable_error” | “fatal_error” }>;
};
Tools Should Be Signal-ly
Another failure mode is tool pollution. The agent calls a backend tool and receives a huge response. Now the model has to inspect a pile of JSON, infer what matters, ignore irrelevant fields, and recover from errors written for developers.
That is backwards.
Agent tools should return the shortest useful result for the next decision. They need precise names, clear input schemas, concise success output, and actionable error output.
Bad tool result:
{
“error”: “500”,
“stack”: “...”,
“data”: null
}
Better tool result:
{
“status”: “retryable_error”,
“reasonCode”: “file_service_unavailable”,
“agentHint”: “Do not claim the file was sent. Apologize briefly and offer to retry or route to a human.”
}
The model should do semantic judgment. It should not be used as a garbage collector for messy tool output.
Eval-First Development
An eval-first harness does not start by asking “what exact phrase should the final answer contain?”
It starts with a scenario:
type EvalScenario = {
id: string;
initialState: FixtureState;
turns: UserSimulator | StaticTurns;
availableTools: ToolFixture[];
expectedOutcomes: ExpectedOutcome[];
rubric: SemanticRubric;
};
Then it runs the real path:
Seed fixtures.
Run the router.
Run the selected workflow.
Let the model call tools or return structured decisions.
Execute tools through production-like validation.
Persist state transitions.
Capture a trace.
Grade the outcome.
Deterministic graders check schema validity, selected IDs, allowed actions, queue state, persisted fields, confirmed payload hashes, and tool-backed claims.
Semantic graders check whether the response is helpful, grounded, in the user’s language, and appropriate to the active workflow.
This is why evals are not tests with a trendy name. A good eval measures the model plus the harness: tools, state, policies, traces, and outcomes. It allows multiple valid trajectories, but not invalid business results.
The Harness Shape
The harness I want looks like this:
Inbound message
-> Context Builder
-> Intent Router
-> Use Case Contract Registry
-> Data Resolver
-> Specialized Workflow or Agent
-> Tool Executor
-> Policy Guard
-> Confirmation Gate
-> Action Executor
-> Response Composer
-> Trace Recorder
-> Eval Verifier
The names matter less than the ownership.
The router owns semantic classification. The context builder owns what the model sees. The contract registry owns which use cases exist, what tools they may use, what policies apply, and how they are verified. The workflow owns state progression. The tool executor owns validation and side effects. The response composer owns user language grounded in actual results. The trace recorder owns observability.
Without those boundaries, every fix lands in the same giant service file, and every new use case increases entropy.
The Skill File Rule
If you use coding agents to build agents, give them explicit rules:
Do not infer user intent with regex, keywords, substring checks, fuzzy matching, or transcript patterns.
Use model structured output or tool calls for semantic decisions.
Use deterministic code for validation, execution, policy, persistence, and audit.
Add new use cases by extending schemas, catalogs, tools, workflows, and eval fixtures.
Evals must run the actual agent path and grade traces, tool calls, state, business outcomes, and semantic quality.
If a failure seems to require a new string pattern, stop and redraw the agent boundary.
When a coding agent wants to add a regex, it is often telling you something useful, which is that the harness does not give the model a clean way to make that decision.
Maybe you need a structured router. Maybe a catalog needs stable IDs. Maybe a tool response is too noisy. Maybe state is not durable. Maybe the eval checks a transcript instead of an outcome.
Final Thoughts
The future of agent engineering is not one giant prompt. It is also not a thousand regexes hiding behind a friendly chat bubble.
It is “harness engineering” (as cliché as it may sound now…..) which means instructions, tools, environment, state, and feedback arranged so the model can make semantic decisions and deterministic software can keep those decisions safe.
For agents, the question worth making green is not “did this exact phrase appear?”
It is “did the agent understand, act, recover, and leave the system in the right state?”
Useful/Supporting material
WalkingLabs’ Learn Harness Engineering frames a harness as more than a prompt file: instructions, tools, environment, state, and feedback. https://walkinglabs.github.io/learn-harness-engineering/en/lectures/lecture-02-what-a-harness-actually-is/
Anthropic’s eval guidance separates code-based, model-based, and human graders, plus capability and regression evals. https://www.anthropic.com/engineering/demystifying-evals-for-ai-agents
Anthropic’s tool-design guidance emphasizes clear boundaries, meaningful context, token-efficient responses, and actionable errors. https://www.anthropic.com/engineering/writing-tools-for-agents
Anthropic’s context-engineering guidance discusses structured note-taking and memory outside the context window. https://www.anthropic.com/engineering/effective-context-engineering-for-ai-agents
OpenAI’s agent guide describes tool-using agent loops, clear tools/instructions/guardrails, and keeping orchestration as simple as possible. https://openai.com/business/guides-and-resources/a-practical-guide-to-building-ai-agents/
Mastra’s docs describe agents with tools, memory, structured outputs, and optional workflows. https://mastra.ai/docs/agents/overview
LangChain’s eval writing recommends targeted evals, trace review, and avoiding eval suites that only create an illusion of improvement. https://www.langchain.com/blog/how-we-build-evals-for-deep-agents
12-Factor Agents argues for natural-language-to-tool-calls, deterministic execution, compact errors, and unified execution/business state. https://github.com/humanlayer/12-factor-agents
Xia et al. propose evaluation-driven development for LLM agents because traditional predefined test cases struggle with open-ended, tool-using behavior. https://arxiv.org/html/2411.13768v2





