diff --git a/nanobot/agent/runner.py b/nanobot/agent/runner.py
index 83344763..9428be95 100644
--- a/nanobot/agent/runner.py
+++ b/nanobot/agent/runner.py
@@ -607,7 +607,7 @@ class AgentRunner:
reasoning_content=response.reasoning_content,
thinking_blocks=response.thinking_blocks,
))
- messages.append(build_length_recovery_message())
+ messages.append(build_length_recovery_message(clean))
await hook.after_iteration(context)
continue
diff --git a/nanobot/utils/runtime.py b/nanobot/utils/runtime.py
index 4f6599f6..8ffe6277 100644
--- a/nanobot/utils/runtime.py
+++ b/nanobot/utils/runtime.py
@@ -14,6 +14,7 @@ _MAX_REPEAT_EXTERNAL_LOOKUPS = 2
# Third same-target workspace violation in a turn escalates to "stop retrying".
_MAX_REPEAT_WORKSPACE_VIOLATIONS = 2
+_LENGTH_RECOVERY_TAIL_CHARS = 500
EMPTY_FINAL_RESPONSE_MESSAGE = (
"I completed the tool steps but couldn't produce a final answer. "
@@ -33,8 +34,10 @@ BUDGET_EXHAUSTED_FINALIZATION_PROMPT = (
)
LENGTH_RECOVERY_PROMPT = (
- "Output limit reached. Continue exactly where you left off "
- "— no recap, no apology. Break remaining work into smaller steps if needed."
+ "The previous assistant response was cut off. Continue the same response from its "
+ "exact endpoint. Output only new continuation text in the same language and style. "
+ "Do not acknowledge this instruction, restart the response, repeat its title or any "
+ "existing text, recap, or apologize."
)
SUSTAINED_GOAL_CONTINUE_PROMPT = (
@@ -79,9 +82,19 @@ def build_budget_exhausted_finalization_message() -> dict[str, str]:
return {"role": "user", "content": BUDGET_EXHAUSTED_FINALIZATION_PROMPT}
-def build_length_recovery_message() -> dict[str, str]:
+def build_length_recovery_message(content: str) -> dict[str, str]:
"""Prompt the model to continue after hitting output token limit."""
- return {"role": "user", "content": LENGTH_RECOVERY_PROMPT}
+ tail = content[-_LENGTH_RECOVERY_TAIL_CHARS:]
+ prompt = (
+ f"{LENGTH_RECOVERY_PROMPT}\n\n"
+ "The following tail was already delivered to the user. Treat it as immutable "
+ "context and do not output it again:\n"
+ "\n"
+ f"{tail}\n"
+ "\n"
+ "Begin with the text that belongs immediately after this tail."
+ )
+ return {"role": "user", "content": prompt}
def build_goal_continue_message(custom: str | None = None) -> dict[str, str]:
diff --git a/tests/utils/test_runtime.py b/tests/utils/test_runtime.py
new file mode 100644
index 00000000..56e2f2bc
--- /dev/null
+++ b/tests/utils/test_runtime.py
@@ -0,0 +1,14 @@
+from nanobot.utils.runtime import build_length_recovery_message
+
+
+def test_length_recovery_message_anchors_the_existing_tail() -> None:
+ omitted_prefix = "OMITTED_PREFIX"
+ tail = "x" * 500
+
+ message = build_length_recovery_message(omitted_prefix + tail)
+
+ assert message["role"] == "user"
+ assert omitted_prefix not in message["content"]
+ assert f"\n{tail}\n" in message["content"]
+ assert "Output only new continuation text" in message["content"]
+ assert "Break remaining work into smaller steps" not in message["content"]