Skip to content
0degrees.ai
Agents

Steering a Drifting AI Agent: How to Correct Course Mid-Task

Practical techniques for recognizing when an AI coding agent has gone off track and redirecting it effectively — before it digs you deeper into the wrong solution.

Josh 7 min read

AI coding agents are most useful when you can trust the direction they’re heading. But every developer who has used one for more than ten minutes has experienced the drift: you asked for a feature, the agent started implementing it, and somewhere around the third tool call you realized it was building something subtly — or not so subtly — wrong.

The natural instinct is to correct it in the existing session with a quick note. That works sometimes. It fails more often than people admit. Understanding when and how to course-correct is one of the more transferable skills in working with AI agents.

Recognizing drift before it compounds

The earlier you catch drift, the less expensive it is to fix. Signs that an agent has gone off track:

It’s implementing the wrong abstraction. You asked for a stateless request handler; it’s building a class with mutable state. You asked for a simple SQL query; it’s scaffolding a full ORM configuration.

It’s solving the stated problem, not the actual one. You asked to “fix the timeout” and it added a retry loop — technically addressing “timeout” — but the real issue is a slow database query that the retry loop will just hammer harder.

It’s making decisions you didn’t authorize. A new dependency appeared in package.json. A file that wasn’t mentioned got modified. A schema column was renamed without being asked.

The scope is expanding. You asked for a utility function and it’s refactoring the module it lives in. You asked for a bug fix and it’s rewriting the test suite.

Any of these is a signal to stop, not to nudge. A short intervention early saves a long untangle later.

Technique 1: The explicit rejection with a constraint

The weakest corrective is “actually, do it differently.” The agent usually tries to reconcile its in-progress work with the new direction, which produces a hybrid that satisfies neither.

The stronger move is an explicit rejection that names what was wrong and adds a constraint that prevents the same mistake:

Stop. The class-based approach is wrong for this use case.
We need a pure function that takes a config object and returns a handler —
no state, no constructor, no `this`. Here's the signature I want:

  function createHandler(config: HandlerConfig): (req: Request) => Promise<Response>

Start over with that signature.

The “start over” instruction matters. Without it, the model tries to salvage its existing work. With it, you get a clean attempt from a clear starting point.

Notice the constraint: “no state, no constructor, no this.” Adding specific negative constraints reduces the chance the model makes the same wrong choice again without realizing it. Generic feedback like “make it simpler” leaves too much room for the same error in a different costume.

My most expensive habit — Josh: For a long time I corrected drift with a polite “actually, can you do it more like X” and let the agent keep whatever it had already built. It almost always produced a Frankenstein hybrid that half-followed both directions and worked in neither. The first time I actually typed “Stop. Start over with this signature.” and got a clean result in one shot, I realized my reluctance to throw away in-progress work had been costing me far more than the work was ever worth.

Technique 2: The anchor re-statement

Sometimes the agent hasn’t made a fundamentally wrong choice — it’s drifted because the original goal got diluted as the session grew. This is especially common in long agentic loops where the model has processed many tool outputs and intermediate steps.

The fix is to re-state the objective explicitly, stripped of everything that accumulated around it:

Let's step back. The only goal here is:

  Add a `userId` column to the `sessions` table and populate it
  from the existing `user_email` lookup.

Everything we've discussed about caching and index optimization is a
future concern. Stay focused on the migration and the backfill query only.

This is less about correcting a mistake and more about re-focusing an agent that’s trying to do too much. It works because it gives the model a clear foreground/background distinction — what’s in scope right now versus what can be safely deferred.

Technique 3: The minimal reproduction request

When an agent gets tangled in complexity — multiple files modified, cascading changes, conflicting state — sometimes the most effective move is to ask it to prove the core idea in isolation first:

Before we continue, write a minimal standalone test that shows this approach
works at all — no database, no HTTP layer, just a function that takes an
input object and returns the expected output. Make it runnable with
`node test.js` with no imports beyond Node builtins.

This forces the agent to disentangle the idea from the implementation context. If it can’t produce a working standalone example, the approach itself is probably wrong — and you’ve discovered that before the approach was wired into your actual codebase. If it can, you have a verified kernel to build from.

// What you asked for: standalone proof
function applyDiscount(price, coupon) {
  if (!coupon || coupon.expired) return price;
  return price * (1 - coupon.discountRate);
}

const result = applyDiscount(100, { expired: false, discountRate: 0.2 });
console.assert(result === 80, `Expected 80, got ${result}`);
console.log('discount applied correctly');

Once this passes, the agent has a working reference. The next prompt can be precise: “now wire exactly this function into the route handler, nothing else.” You’ve gone from an unbounded implementation task to a bounded integration task.

Technique 4: Starting fresh with better constraints

If the session has run long and the agent’s context is polluted with a failed approach, starting a new session is often faster than fixing the current one. The reluctance to do this is psychological — you’ve invested time in the current session — but the session’s value is the insight you gained, not the conversation itself.

Before starting fresh, write down what you learned:

  • What approach you tried and why it didn’t work
  • The specific constraint that rules it out
  • What the correct shape of the solution looks like, even partially

Then open a new session and start with those notes, not with the original goal. You’re not starting from zero; you’re starting from accumulated knowledge without accumulated context pollution.

New session. Context from last attempt:

- Tried implementing this as Express middleware — failed because middleware
  can't access the request body after it's been consumed by the body parser.
- Correct approach: a wrapper called inside the route handler, after body
  parsing is complete.
- Starting point: src/routes/upload.ts, the handleUpload function.
- Change needed: validate file type before passing to the storage layer.

This is the same checkpoint pattern that makes long sessions manageable — see Managing Context in Long AI Coding Sessions for the full framework. The session’s value is the decision, not the transcript. Carry the decision forward, leave the baggage behind.

When to abandon rather than correct

Not every drift is correctable in place. Patterns that warrant abandoning the current approach entirely:

  • The agent made a fundamentally wrong architectural assumption that’s now load-bearing across multiple files
  • You’ve issued two corrective prompts and the agent produced the same wrong result a third time
  • The implementation has branched across more than three files and you can’t trace what went wrong
  • The approach violates a hard constraint you can’t work around — a security boundary, a type system invariant, an external API contract

In these cases, the cost of continuing is higher than the cost of resetting. Use the minimal reproduction technique first to verify that your preferred approach is sound, then start fresh with the validated kernel as your anchor.

A useful heuristic: if untangling the current state would take longer than re-doing it from scratch with better constraints, reset. The lost time is already spent; don’t compound it by continuing in the wrong direction.

Correct early, correct clearly

The leverage is in timing and specificity. A vague “that’s not quite right” at message 15 is nearly useless. An explicit rejection with a specific constraint at message 3 saves hours.

Agents don’t have intuition about when you’re disappointed versus when you’re directionally aligned but adjusting details. The more precisely you name what’s wrong — the wrong abstraction, the unauthorized scope expansion, the literal-versus-intended mismatch — the better the correction lands.

The deeper fix is to prevent these situations from starting. Specifying the signature, the constraints, and the scope at the beginning of the task catches most drift before it begins. For the prompting discipline that prevents these patterns upstream, see Prompt Engineering Patterns That Survive Production.

Keep reading

Agents 4 min read

Build Your First AI Agent in TypeScript

A from-scratch walkthrough of the agent loop — tools, reasoning, and termination — using the Claude API and plain TypeScript. No frameworks.

Josh