{"record":{"id":"8cbc4fabee600b50","repo":"agentscope-ai/agentscope","slug":"agent-did-not-produce-a-final-message","errorCode":null,"errorMessage":"Agent did not produce a final message.","messagePattern":"Agent did not produce a final message\\.","errorType":"exception","errorClass":"RuntimeError","httpStatus":null,"severity":"error","filePath":"src/agentscope/agent/_agent.py","lineNumber":346,"sourceCode":"                  continue from the current state).\n            structured_schema (`Type[BaseModel] | None`, optional):\n                The Pydantic model class that the reply's structured output\n                must conform to, with the validated result carried on the\n                final message's ``structured_output`` attribute as a dict.\n\n        Returns:\n            `Msg`:\n                A final reply message.\n        \"\"\"\n        final_msg: Msg | None = None\n        async for evt_or_msg in self._reply(\n            inputs=inputs,\n            structured_schema=structured_schema,\n        ):\n            if isinstance(evt_or_msg, Msg):\n                final_msg = evt_or_msg\n        if final_msg is None:\n            raise RuntimeError(\"Agent did not produce a final message.\")\n        return final_msg\n\n    async def observe(self, msgs: Msg | list[Msg] | None = None) -> None:\n        \"\"\"Receive external observation message(s) and save them into\n        context.\"\"\"\n        await self._handle_incoming_messages(msgs)\n\n    async def compress_context(\n        self,\n        context_config: ContextConfig | None = None,\n        instructions: HintBlock | None = None,\n    ) -> None:\n        \"\"\"Compress the agent's context if the token count exceeds the\n        threshold.\n\n        Args:\n            context_config (`ContextConfig | None`, optional):\n                If provided, compress the context with the given context","sourceCodeStart":328,"sourceCodeEnd":364,"githubUrl":"https://github.com/agentscope-ai/agentscope/blob/e90f1c7592896cc95f6e5ee506194f533378247d/src/agentscope/agent/_agent.py#L328-L364","documentation":"Agent.reply() consumes the reasoning/acting event stream and records the last Msg produced. If the whole loop finishes without the agent ever emitting a final Msg (final_msg stays None), reply raises this RuntimeError. It signals the agent's loop terminated abnormally — usually max_iters exhausted during reasoning or an empty model response — rather than a normal API misuse.","triggerScenarios":"Calling await agent.reply(...) (directly or via ask) when the agent hits its iteration limit while still in reasoning mode, or the model returns empty/no-message responses repeatedly (seen in tests like test_max_iters_counts_reasoning_acting_round_once and test_thinking_only_response_continues_reasoning).","commonSituations":"max_iters set too low for chains that need many reasoning rounds; a thinking-only model response loop that never produces a final answer; malformed model outputs causing rounds to be skipped; token limits truncating responses before content is produced.","solutions":["Increase max_iters so the agent can finish reasoning and produce a final message","Inspect the conversation context before the failure to see whether the model is emitting only thinking content, and if so adjust the model/prompt to require a final answer","Wrap reply() in try/except RuntimeError and retry with a nudge message (e.g. 'Please provide your final answer')","If using a custom subagent, ensure it actually yields a Msg from its reasoning step"],"exampleFix":"# before\nmsg = await agent.reply(inputs)\n\n# after\ntry:\n    msg = await agent.reply(inputs)\nexcept RuntimeError:\n    await agent.observe(Msg(\"user\", \"Please give your final answer now.\", role=\"user\"))\n    msg = await agent.reply()\n\n# or preemptively: agent = ReActAgent(..., max_iters=20)","handlingStrategy":"try-catch","validationCode":"if agent._iters >= agent.max_iters and not agent.state.final_message:\n    # about to exhaust the budget; raise max_iters proactively\n    agent.max_iters += 10","typeGuard":"null","tryCatchPattern":"try:\n    final = await agent.reply(inputs)\nexcept RuntimeError as e:\n    if \"did not produce a final message\" in str(e):\n        await agent.observe(\n            Msg(\"user\", \"Please provide your final answer now.\", role=\"user\")\n        )\n        final = await agent.reply()\n    else:\n        raise","preventionTips":["Set max_iters generously for multi-step chains (reasoning + tool calls each consume rounds)","Watch for thinking-only responses; nudge the model to emit a final answer explicitly in the prompt","Wrap reply() calls in a retry-with-nudge pattern in production agents","Log the context right before failure to diagnose why no Msg was produced"],"tags":["agent","runtimeerror","max-iters","reasoning-loop","reply"],"backgroundTag":"agent-loop-no-final-message","analyzedSha":"e90f1c7592896cc95f6e5ee506194f533378247d","analyzedAt":"2026-08-28T18:24:12.087Z","schemaVersion":2},"datasetVersion":"2026-08-28T21:17:43.275Z"}