Skip to content

Commit 6caef14

Browse files
Customize the ledger to not to skip agents
1 parent 00bd39b commit 6caef14

File tree

1 file changed

+61
-1
lines changed

1 file changed

+61
-1
lines changed

src/backend/v4/orchestration/human_approval_manager.py

Lines changed: 61 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)