@@ -221,6 +221,8 @@ async def replan(self, magentic_context: MagenticContext) -> Any:
221221 async def create_progress_ledger (self , magentic_context : MagenticContext ):
222222 """
223223 Check for max rounds exceeded and send final message if so, else defer to base.
224+ After base evaluation, prevent premature satisfaction by ensuring all planned
225+ agents have responded before allowing is_request_satisfied=True.
224226
225227 Returns:
226228 Progress ledger object (type depends on agent_framework version)
@@ -256,7 +258,65 @@ async def create_progress_ledger(self, magentic_context: MagenticContext):
256258 return ledger
257259
258260 # Delegate to base for normal progress ledger creation
259- return await super ().create_progress_ledger (magentic_context )
261+ ledger = await super ().create_progress_ledger (magentic_context )
262+
263+ # --- Premature satisfaction guard ---
264+ # If the LLM says the request is satisfied, verify that all planned
265+ # (non-proxy, non-manager) agents have actually responded before allowing
266+ # the workflow to terminate. This addresses the bug where the orchestrator
267+ # marks satisfied=True after a single comprehensive agent response.
268+ if ledger .is_request_satisfied .answer :
269+ uncalled = self ._get_uncalled_agents (magentic_context )
270+ if uncalled :
271+ next_agent = uncalled [0 ]
272+ logger .info (
273+ "Progress ledger marked satisfied but %d agent(s) have not responded yet: %s. "
274+ "Overriding to continue with '%s'." ,
275+ len (uncalled ),
276+ uncalled ,
277+ next_agent ,
278+ )
279+ ledger .is_request_satisfied .answer = False
280+ ledger .is_request_satisfied .reason = (
281+ f"Not all agents have responded yet. Waiting for: { ', ' .join (uncalled )} "
282+ )
283+ ledger .is_progress_being_made .answer = True
284+ ledger .is_progress_being_made .reason = "Continuing to consult remaining agents"
285+ ledger .next_speaker .answer = next_agent
286+ ledger .next_speaker .reason = f"{ next_agent } has not yet been consulted"
287+ # Always override instruction with task-relevant prompt so that
288+ # data agents (Azure AI Search, RAG) execute meaningful queries
289+ # instead of receiving a stale finalization instruction.
290+ task_text = getattr (magentic_context .task , "text" , str (magentic_context .task ))
291+ ledger .instruction_or_question .answer = (
292+ f"Using your available tools and data sources, provide your response for the following task: { task_text } "
293+ )
294+ ledger .instruction_or_question .reason = (
295+ f"Routing to { next_agent } who has not yet contributed"
296+ )
297+
298+ return ledger
299+
300+ @staticmethod
301+ def _get_uncalled_agents (magentic_context : MagenticContext ) -> list [str ]:
302+ """Return agent names from participant_descriptions that have not yet
303+ authored a message in the chat_history (excluding ProxyAgent and the
304+ MagenticManager)."""
305+ skip_names = {"ProxyAgent" , "MagenticManager" , "magentic_manager" }
306+
307+ all_agents = [
308+ name for name in magentic_context .participant_descriptions
309+ if name not in skip_names
310+ ]
311+
312+ # Collect author names that appear in chat_history
313+ responded = set ()
314+ for msg in magentic_context .chat_history :
315+ author = getattr (msg , "author_name" , None )
316+ if author :
317+ responded .add (author )
318+
319+ return [name for name in all_agents if name not in responded ]
260320
261321 async def _wait_for_user_approval (
262322 self , m_plan_id : Optional [str ] = None
0 commit comments