@@ -235,10 +235,12 @@ type Loop struct {
235235 CoverageForbiddenTools []string
236236
237237 // ResultPolicy, if set, scans every tool result for secret leakage,
238- // destructive operations, and network egress. It can block subsequent
239- // writes or require confirmation for the next destructive tool call
240- // (issue #374). Optional — nil preserves legacy behavior.
241- ResultPolicy * permission.ResultPolicyStore
238+ // destructive operations, and network egress (issue #374).
239+ ResultPolicy * permission.ResultPolicy
240+
241+ // Observer, when set, observes tool calls via Observe/LastTrip
242+ // for full fingerprint-based loop detection (issue #377).
243+ Observer * LoopDetector
242244
243245 // RunOverride, if set, replaces the default Run. Used by the
244246 // WebUI v2 chat API (issue #52) so tests can swap in a
@@ -252,12 +254,6 @@ type Loop struct {
252254 // == "poc" (issue #290).
253255 TournamentRunner TournamentRunner
254256
255- // ResultPolicy, if set, scans the string returned by every executed
256- // tool and surfaces warnings/escalations for secret leakage,
257- // destructive confirmations, or network egress markers (issue #374).
258- // Optional — nil preserves exact legacy behavior.
259- ResultPolicy * permission.ResultPolicy
260-
261257 // Frustration, when set, tracks user message patterns for frustration
262258 // signals and appends a system-prompt suffix when detected (issue #271).
263259 // Optional — nil preserves legacy behavior.
@@ -1235,9 +1231,9 @@ func (l *Loop) Run(ctx context.Context, sess *session.Session, prompt string) (*
12351231 // TOOL REFUSED message and skip execute() so the model
12361232 // gets feedback AND the dispatch site never reaches a
12371233 // destructive mutator while the worker is thrashing.
1238- if l .Observer != nil && l .Observer .Enabled () {
1239- if oerr := l .Observer .Observe (tc , "" ); oerr != nil {
1240- trip := l .Observer .LastTrip ()
1234+ if l .LoopDetector != nil && l .LoopDetector .Enabled () {
1235+ if oerr := l .LoopDetector .Observe (tc , "" ); oerr != nil {
1236+ trip := l .LoopDetector .LastTrip ()
12411237 data := map [string ]any {"reason" : "loop.detected" }
12421238 if trip != nil {
12431239 data ["pattern_length" ] = trip .Length
0 commit comments