Trial and ERAI: Shipping Software When Code Writes Itself
AI made attempts cheap. Knowing which attempt is right did not get cheaper. How specs, tests, reviews and evals become the real work in the era of AI.
Software has always been built by trial and error. You write something, run it, watch it break, and try again. Every engineer knows the loop, and a lot of engineering practice exists to make it shorter and safer.
AI has changed the economics of that loop. An attempt, meaning a function, a component or a whole first draft of a feature, now costs almost nothing to produce. You can generate ten in the time it used to take to type one.
That sounds like the hard part is solved. It isn’t. Attempts were never the hard part. The hard part was always deciding exactly what to build, and then knowing which attempt actually does it. When attempts are free, everything depends on the trial: the spec, the test, the review, the evaluation that separates a correct answer from a confident wrong one.
We call this trial and ERAI: trial and error in the era of AI. Here’s what we think changes, what doesn’t, and how we’re adapting the way we work.
The bottleneck moved
Picture the work on a typical feature as three phases:
- Specify: turn a fuzzy need into precise behavior.
- Implement: turn that behavior into code.
- Verify: prove the code does what was specified, and nothing else.
For most of software’s history, implementation took the most time, so it set the pace. Process, estimates and team size were all built around it.
AI coding assistants shrink phase 2 a lot. A competent engineer with good tools can produce a working first draft of a well-defined component in minutes instead of hours. But a vague spec still gives you a vague result, only faster. Code nobody has verified is still a liability, and there is now much more of it.
The bottleneck has moved to the two ends of the pipeline. Teams that do well now are the ones that invest there.
1. Specifications are worth more than ever
When a person implements a vague requirement, they fill the gaps with judgment. They ask questions, notice contradictions and push back. An AI model fills the gaps with plausible guesses, and it does so with confidence.
So the quality of the input now sets the quality of the output. A good spec today is explicit about:
- Inputs and outputs, including types, formats and limits.
- Edge cases: empty states, duplicates, time zones, partial failures.
- Non-goals: what the feature deliberately does not do.
- Acceptance criteria written so a test can check them.
This is no extra paperwork. Writing it was always good practice, and now it pays off right away, because the same document guides the engineer, the AI assistant and the reviewer.
2. Tests are the contract
If code is cheap to produce, it’s also cheap to produce wrong. The only scalable defense is automated verification that doesn’t depend on whoever, or whatever, wrote the code.
In practice, that means:
- Write the tests from the spec, before or alongside the implementation. Tests generated from the implementation tend to lock in its bugs.
- Test behavior, not internals. AI-assisted refactors are frequent and sweeping. Tests tied to private implementation details break constantly and teach the team to ignore them.
- Keep the suite fast. A suite that runs in seconds gets run on every change. One that takes twenty minutes gets run before lunch.
A useful rule of thumb: if you couldn’t tell whether a piece of AI-generated code is correct without reading every line, you don’t have enough tests yet.
3. Review is where the real engineering happens
Code review used to catch typos, style issues and the occasional logic error. Now it is the main point where a human checks that the system still makes sense.
Reviewing AI-assisted code calls for a different focus:
- Intent over syntax. Formatting and linting are automated. Spend review time on the question does this solve the problem we specified?
- Look for confident nonsense. Watch for calls to APIs that don’t exist, configuration options that are invented, and error handling that catches everything and handles nothing.
- Keep changes small. A 2,000-line pull request generated in ten minutes still takes hours to review properly. Teams that keep pull requests small and focused review them better.
The engineer who submits the code owns it, whoever typed it. That principle hasn’t changed. It just needs saying more often now.
4. Building AI into products: treat the model as an unreliable dependency
Using AI to build software is one shift. Shipping AI inside software is another, and it brings an engineering problem most teams haven’t dealt with before: a core dependency that is non-deterministic, slow, metered and occasionally wrong.
The answer isn’t new. Treat the model the way you’d treat any flaky third-party service: validate its output, put a time limit on it, budget for it, and have a fallback.
Here’s a simplified example: triaging incoming support messages with a language model. Note what the code does not trust.
import { z } from 'zod';
const Triage = z.object({
category: z.enum(['billing', 'bug', 'feature_request', 'other']),
priority: z.enum(['low', 'medium', 'high']),
summary: z.string().min(1).max(280),
});
type Triage = z.infer<typeof Triage>;
export async function triage(message: string): Promise<Triage> {
for (let attempt = 1; attempt <= 2; attempt++) {
try {
const raw = await llm.complete({
system: TRIAGE_PROMPT,
input: message,
responseFormat: 'json',
timeoutMs: 8_000,
});
const parsed = Triage.safeParse(JSON.parse(raw));
if (parsed.success) return parsed.data;
log.warn('triage.invalid_output', { attempt, issues: parsed.error.issues });
} catch (err) {
log.warn('triage.call_failed', { attempt, err });
}
}
// Safe default: never drop a customer message because the model misbehaved.
return { category: 'other', priority: 'medium', summary: message.slice(0, 280) };
}
Each line here is a decision:
- Schema validation. The model’s output is untrusted input, just like a form submission. If it doesn’t match the schema, it doesn’t enter the system.
- Bounded retries. One retry fixes most problems that come from randomness. Unlimited retries turn a bad prompt into a large bill.
- Timeouts. A request that hangs is worse than one that fails.
- A deterministic fallback. The feature degrades gracefully instead of failing. In this case the message still reaches a human.
- Logging invalid outputs. These logs become the raw material for improving the prompt.
5. You can’t unit-test vibes, so write evals
Traditional tests assert exact outputs. AI features rarely have one exact correct output, which tempts teams to skip testing and rely on “it looked good when I tried it.”
The answer is an evaluation set: a few dozen to a few hundred real, representative inputs, each with the properties a good answer must have. For the triage example, that could be:
| Input (abridged) | Must be | Must not be |
|---|---|---|
| “I was charged twice this month” | billing |
priority low |
| “App crashes when I upload a PDF” | bug |
feature_request |
| “Could you add dark mode?” | feature_request, priority low |
bug |
| “URGENT site is down for all users” | bug, priority high |
Run the set on every prompt change and every model upgrade, and track the pass rate like any other quality metric. When the score drops, you find out before your users do. Add every production failure to the set, so the system can’t make the same mistake twice without anyone noticing.
6. Data deserves a second look
Every prompt you send to a hosted model is data leaving your system. Before shipping an AI feature, answer these explicitly:
- What exactly is sent? Does it include personal data, credentials or client-confidential material that the task doesn’t need?
- Where does it go, and for how long? Read the provider’s data-retention and training policies, and pick the settings that fit your obligations.
- Can you redact first? Often the model doesn’t need the customer’s name, email or account number to do its job.
Data protection was a promise before AI arrived. It still is, and there are now more ways to break it by accident.
What doesn’t change
Strip away the novelty and the fundamentals look familiar. At Wandertech every project still goes through the same four steps:
- Plan. Clear specs and acceptance criteria, now even more valuable.
- Build. Faster with AI assistance, held in check by tests and careful review.
- Deploy. With the same safeguards, plus timeouts, budgets and fallbacks wherever a model is in the loop.
- Maintain and improve. Driven by user feedback, production logs and an evaluation set that grows over time.
AI is a powerful new tool in that process, not a replacement for it. Trial and error is still how software gets built. The difference is that the errors now arrive faster, look more convincing, and come in larger batches. The teams that do best won’t be the ones generating the most code. They’ll be the ones with the best trials: they know exactly what they want, can prove they got it, and treat every new capability with healthy engineering skepticism.
A short checklist for your next feature
- The spec lists inputs, outputs, edge cases and non-goals.
- Acceptance criteria are written as checkable statements.
- Tests are derived from the spec, not from the implementation.
- Pull requests are small enough to review properly.
- Any model output is validated against a schema.
- Model calls have timeouts, bounded retries and a fallback.
- An evaluation set exists and runs on every prompt or model change.
- You know what data leaves your system, and you’ve minimized it.
Planning an AI feature, or wondering where AI fits in your product? Talk to us. Consultation is free.