Orkas Orkas
Home Blog Architecture
Architecture

Loop Detection Is Not Stall Detection: Catching Agents That Spin Without Repeating

Orkas has three loop guards. None of them catch a capable model that stalls, because all three ask whether it is repeating itself — and a stuck model never does. Here is the blind spot, the output-side definition of progress we borrowed from BEACON, and what we plan to measure before changing anything.

This is the first of a series of design notes on long-horizon agents, prompted by a close read of BEACON (Zhejiang University, arXiv:2605.06078).

For an agent to hold up over a long task, we think two things have to be right:

  • It keeps making progress toward the goal. That breaks into context compaction (what is safe to forget), milestone design (how you know a step is really done), and stall prevention (when it gets stuck, something has to notice).
  • It can reflect and improve.

This post covers stall prevention, because that is the one that cost us the most.

The short version An agent that spins without repeating still needs catching Stall detection ships in Orkas, so a long run tells you it is stuck instead of quietly spending the rest of your budget.
Download Orkas — free

The symptom

We had user reports, and saw it internally too: some very long tasks stop moving forward partway through.

Look at the logs and the agent is busy — reading files, searching, running commands, never idle. But half an hour later nothing has advanced.

Our first explanation was context loss: compaction dropped the record that a path had already been tried, so the agent went and tried it again. Reading the code and the logs together, that turned out to be only half the story.

We already had guards — three tiers of them

TierCriterionThresholds
Exact repeatTool name + canonicalized args, byte-identicalLOOP_WARN=3 warn / LOOP_HARD=5 force-stop
Near-duplicateIdentical except volatile id / timestamp fieldsNEAR_DUP_LOOP_WARN=6 / NEAR_DUP_LOOP_HARD=12
Spin convergence≥2 compactions and ≥75% of the tool-loop budget burnedSPIN_CONVERGENCE_MIN_COMPACTIONS=2
SPIN_CONVERGENCE_TOOL_LOOP_RATIO=0.75

The comment on that third tier reads, verbatim: Compound "may be spinning after context loss" signal. Someone had already seen this coming. So the problem was never that nothing was built — it was that what we built could not catch it.

The blind spot: all three are input-side detectors

The signature every tier keys on is tool name + canonicalized args. That answers exactly one question: are you doing the same thing twice?

But a capable model that is stuck does not repeat calls. It does this:

read file A → grep X → re-read A with a different line range
  → run a slightly different command → read file B → grep again …

Every call has a distinct signature. All three tiers stay silent. And across that entire stretch, the number of verifiable state changes is zero: no file actually rewritten, no command producing a new result, nothing irreversible happening at all.

Loop detection is not stall detection. The first looks at inputs. The second has to look at outputs.

The paper supplies exactly that output-side definition

BEACON's in-segment reward:

r_t = R_ms · γ^(t_k − t)    if the segment ends in a milestone
    = 0                     otherwise

A segment that does not terminate in a milestone earns exactly zero, regardless of how many actions it contained. Action count never enters the formula. That is the cleanest formalization of busy ≠ progressing we have seen.

The baseline layer is harsher still. The baseline is the group-average per-step return, so a segment that took 8 steps where the group averaged 5 has its entire advantage skewed negative, and the penalty scales with the overshoot. Spinning is not merely unrewarded; it is actively and proportionally penalized.

Figure 8 of the paper shows a failed trajectory where the last two actions receive an identical −2.20. That tail — the stretch after the last milestone that never reaches another one — is the mathematical shape of spinning. And it is common: trajectories that complete at least one subgoal but fail the task hold steady at 39–47% of samples.

One ablation rules out step-count triggers

PartitioningScorevs. baseline (72.8)
Random 5-way split74.2+1.4
Real milestones91.4+17.2

Splitting by arbitrary step counts is worth almost nothing. Splitting by real structure is worth a lot.

Now look back at our third-tier criterion — 75% of the tool-loop budget consumed. That is an arbitrary-step-count trigger. It asks how much you have burned, not what you have achieved. The right shape is:

✗  if steps > N                              → intervene
✓  if steps > N AND zero verified milestones → intervene

The second will not misfire on a legitimately long task that is progressing, because such a task hits milestones along the way. The first will.

There is also a positive feedback loop

Put the constants side by side. Compaction triggers at 82% of the context window. Spin convergence requires two compactions plus 75% of the loop budget before it fires.

spin → context fills → compaction fires → durable state summarized away
     → re-derive what was lost → spin more

The spin detector infers spinning by observing that compaction happened repeatedly — but compaction is the very step that causes the amnesia. It is detecting a downstream symptom, and it has to wait for the loop to go around twice.

Worse, its intervention is to nudge the model to re-anchor on its durable state. If the durable state is precisely what was compacted away, there is nothing left to re-anchor on. This is a prompt-layer patch for a state-layer problem.

What we are adding: output-side stall detection

A fourth tier, built from two counters. Both are derived mechanically from tool observations the host already records; neither needs model judgment.

Steps since the last verified milestone. The simplest possible progress metric, used in the compound criterion above rather than alone.

Deduplicated new state changes. The more useful of the two:

  • A file read whose content hash matches a previous read is not new information — re-reading the same file at a different line range leaves the hash unchanged.
  • A command whose name, exit code, and output hash all match a previous run is not new information.
  • A write where the after-hash equals the before-hash means nothing was actually written.

This counter targets exactly the case signature matching misses: every action different, information gain zero. The criterion is mechanical and needs no semantic understanding.

Then the pressure should be continuous rather than a single nudge:

surface the counter in context so the model can see it
  → force a plan revision (admit this path is dead)
    → ask the user
      → abort, but preserve the milestones already achieved

That last rung matters. If the run is going to stop, it should stop holding what it earned — which is the same 39–47% of partial progress the paper measured being thrown away.

What the paper does not give us

BEACON is a training method. It shapes gradients so the trained policy is less prone to wandering, but it has no runtime detection or intervention mechanism of its own. It supplies a definition of progress, not a controller. Thresholds, escalation ladders, and abort conditions are ours to design.

Its per-step baseline also needs a group-average segment length as reference. In production a given user task usually runs exactly once, so there is no group. The best available substitute is historical statistics over similar tasks, which is considerably noisier and carries none of the paper's variance-isolation guarantee.

The first move is to measure, not to fix

Before changing any decision logic, we want to instrument and answer a question we currently cannot: of the stalls happening in production, how many are amnesia-type and how many are no-gradient-type?

  • Mostly accompanied by information gain at zero while call signatures all differ → no-gradient-type. The existing three tiers structurally cannot catch it, and output-side detection is the fix.
  • Mostly accompanied by re-reading content that was already compacted away → amnesia-type. What needs fixing is what compaction preserves.

Those two conclusions call for completely different investments. Measuring first is cheaper than designing first, and the instrumentation is nearly free because the observations already exist.

Everything above depends on one notion this note has been leaning on without defining: a verified milestone. The next note is about that. Why a step being declared complete is not evidence that it is, which two halves already exist in the product without ever being connected, and one criterion the paper does not have: look at irreversibility, not importance.