diff --git a/nanobot/agent/context_governance.py b/nanobot/agent/context_governance.py index ed98cb2c..b2e1249f 100644 --- a/nanobot/agent/context_governance.py +++ b/nanobot/agent/context_governance.py @@ -425,9 +425,14 @@ class ContextGovernor: return system_messages + self._legal_history_tail(kept, non_system) @staticmethod - def _summary_for(message: dict[str, Any]) -> str: + def _tool_result_compaction_message(message: dict[str, Any]) -> str: name = message.get("name", "tool") - return f"[Prior {name} result compacted to fit context; the tool call already completed.]" + return ( + f"Error: The previous {name} result was compacted to fit context because it was too " + "large. Do not repeat the same call unchanged. Retry with a narrower path, query, " + "range, or result limit, use another tool, or tell the user the task cannot fit in " + "the available context." + ) def _legal_history_tail( self, @@ -462,12 +467,12 @@ class ContextGovernor: tool_call_id = msg.get("tool_call_id") if not tool_call_id or str(tool_call_id) not in compacted_tool_call_ids: continue - summary = self._summary_for(msg) - if msg.get("content") == summary: + compaction_message = self._tool_result_compaction_message(msg) + if msg.get("content") == compaction_message: continue if updated is messages: updated = [dict(m) for m in messages] - updated[idx]["content"] = summary + updated[idx]["content"] = compaction_message return updated def _inflight_compaction_candidates( @@ -500,4 +505,4 @@ class ContextGovernor: return primary + fallback def _compact_tool_result_at(self, messages: list[dict[str, Any]], idx: int) -> None: - messages[idx]["content"] = self._summary_for(messages[idx]) + messages[idx]["content"] = self._tool_result_compaction_message(messages[idx]) diff --git a/tests/agent/test_runner_governance.py b/tests/agent/test_runner_governance.py index 7b22740f..7acbb684 100644 --- a/tests/agent/test_runner_governance.py +++ b/tests/agent/test_runner_governance.py @@ -574,7 +574,7 @@ def test_microcompact_overflow_compacts_to_low_watermark(monkeypatch): def test_microcompact_compacts_newest_when_it_alone_overflows(monkeypatch): - """The newest result is preserved only while the request can still fit.""" + """An unfit newest result tells the model to retry narrowly or report the limit.""" provider = MagicMock() provider.generation = SimpleNamespace(max_tokens=0) tools = MagicMock() @@ -611,6 +611,9 @@ def test_microcompact_compacts_newest_when_it_alone_overflows(monkeypatch): tool_msg = next(m for m in result if m.get("role") == "tool") assert "compacted to fit context" in tool_msg["content"] + assert "Do not repeat the same call unchanged" in tool_msg["content"] + assert "Retry with a narrower path, query, range, or result limit" in tool_msg["content"] + assert "tell the user the task cannot fit" in tool_msg["content"] assert compacted_tool_call_ids == {"c0"}