Skip to content

fix(observer): emit <skip_summary/> on skip instead of empty response#3068

Open
funrun11 wants to merge 1 commit into
thedotmack:mainfrom
funrun11:fix/sdk-observer-skip-summary-contract
Open

fix(observer): emit <skip_summary/> on skip instead of empty response#3068
funrun11 wants to merge 1 commit into
thedotmack:mainfrom
funrun11:fix/sdk-observer-skip-summary-contract

Conversation

@funrun11

Copy link
Copy Markdown

Problem

On quiet / low-signal sessions the SDK observer poison-loops and stores zero observations:

[PARSER]  SDK returned non-XML idle response — ignoring queued batch {consecutiveInvalidOutputs=1}
[PARSER]  SDK returned non-XML prose response — ignoring queued batch {consecutiveInvalidOutputs=2}
[PARSER]  SDK returned non-XML idle response — ignoring queued batch {consecutiveInvalidOutputs=3}
[ERROR]   SDK session poisoned — killing and respawning, pending messages preserved

Reproduced on 13.8.0 and 13.8.1 (Linux + Windows). Worker restart and version bump do not fix it.

Root cause — contract drift

src/sdk/prompts.ts instructs the observer:

Return either one or more <observation>...</observation> blocks, or an empty response if this tool use should be skipped.

and several mode skip_guidance fields repeat "return an empty response only" / "No output necessary if skipping."

But src/sdk/output-classifier.ts classifies an empty/non-XML reply as idle/proseinvalid output, and ResponseProcessor.ts poisons the session after 3 consecutive invalids. So a correct skip is scored as an invalid output and walks the session straight to the poison threshold.

Meanwhile the parser and the server-side providers already standardize on a self-closing <skip_summary/> tag:

  • src/sdk/parser.ts&lt;skip_summary(?:\s+reason="([^"]*)")?\s*/&gt;
  • src/sdk/output-classifier.ts — treats <skip_summary/> as valid xml
  • src/server/generation/providers/shared/prompt-builder.ts"return a single self-closing <skip_summary />"

Fix

Align the SDK observation path and the mode skip_guidance with that existing contract: on skip, emit <skip_summary reason="..."/> (a recognized, valid output) instead of an empty response. No parser/threshold changes.

  • src/sdk/prompts.ts — central skip instruction
  • plugin/modes/code.json, meme-tokens.json, law-study.json, law-study--chill.json, email-investigation.jsonskip_guidance

Testing

Patched a local install, restarted the worker, ran a quiet file-reading session. Before: poison loop, 0 observations. After: skips emit <skip_summary reason="..."/> (stored as valid, consecutiveInvalidOutputs stays 0), substantive reads distill into <observation> blocks and sync to chroma. No poison events.

Notes

Refs #3032, #3042. Complementary to the parser-side guards in #3059 / #2943 — this fixes the contract at the source (don't emit the empty reply) rather than tolerating it downstream. The two approaches compose.

The SDK observation prompt (src/sdk/prompts.ts) tells the observer to
return an empty response when a tool use should be skipped, and several
mode skip_guidance fields repeat "return an empty response" / "no output
necessary". But the output classifier treats a non-XML / empty reply as
an invalid output (idle/prose). Three consecutive invalid outputs poison
the SDK session, which is killed and respawned — a self-sustaining loop
on quiet or low-signal sessions that drops all captured work and stores
zero observations.

The parser and the server-side providers (Gemini/OpenRouter/Claude)
already standardize on a self-closing <skip_summary/> tag
(src/sdk/parser.ts, src/sdk/output-classifier.ts,
src/server/generation/providers/shared/prompt-builder.ts). This aligns
the SDK observation path and the mode skip_guidance with that same
contract, so a skip is emitted as a recognized, valid output instead of
an invalid one that trips the poison threshold.

Refs thedotmack#3032, thedotmack#3042. Complementary to the parser-side guards in thedotmack#3059 / thedotmack#2943
(fix the contract at the source rather than tolerating the empty reply).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@greptile-apps

greptile-apps Bot commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR aligns observer skip output with the existing XML skip contract. The main changes are:

  • SDK observation prompts now request a single <skip_summary reason="brief reason" /> tag for skipped tool uses.
  • Code, email investigation, law study, law study chill, and meme token modes now tell the observer not to return empty output on skips.
  • The changed guidance matches the parser and output classifier paths that already treat <skip_summary /> as valid XML.

Confidence Score: 5/5

Safe to merge: the changes are limited to prompt guidance and mode configuration text that now matches the existing parser/classifier contract.

The modified files consistently replace empty skip-output instructions with the recognized skip tag, and no runtime parsing or threshold logic was changed.

T-Rex T-Rex Logs

What T-Rex did

  • T-Rex reviewed the pre-change prompts.ts state around line 148 describing empty response or no-output skip behavior.
  • T-Rex confirmed the post-change state that prompts.ts now uses a single self-closing <skip_summary reason="brief reason" /> tag and that all changed modes have hasSkipSummaryBriefReason: true.
  • T-Rex verified the parser/classifier results showing that a sample <skip_summary reason="brief reason" /> produced valid:true and skipped:true with classifyResult: xml, whereas an empty input produced valid:false and classifyResult: idle.

View all artifacts

T-Rex Ran code and verified through T-Rex

Reviews (1): Last reviewed commit: "fix(observer): emit <skip_summary/> on s..." | Re-trigger Greptile

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant