Skip to content
Goatfied

agent-loop

Teaching an agent when to stop and ask a human

Agents need clear escalation rules to avoid wasting time on stuck tasks - this post covers detection signals and handoff patterns that prevent expensive retry loops.

2026-07-298 min readBy Goatfied
Teaching an agent when to stop and ask a human

The hardest part of building an AI coding agent isn't making it write code—it's teaching it to recognize when it's stuck and escalate to a human before wasting ten minutes trying the same broken approach five different ways.

We've all seen it: the agent tries to fix a test failure, breaks two more tests, rolls back, tries a slightly different import path, breaks the same tests again, then generates a comment explaining why it can't proceed while the actual fix was just adding a missing await keyword. The cost isn't just compute time—it's the context switch when you notice the agent spinning, the lost trust when you have to review a dozen failed attempts, and the eventual decision to just do it yourself next time.

The question isn't whether agents should hand off to humans, but how they decide when. Every team will draw different lines based on their risk tolerance, their codebase complexity, and how much supervision they can afford. Here's what we've learned building handoff logic that actually gets used.

Detecting agent uncertainty vs. agent confusion

The first distinction that matters: uncertainty is "I have three plausible approaches and no strong signal which is right," while confusion is "I don't understand what's being asked or how the existing system works." Both should trigger handoffs, but the prompts are different.

Uncertainty handoffs look like branching decision points. The agent detects that the change will touch a critical path—maybe it's modifying authentication middleware or changing a database migration—and multiple strategies seem equally valid. A good heuristic here is: if the agent's planning step generates two or more approaches that each pass a basic sanity check (imports resolve, types match, no obvious runtime errors), flag it for human choice rather than picking arbitrarily.

Confusion handoffs are pattern-match failures. The codebase uses a custom ORM with non-standard query syntax, or there's an internal service mesh the agent hasn't seen before, or the user said "fix the widget" and there are seven different components called Widget. Here the agent can usually articulate what it doesn't understand: "I found three classes named Widget in different modules—which one handles the checkout flow?" That specificity is the handoff.

In Goatfied's constraint phase, we surface both. Before committing to an edit, the agent checks: does this change touch files flagged as high-risk in .goatfied/config.yml? Are there unresolved ambiguities in the plan that would force guessing? If yes, the handoff happens before any code is written, not after a failed compile attempt.

Validation failures are cheap signals

The simplest handoff trigger is repeated validation failure. If the agent makes an edit, the compile step fails, the agent tries a fix, and it fails again in the same way, that's a strong signal to stop.

Here's a real pattern we gate against:


# .goatfied/handoff-rules.yml

validation:

  max_retry_same_error: 2

  error_similarity_threshold: 0.8

When the agent sees the same error message—or an error message more than 80% similar by token overlap—twice in a row, it pauses and asks. The prompt becomes: "I've tried fixing this cannot find module '@internal/auth' error twice with different import paths. The module might be aliased in a way I don't recognize, or it might be a workspace dependency that needs setup. Should I continue or do you want to review?"

This catches a huge class of failures: misconfigured build tools, missing environment variables, outdated lockfiles. All things a human can spot in ten seconds that an agent will burn cycles on.

The trick is setting the threshold right. Too low (max_retry = 1) and you get handoffs for transient flakes or errors the agent would have fixed on attempt two. Too high (max_retry = 5) and you've already wasted time. We've found two retries with high similarity is the sweet spot for most teams—enough to catch one-off mistakes, not enough to let spinning happen.

Scope creep as a handoff condition

Agents drift. You ask for a button color change and the agent decides the entire component should be refactored to use a different state management pattern. Sometimes that's brilliant insight; more often it's scope creep that should have been discussed first.

Track scope by counting files touched and diff size. If the original plan said "modify Button.tsx" and the agent is now editing Button.tsx, theme.ts, useTheme.hook.ts, and GlobalStyles.tsx, something shifted. Pause and confirm.

We use a simple ratio: if the actual diff touches more than 150% of the files in the original plan, or if any single file diff exceeds 200 lines when the plan estimated under 50, trigger a handoff. The agent explains what expanded and why:

> "The button color is controlled by the theme system, which currently doesn't support runtime palette swaps. I can either: (a) hardcode the new color, bypassing the theme (3-line change), or (b) extend the theme system to support dynamic palettes (affects 4 files, ~180 lines). Which do you prefer?"

This also prevents the classic agent trap of "fixing" unrelated issues it notices along the way. Yes, there's a TODO comment about refactoring the error handling. No, we don't need to do that right now.

Human-in-the-loop as a compliance gate

For regulated environments—fintech, healthcare, anything with SOC 2 controls—handoffs aren't just helpful, they're mandatory. You need a human to review and approve changes to specific files or systems before they merge.

This is less about agent capability and more about audit trails. Goatfied supports explicit approval gates:


# .goatfied/policies.yml

require_approval:

  - path: src/billing/**

    reason: "PCI scope - requires security review"

  - path: migrations/**

    reason: "Schema changes need DBA sign-off"

When the agent plans a change touching any of those paths, it generates the full diff but blocks merge until a human reviews and types /approve with a comment. The approval, the reviewer identity, and the timestamp all go into the commit metadata and audit log.

We've seen teams extend this to test coverage thresholds too: if a change drops coverage below 80% for a critical module, hand off before merging. The agent can still write the code and run the tests, but it doesn't ship until someone confirms the coverage gap is acceptable or writes the missing tests themselves.

The economics of handoff granularity

Every handoff has a cost: it breaks flow, it requires context switching, and it adds latency to the change. But not handing off also has a cost: wasted cycles, broken builds pushed to CI, and eroded trust in the agent's judgment.

The goal isn't to minimize handoffs—it's to make each handoff worth the interruption. A handoff that saves twenty minutes of debugging is great. A handoff that asks "should this variable be named userId or user_id" is noise.

We've found a few filters help:

  • Batch small questions. If the agent has three minor uncertainties in one edit, hand off once with all three questions rather than interrupting three times.
  • Provide defaults. Frame the handoff as "I'll do X unless you say otherwise" rather than blocking until input arrives. Let the human override if they're around; otherwise proceed after a timeout.
  • Learn from past handoffs. If a human consistently approves a certain class of change without modification (e.g., "adding test files never needs review"), update the policy to skip that handoff next time.

Goatfied's agent loop writes every handoff decision and outcome to .goatfied/handoff-log.jsonl. After a month, you can analyze: which handoff rules triggered most often? Which ones led to actual human corrections vs. rubber-stamps? Tune the thresholds based on real data from your team's workflow.

When the agent knows less than it thinks

The most dangerous failure mode isn't the agent asking for help—it's the agent not asking when it should because it's overconfident. This happens when the agent pattern-matches similar-looking code and assumes it understands the semantics.

Classic example: the agent sees a function called deleteUser(userId) and assumes it's safe to call in a cleanup script. Turns out it's a soft delete that triggers a dozen downstream workflows, and calling it outside the normal request lifecycle breaks everything. A human who's worked in that codebase would know; the agent just sees a function signature.

There's no perfect solution here, but you can add friction for dangerous operations. Maintain a list of functions or modules that require extra scrutiny:


// .goatfied/dangerous-apis.ts

export const requiresReview = [

  'deleteUser',

  'processPayment',

  'sendEmailBatch',

  'migrateData'

];

If the agent's diff includes a call to any of those, force a handoff with context: "I'm calling deleteUser() in a background job. Is this safe in that context, or does it need to run inside a request handler?"

Pair this with small, reversible edits. Goatfied's edit step prefers many small diffs over one large refactor, specifically so each change can be validated and, if necessary, handed off independently. A 500-line refactor is hard to review on a phone during a handoff; a 15-line focused change is manageable.

Related posts

Teaching an agent when to stop and ask a human | Goatfied Blog