Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
Show all changes
59 commits
Select commit Hold shift + click to select a range
c48b84e
fix(core): make loop detection result-aware for task_list polls (#9450)
yiliang114 Aug 19, 2026
91256db
fix(core): report result-aware loops to arena
yiliang114 Aug 19, 2026
83d2bbb
fix(core): complete loop attribution plumbing for #9450
yiliang114 Aug 19, 2026
b0db430
test(core): pin result-aware loop guards against silent regressions (…
yiliang114 Aug 20, 2026
2ea0eea
test(core): pin loop_type journal mapping and stateful callId guard (…
yiliang114 Aug 21, 2026
8b5b610
fix(core): keep result-aware loop guards alive for persisted oversize…
yiliang114 Aug 22, 2026
96eeb5c
refactor(core): keep loop-guard stub parsing in lockstep with the stu…
yiliang114 Aug 22, 2026
164a9a8
fix(core): fingerprint the full output in persistence stubs (#9450)
yiliang114 Aug 22, 2026
0b619f5
test(core): cover unwrapped and truncated oversized stub shapes (#9450)
yiliang114 Aug 22, 2026
e1e61d7
Merge remote-tracking branch 'origin/main' into HEAD
yiliang114 Aug 22, 2026
f4e85c7
fix(core): fingerprint the full output in truncated stubs (#9450)
yiliang114 Aug 22, 2026
167b0af
fix(core): fingerprint batch-budget fits for the loop guards (#9450)
yiliang114 Aug 22, 2026
77a4c88
fix(core): make stateful pair counts order-aware (#9450)
yiliang114 Aug 22, 2026
76621a1
fix(core): make the alternating-pattern guard result-aware (#9450)
yiliang114 Aug 22, 2026
b0b2558
Merge remote-tracking branch 'origin/main' into fix/9450-task-list-lo…
yiliang114 Aug 22, 2026
61dd8c5
fix(core): disarm the cap on thawed boards and anchor stub digests (#…
yiliang114 Aug 22, 2026
1ac2a81
fix(core): count deduped provider call ids once in the subagent loop …
yiliang114 Aug 22, 2026
e7085ec
fix(core): surface subagent loop attribution in the FINISH display (#…
yiliang114 Aug 22, 2026
dd2510d
fix(cli): make the daemon loop guard result-aware for task_list polls…
yiliang114 Aug 22, 2026
7cc2bff
fix(core): count deduped provider call ids once in the main-session l…
yiliang114 Aug 22, 2026
20ce795
fix(core): carry the inner stub digest through batch-budget fitting (…
yiliang114 Aug 23, 2026
edb1f30
fix(core): decay abandoned stateful streaks at round-trip boundaries …
yiliang114 Aug 23, 2026
dd309b3
fix(cli): decay abandoned stateful streaks at daemon batch boundaries…
yiliang114 Aug 23, 2026
00aa71b
fix(core): make the alternating-pattern carve-out in-flight aware (#9…
yiliang114 Aug 23, 2026
71630d8
fix(core): keep degenerate batch-budget fits content-dependent (#9450)
yiliang114 Aug 23, 2026
660cc46
fix(core): exclude never-executed synthetic results from the subagent…
yiliang114 Aug 23, 2026
0f8abb8
fix(core): exclude synthetic duplicate responses from the main-sessio…
yiliang114 Aug 23, 2026
a08fac3
fix(cli): skip daemon abandonment decay for batches that execute noth…
yiliang114 Aug 23, 2026
0ca1aa3
fix(core): canonicalize loop-guard fingerprints across the batch-budg…
yiliang114 Aug 23, 2026
da1d1c3
fix(core): unwind suppressed replays and skip decay for requested key…
yiliang114 Aug 23, 2026
395420a
fix(cli): skip daemon batch decay for re-requested stateful keys (#9450)
yiliang114 Aug 23, 2026
b7d8386
fix(core): keep sub-label batch-budget fits content-dependent (#9450)
yiliang114 Aug 23, 2026
e754027
test(core): align the replay-in-streak halt with the suppression unwi…
yiliang114 Aug 23, 2026
95f2447
Merge remote-tracking branch 'origin/main' into fix/9450-task-list-lo…
yiliang114 Aug 24, 2026
b1a8e66
fix(core): make the always-on consecutive guard in-flight-aware for p…
yiliang114 Aug 24, 2026
5746008
fix(core): reset the consecutive streak when its result evidence deca…
yiliang114 Aug 24, 2026
c4db73a
fix(cli): mark suppressed stateful replays for the daemon batch decay…
yiliang114 Aug 24, 2026
a91b6b2
fix(cli): exclude never-executed skipped-output synthetics from the l…
yiliang114 Aug 24, 2026
4201cd7
fix(core): reduce digest-carrying stub shapes the prefix list does no…
yiliang114 Aug 24, 2026
88edb22
fix(cli): unwind the loop-guard reservations for plan-mode sibling sk…
yiliang114 Aug 24, 2026
3ca2fdd
fix(cli): protect the suppressed replay key across the next daemon ba…
yiliang114 Aug 24, 2026
429881f
fix(core): keep the loop attribution on the final agent task card (#9…
yiliang114 Aug 24, 2026
f6199ae
Merge remote-tracking branch 'origin/main' into HEAD
yiliang114 Aug 24, 2026
342ea34
fix(core): return the full digest line from degenerate batch-budget f…
yiliang114 Aug 24, 2026
7f49db3
fix(core): carry stateful streak marks across replay-suppressed round…
yiliang114 Aug 24, 2026
372e4b7
fix(core): keep rejected stateful calls counting toward the consecuti…
yiliang114 Aug 24, 2026
27f12a2
fix(cli): unwind never-executed not_started synthetics before the int…
yiliang114 Aug 24, 2026
9ee8300
fix(core): clear stateful loop-guard trackers on model fallback (#9450)
yiliang114 Aug 25, 2026
23b6f8a
fix(core): keep suppressed request counts when decay keeps the streak…
yiliang114 Aug 25, 2026
c30eadb
fix(core): share one stub grammar between fitText and the loop guards…
yiliang114 Aug 25, 2026
d603092
test(core): realign the replay-in-streak halt pin with the kept suppr…
yiliang114 Aug 25, 2026
9b5089f
fix(cli): carry live stateful streak marks across non-stateful replay…
yiliang114 Aug 25, 2026
ccdb89d
Merge branch 'main' into fix/9450-task-list-loop-false-positive
wenshao Aug 26, 2026
dfadc01
refactor(core): trim PR back to the core result-aware loop-guard fix …
yiliang114 Aug 26, 2026
f34098e
fix(core): count consecutive results for stateful poll loop guards (#…
yiliang114 Aug 26, 2026
4bbf8ea
fix(core): restore the trimmed verification findings for stateful pol…
yiliang114 Aug 26, 2026
fc76bc2
Merge branch 'main' into fix/9450-task-list-loop-false-positive
yiliang114 Aug 28, 2026
2351563
Merge remote-tracking branch 'origin/main' into resolve-9492
yiliang114 Aug 28, 2026
1d515ff
Merge branch 'main' into fix/9450-task-list-loop-false-positive
yiliang114 Aug 30, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 59 additions & 0 deletions packages/core/src/agents/runtime/agent-core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ import {
type ToolCallRequestInfo,
} from '../../core/turn.js';
import { LoopDetectionService } from '../../services/loopDetectionService.js';
import type { LoopType } from '../../telemetry/types.js';
import {
CoreToolScheduler,
type ToolCall,
Expand Down Expand Up @@ -295,6 +296,12 @@ export interface ReasoningLoopResult {
terminateMode: AgentTerminateMode | null;
/** Number of model round-trips completed. */
turnsUsed: number;
/**
* Which loop detector fired, when terminateMode is LOOP_DETECTED (issue
* #9450 — attribution for stops that all render as one generic message
* otherwise). null otherwise.
*/
loopType?: LoopType | null;
}

/**
Expand Down Expand Up @@ -1149,6 +1156,24 @@ export class AgentCore {
terminateMode = AgentTerminateMode.LOOP_DETECTED;
break;
}
// Result-aware loop guards (issue #9450): stateful reads like
// task_list may legitimately repeat with identical arguments while
// the shared task board changes, so the detector must see each
// executed result before the next round re-emits the call.
for (const toolResult of toolCallResult.results) {
Comment thread
yiliang114 marked this conversation as resolved.
Resolved
if (
loopDetector.recordToolResult(
Comment on lines +1233 to +1235

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verified REAL at head 2351563 (read-only code trace; no code executed or changed):

  • Request counting happens at stream time: every batched ToolCallRequest is fed to checkAlwaysOnSafeties (agent-core.ts:1142, deduped only by call id), incrementing toolCallRepetitionCount (loopDetectionService.ts:816).
  • Results are fed only after the whole batch executes: processFunctionCalls (agent-core.ts:1214) runs first, then the recordToolResult loop (agent-core.ts:1233-1235). Nothing is fed mid-round.
  • The exemption requires state.resultsObserved >= toolCallRepetitionCount - 1 (loopDetectionService.ts:846-847), and recordToolResult only increments resultsObserved for the current streak key (:465-466).

Trace: round 1 = 3 identical task_list({}) requests -> counts 1-3 (< TOOL_CALL_LOOP_THRESHOLD=5, :41), then 3 changed boards fed post-batch -> resultsObserved=3, unchangedStreak=0. Round 2: request 4 -> count 4; request 5 -> count 5 >= threshold: expectedResults=4 but resultsObserved=3 (round 2's batch has not executed yet), exemption unreachable -> halt with consecutive_identical_tool_calls despite every recorded board having changed. Any split where the 5th request does not open its round (3+2, 2+3, 1+4) hits this.

This PR is scope-fused and has been non-converging for 19 patrol rounds; recorded as needs-human-decision. No code changes this round. Leaving unresolved.

Comment thread
yiliang114 marked this conversation as resolved.
Resolved
{ name: toolResult.toolName, args: toolResult.args },
toolResult.responseParts,
)
) {
Comment thread
yiliang114 marked this conversation as resolved.
Resolved
terminateMode = AgentTerminateMode.LOOP_DETECTED;
break;
}
}
if (terminateMode === AgentTerminateMode.LOOP_DETECTED) {
break;
}
currentMessages = toolCallResult.messages;

const externalInputs = this.drainExternalInputs(options);
Expand Down Expand Up @@ -1266,6 +1291,9 @@ export class AgentCore {
text: finalText,
terminateMode,
turnsUsed: turnCounter,
...(terminateMode === AgentTerminateMode.LOOP_DETECTED
? { loopType: loopDetector.getLastLoopType() }
: {}),
};
}

Expand Down Expand Up @@ -1555,6 +1583,14 @@ export class AgentCore {
): Promise<{
messages: Content[];
repeatedDuplicateProviderToolCall: boolean;
/** Executed calls with their model-visible results, in call order.
* Consumed by the loop detector for result-aware stateful-read guards
* (issue #9450). */
results: Array<{
toolName: string;
args: Record<string, unknown>;
responseParts: Part[];
}>;
}> {
const responseByCallId = new Map<
string,
Expand Down Expand Up @@ -1608,6 +1644,7 @@ export class AgentCore {
return {
messages: [{ role: 'user', parts: [] }],
repeatedDuplicateProviderToolCall: true,
results: [],
};
}

Expand Down Expand Up @@ -2082,9 +2119,31 @@ export class AgentCore {
timestamp: Date.now(),
});

// Pair each executed call with its model-visible (finalized) result so
// the reasoning loop can feed the loop detector's result-aware guards.
const finalizedByCallId = new Map(
Comment thread
yiliang114 marked this conversation as resolved.
Resolved
Comment thread
yiliang114 marked this conversation as resolved.
Resolved
finalizedResponses.map((response) => [response.callId, response]),
);
const results: Array<{
toolName: string;
args: Record<string, unknown>;
responseParts: Part[];
}> = [];
for (const fc of uniqueFunctionCalls) {
const callId = callIdByFunctionCall.get(fc) ?? fc.id ?? '';
const finalized = finalizedByCallId.get(callId);
Comment thread
yiliang114 marked this conversation as resolved.
Resolved
Comment thread
yiliang114 marked this conversation as resolved.
Resolved
if (!finalized) continue;
Comment thread
yiliang114 marked this conversation as resolved.
Resolved
results.push({
toolName: String(fc.name ?? ''),
args: (fc.args ?? {}) as Record<string, unknown>,
responseParts: finalized.responseParts,
});
Comment thread
yiliang114 marked this conversation as resolved.
Resolved
Comment on lines +2340 to +2344

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Patrol note (automated sweep): this PR has hit the scope fuse — cumulative additions are +1853 (>1500-line cap) and this finding shape has recurred across five-plus review rounds without converging. Per patrol policy no more code is added from the sweep; this thread stays open for a maintainer decision on direction (the digest/fingerprint gaps are real, but each round mints new entrance variants faster than they close).

}

return {
messages: [{ role: 'user', parts: toolResponseParts }],
repeatedDuplicateProviderToolCall: false,
results,
};
}

Expand Down
6 changes: 6 additions & 0 deletions packages/core/src/agents/runtime/agent-events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,12 @@ export interface AgentExternalMessageEvent {
export interface AgentFinishEvent {
subagentId: string;
terminateReason: string;
/**
* Which loop detector fired when terminateReason is LOOP_DETECTED
* (issue #9450), so stops are attributable in journals/telemetry instead
* of collapsing into one generic label.
*/
loopType?: string;
Comment thread
yiliang114 marked this conversation as resolved.
Resolved
Comment thread
yiliang114 marked this conversation as resolved.
Resolved
Comment thread
yiliang114 marked this conversation as resolved.
Resolved
timestamp: number;
rounds?: number;
totalDurationMs?: number;
Expand Down
Loading
Loading