fix(parser): stop warning on normal observation responses (#2074)

parseSummary runs on every agent response, not just summary turns. When the
turn is a normal observation, the LLM correctly emits <observation> and no
<summary> — but the fallthrough branch from #1345 treated this as prompt
misbehavior and logged "prompt conditioning may need strengthening" every
time. That assumption stopped holding after #1633 refactored the caller to
always invoke parseSummary with a coerceFromObservation flag.

Gate the whole observation-on-summary path on coerceFromObservation. On a
real summary turn, coercion still runs and logs the legitimate "coercion
failed" warning when the response has no usable content. On an observation
turn, parseSummary returns null silently, which is the correct behavior.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Alex Newman
2026-04-19 16:30:05 -07:00
committed by GitHub
parent e6e5751ef5
commit 2337997c48
4 changed files with 254 additions and 253 deletions
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+12 -11
View File
@@ -137,22 +137,23 @@ export function parseSummary(text: string, sessionId?: number, coerceFromObserva
const summaryMatch = summaryRegex.exec(text); const summaryMatch = summaryRegex.exec(text);
if (!summaryMatch) { if (!summaryMatch) {
// When the LLM returns <observation> tags instead of <summary> tags, // When the LLM returns <observation> tags instead of <summary> tags on a
// coerce the observation content into summary fields rather than discarding it. // summary turn, coerce the observation content into summary fields rather
// This breaks the infinite retry loop described in #1633: without coercion, // than discarding it. This breaks the infinite retry loop described in
// the summary is silently dropped, the session completes without a summary, // #1633: without coercion, the summary is silently dropped, the session
// a new session is spawned with an ever-growing prompt, and the cycle repeats. // completes without a summary, a new session is spawned with an ever-growing
// Only coerce when explicitly requested (i.e., when a summarize message was sent). // prompt, and the cycle repeats.
if (/<observation>/.test(text)) { //
if (coerceFromObservation) { // parseSummary is called on every response (see ResponseProcessor), not just
// summary turns — so the absence of <summary> in an observation response is
// expected, not a prompt-conditioning failure. Only act when the caller
// actually expected a summary (coerceFromObservation=true).
if (coerceFromObservation && /<observation>/.test(text)) {
const coerced = coerceObservationToSummary(text, sessionId); const coerced = coerceObservationToSummary(text, sessionId);
if (coerced) { if (coerced) {
return coerced; return coerced;
} }
logger.warn('PARSER', 'Summary response contained <observation> tags instead of <summary> — coercion failed, no usable content', { sessionId }); logger.warn('PARSER', 'Summary response contained <observation> tags instead of <summary> — coercion failed, no usable content', { sessionId });
} else {
logger.warn('PARSER', 'Summary response contained <observation> tags instead of <summary> — prompt conditioning may need strengthening', { sessionId });
}
} }
return null; return null;
} }