compactIfNeeded estimates the token impact of tool results added since messageCountBefore and triggers proactive compaction when the estimated total exceeds 90% of the context window. This prevents sending an oversized request on the next iteration.
( ctx context.Context, sess *session.Session, a *agent.Agent, contextLimit int64, messageCountBefore int, events EventSink, )
| 1094 | // total exceeds 90% of the context window. This prevents sending an |
| 1095 | // oversized request on the next iteration. |
| 1096 | func (r *LocalRuntime) compactIfNeeded( |
| 1097 | ctx context.Context, |
| 1098 | sess *session.Session, |
| 1099 | a *agent.Agent, |
| 1100 | contextLimit int64, |
| 1101 | messageCountBefore int, |
| 1102 | events EventSink, |
| 1103 | ) { |
| 1104 | // contextLimit is the effective budget (primary window, capped to a smaller |
| 1105 | // dedicated compaction model's window when configured) computed by |
| 1106 | // runStreamLoop, so this site fires consistently with the pre-turn trigger. |
| 1107 | if !r.sessionCompaction || contextLimit <= 0 { |
| 1108 | return |
| 1109 | } |
| 1110 | |
| 1111 | // Estimate only over the session's own new messages: sub-session |
| 1112 | // content recorded during tool calls (transfer_task and friends) |
| 1113 | // never enters this session's prompt, so counting it here would |
| 1114 | // attribute phantom tokens to a small parent conversation and |
| 1115 | // trigger a compaction that wipes it (see issue #2871). |
| 1116 | newMessages := sess.OwnMessages()[messageCountBefore:] |
| 1117 | var addedTokens int64 |
| 1118 | for _, msg := range newMessages { |
| 1119 | addedTokens += compaction.EstimateMessageTokens(&msg.Message) |
| 1120 | } |
| 1121 | |
| 1122 | if !compaction.ShouldCompact(sess.InputTokens, sess.OutputTokens, addedTokens, contextLimit) { |
| 1123 | return |
| 1124 | } |
| 1125 | |
| 1126 | slog.InfoContext(ctx, "Proactive compaction: tool results pushed estimated context past 90%% threshold", |
| 1127 | "agent", a.Name(), |
| 1128 | "input_tokens", sess.InputTokens, |
| 1129 | "output_tokens", sess.OutputTokens, |
| 1130 | "added_estimated_tokens", addedTokens, |
| 1131 | "estimated_total", sess.InputTokens+sess.OutputTokens+addedTokens, |
| 1132 | "context_limit", contextLimit, |
| 1133 | ) |
| 1134 | r.compactWithReason(ctx, sess, "", compactionReasonThreshold, events) |
| 1135 | } |
| 1136 | |
| 1137 | // getTools executes tool retrieval with automatic OAuth handling. |
| 1138 | // emitLifecycleEvents controls whether MCPInitStarted/Finished are emitted; |