Repository navigation
fix(core): make loop detection result-aware for task_list polls #9492
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 4 commits
c48b84e
91256db
83d2bbb
b0db430
2ea0eea
8b5b610
96eeb5c
164a9a8
0b619f5
e1e61d7
f4e85c7
167b0af
77a4c88
76621a1
b0b2558
61dd8c5
1ac2a81
e7085ec
dd2510d
7cc2bff
20ce795
edb1f30
dd309b3
00aa71b
71630d8
660cc46
0f8abb8
a08fac3
0ca1aa3
da1d1c3
395420a
b7d8386
e754027
95f2447
b1a8e66
5746008
c4db73a
a91b6b2
4201cd7
88edb22
3ca2fdd
429881f
f6199ae
342ea34
7f49db3
372e4b7
27f12a2
9ee8300
23b6f8a
c30eadb
d603092
9b5089f
ccdb89d
dfadc01
f34098e
4bbf8ea
fc76bc2
2351563
1d515ff
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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, | ||
|
|
@@ -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; | ||
| } | ||
|
|
||
| /** | ||
|
|
@@ -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) { | ||
| if ( | ||
| loopDetector.recordToolResult( | ||
|
Comment on lines
+1233
to
+1235
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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):
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.
yiliang114 marked this conversation as resolved.
Resolved
|
||
| { name: toolResult.toolName, args: toolResult.args }, | ||
| toolResult.responseParts, | ||
| ) | ||
| ) { | ||
|
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); | ||
|
|
@@ -1266,6 +1291,9 @@ export class AgentCore { | |
| text: finalText, | ||
| terminateMode, | ||
| turnsUsed: turnCounter, | ||
| ...(terminateMode === AgentTerminateMode.LOOP_DETECTED | ||
| ? { loopType: loopDetector.getLastLoopType() } | ||
| : {}), | ||
| }; | ||
| } | ||
|
|
||
|
|
@@ -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, | ||
|
|
@@ -1608,6 +1644,7 @@ export class AgentCore { | |
| return { | ||
| messages: [{ role: 'user', parts: [] }], | ||
| repeatedDuplicateProviderToolCall: true, | ||
| results: [], | ||
| }; | ||
| } | ||
|
|
||
|
|
@@ -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( | ||
|
yiliang114 marked this conversation as resolved.
Resolved
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); | ||
|
yiliang114 marked this conversation as resolved.
Resolved
yiliang114 marked this conversation as resolved.
Resolved
|
||
| if (!finalized) continue; | ||
|
yiliang114 marked this conversation as resolved.
Resolved
|
||
| results.push({ | ||
| toolName: String(fc.name ?? ''), | ||
| args: (fc.args ?? {}) as Record<string, unknown>, | ||
| responseParts: finalized.responseParts, | ||
| }); | ||
|
yiliang114 marked this conversation as resolved.
Resolved
Comment on lines
+2340
to
+2344
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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, | ||
| }; | ||
| } | ||
|
|
||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.