{"record":{"id":"80b90cb08a7f94e2","repo":"alibaba/spring-ai-alibaba","slug":"cannot-find-safe-cutoff-point-for-summarization","errorCode":null,"errorMessage":"Cannot find safe cutoff point for summarization","messagePattern":"Cannot find safe cutoff point for summarization","errorType":"console","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"spring-ai-alibaba-agent-framework/src/main/java/com/alibaba/cloud/ai/graph/agent/hook/summarization/SummarizationHook.java","lineNumber":120,"sourceCode":"\t@Override\n\tpublic AgentCommand beforeModel(List<Message> previousMessages, RunnableConfig config) {\n\t\tif (maxTokensBeforeSummary == null) {\n\t\t\treturn new AgentCommand(previousMessages);\n\t\t}\n\n\t\tint totalTokens = tokenCounter.countTokens(previousMessages);\n\n\t\tif (totalTokens < maxTokensBeforeSummary) {\n\t\t\treturn new AgentCommand(previousMessages);\n\t\t}\n\n\t\tlog.info(\"Token count {} exceeds threshold {}, triggering summarization\",\n\t\t\t\ttotalTokens, maxTokensBeforeSummary);\n\n\t\tint cutoffIndex = findSafeCutoff(previousMessages);\n\n\t\tif (cutoffIndex <= 0) {\n\t\t\tlog.warn(\"Cannot find safe cutoff point for summarization\");\n\t\t\treturn new AgentCommand(previousMessages);\n\t\t}\n\n\t\tUserMessage firstUserMessage = null;\n\t\tif (keepFirstUserMessage) {\n\t\t\tfor (Message msg : previousMessages) {\n\t\t\t\tif (msg instanceof UserMessage) {\n\t\t\t\t\tfirstUserMessage = (UserMessage) msg;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tList<Message> toSummarize = new ArrayList<>();\n\t\tfor (int i = 0; i < cutoffIndex; i++) {\n\t\t\tMessage msg = previousMessages.get(i);\n\t\t\tif (msg != firstUserMessage) {\n\t\t\t\ttoSummarize.add(msg);","sourceCodeStart":102,"sourceCodeEnd":138,"githubUrl":"https://github.com/alibaba/spring-ai-alibaba/blob/f82da0b50f35744c13968191be2b1cd2452ef550/spring-ai-alibaba-agent-framework/src/main/java/com/alibaba/cloud/ai/graph/agent/hook/summarization/SummarizationHook.java#L102-L138","documentation":"SummarizationHook.beforeModel triggers summarization when token count exceeds maxTokensBeforeSummary, then calls findSafeCutoff() to find an index where the message list can be split (e.g. a boundary that keeps tool-call/tool-result pairs intact). If cutoffIndex <= 0 no safe boundary exists, so it logs this warning and returns the previous messages unchanged — summarization is skipped and the oversized context is passed to the model as-is.","triggerScenarios":"History exceeds the token threshold but is composed such that no safe cutoff exists — e.g. the conversation is almost entirely one giant tool-call/tool-result sequence, or messages start mid tool-interaction so cutting anywhere would orphan a tool result.","commonSituations":"A single very long tool response dominating the history; maxTokensBeforeSummary set so low that the first turn already exceeds it; aggressive tool output (web pages, file dumps) early in conversation; keepFirstUserMessage logic combined with a tiny history.","solutions":["Raise maxTokensBeforeSummary so summarization triggers only when there is meaningful earlier history to cut","Ensure tool results are truncated/compacted before entering state so findSafeCutoff can locate a boundary","Increase the minimum retained messages window so the cutoff search has candidates","As a guard, also cap total input tokens at the model level (context compaction / windowing) since this warning means no summarization happened","Inspect findSafeCutoff rules and adjust the boundary policy if your message patterns never satisfy it"],"exampleFix":"// before\nSummarizationHook hook = SummarizationHook.builder()\n    .maxTokensBeforeSummary(2000) // too low: cutoff search fails immediately\n    .build();\n// after\nSummarizationHook hook = SummarizationHook.builder()\n    .maxTokensBeforeSummary(20000)\n    .keepFirstUserMessage(true)\n    .build();","handlingStrategy":"fallback","validationCode":"int approxTokens = messages.stream().mapToInt(m -> m.getText().length() / 4).sum();\nboolean summarizable = approxTokens > maxTokensBeforeSummary && approxTokens < hardModelLimit;","typeGuard":null,"tryCatchPattern":"AgentCommand cmd = hook.beforeModel(state, config).join();\nif (cmd.getMessages().size() >= state.messages().size()) { /* summarization skipped; apply manual compaction */ }","preventionTips":["Keep maxTokensBeforeSummary comfortably below the model context limit and above one turn's size","Truncate large tool outputs before they enter state so a safe cutoff exists","Enable context compaction/editing alongside summarization","Monitor for this warning in production — it means context is growing unbounded"],"tags":["summarization","context-window","tokens","hook"],"backgroundTag":"context-window-limit","analyzedSha":"f82da0b50f35744c13968191be2b1cd2452ef550","analyzedAt":"2026-09-09T15:32:42.421Z","contentChangedAt":"2026-09-09T15:32:42.421Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}