fast-agent-mcp 0.2.46__py3-none-any.whl → 0.2.47__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of fast-agent-mcp might be problematic. Click here for more details.

@@ -0,0 +1,572 @@
1
+ """
2
+ Iterative Planner Agent - works towards an objective using sub-agents
3
+ """
4
+
5
+ import asyncio
6
+ from typing import Any, Dict, List, Optional, Tuple, Type
7
+
8
+ from mcp.types import TextContent
9
+
10
+ from mcp_agent.agents.agent import Agent
11
+ from mcp_agent.agents.base_agent import BaseAgent
12
+ from mcp_agent.agents.workflow.orchestrator_models import (
13
+ Plan,
14
+ PlanningStep,
15
+ PlanResult,
16
+ Step,
17
+ TaskWithResult,
18
+ format_plan_result,
19
+ format_step_result_text,
20
+ )
21
+ from mcp_agent.core.agent_types import AgentConfig, AgentType
22
+ from mcp_agent.core.exceptions import AgentConfigError
23
+ from mcp_agent.core.prompt import Prompt
24
+ from mcp_agent.core.request_params import RequestParams
25
+ from mcp_agent.logging.logger import get_logger
26
+ from mcp_agent.mcp.interfaces import ModelT
27
+ from mcp_agent.mcp.prompt_message_multipart import PromptMessageMultipart
28
+
29
+ logger = get_logger(__name__)
30
+
31
+
32
+ ITERATIVE_PLAN_SYSTEM_PROMPT_TEMPLATE = """
33
+ You are an expert planner, able to Orchestrate complex tasks by breaking them down in to
34
+ manageable steps, and delegating tasks to Agents.
35
+
36
+ You work iteratively - given an Objective, you consider the current state of the plan,
37
+ decide the next step towards the goal. You document those steps and create clear instructions
38
+ for execution by the Agents, being specific about what you need to know to assess task completion.
39
+
40
+ NOTE: A 'Planning Step' has a description, and a list of tasks that can be delegated
41
+ and executed in parallel.
42
+
43
+ Agents have a 'description' describing their primary function, and a set of 'skills' that
44
+ represent Tools they can use in completing their function.
45
+
46
+ The following Agents are available to you:
47
+
48
+ {{agents}}
49
+
50
+ You must specify the Agent name precisely when generating a Planning Step.
51
+
52
+ """
53
+
54
+
55
+ ITERATIVE_PLAN_PROMPT_TEMPLATE2 = """
56
+
57
+ ```
58
+ <fastagent:data>
59
+ <fastagent:progress>
60
+ {plan_result}
61
+ </fastagent:progress>
62
+
63
+ <fastagent:status>
64
+ {plan_status}
65
+ {iterations_info}
66
+ </fastagent:status>
67
+ </fastagent:data>
68
+ ```
69
+
70
+ The overall objective is:
71
+
72
+ ```
73
+ <fastagent:objective>
74
+ {objective}
75
+ </fastagent:objective>
76
+ ```
77
+
78
+ Produce the next step in the plan to complete the Objective.
79
+
80
+ Consider the previous steps and results, and decide what needs to be done next.
81
+
82
+ Set "is_complete" to true when ANY of these conditions are met:
83
+ 1. The objective has been achieved in full or substantively
84
+ 2. The remaining work is minor or trivial compared to what's been accomplished
85
+ 3. The plan has gathered sufficient information to answer the original request
86
+ 4. The plan has no feasible way of completing the objective.
87
+
88
+ Only set is_complete to `true` if there are no outstanding tasks.
89
+
90
+ Be decisive - avoid excessive planning steps that add little value. It's better to complete a plan early than to continue with marginal improvements.
91
+
92
+ Focus on the meeting the core intent of the objective.
93
+
94
+ """
95
+
96
+
97
+ DELEGATED_TASK_TEMPLATE = """
98
+ You are a component in an orchestrated workflow to achieve an objective.
99
+
100
+ The overall objective is:
101
+
102
+ ```
103
+ <fastagent:objective>
104
+ {objective}
105
+ </fastagent:objective>
106
+ ```
107
+
108
+ Previous context in achieving the objective is below:
109
+
110
+ ```
111
+ <fastagent:context>
112
+ {context}
113
+ </fastagent:context>
114
+ ```
115
+
116
+ Your job is to accomplish the "task" specified in `<fastagent:task>`. The overall objective and
117
+ previous context is supplied to inform your approach.
118
+
119
+ ```
120
+ <fastagent:task>
121
+ {task}
122
+ </fastagent:task>
123
+ ```
124
+
125
+ Provide a direct, concise response on completion that makes it simple to assess the status of
126
+ the overall plan.
127
+ """
128
+
129
+
130
+ PLAN_RESULT_TEMPLATE = """
131
+ The below shows the results of running a plan to meet the specified objective.
132
+
133
+ ```
134
+ <fastagent:plan-results>
135
+ {plan_result}
136
+ </fastagent:plan-results>
137
+ ```
138
+
139
+ The plan was stopped because {termination_reason}.
140
+
141
+ Provide a summary of the tasks completed and their outcomes to complete the Objective.
142
+ Use markdown formatting.
143
+
144
+ If the plan was marked as incomplete but the maximum number of iterations was reached,
145
+ make sure to state clearly what was accomplished and what remains to be done.
146
+
147
+
148
+ Complete the plan by providing an appropriate answer for the original objective. Provide a Mermaid diagram
149
+ (in code fences) showing the plan steps and their relationships, if applicable.
150
+ """
151
+
152
+
153
+ class IterativePlanner(BaseAgent):
154
+ """
155
+ An agent that implements the orchestrator workflow pattern.
156
+
157
+ Dynamically creates execution plans and delegates tasks
158
+ to specialized worker agents, synthesizing their results into a cohesive output.
159
+ Supports both full planning and iterative planning modes.
160
+ """
161
+
162
+ @property
163
+ def agent_type(self) -> AgentType:
164
+ """Return the type of this agent."""
165
+ return AgentType.ITERATIVE_PLANNER
166
+
167
+ def __init__(
168
+ self,
169
+ config: AgentConfig,
170
+ agents: List[Agent],
171
+ plan_iterations: int = -1,
172
+ context: Optional[Any] = None,
173
+ **kwargs,
174
+ ) -> None:
175
+ """
176
+ Initialize an OrchestratorAgent.
177
+
178
+ Args:
179
+ config: Agent configuration or name
180
+ agents: List of specialized worker agents available for task execution
181
+ plan_type: Planning mode ("full" or "iterative")
182
+ context: Optional context object
183
+ **kwargs: Additional keyword arguments to pass to BaseAgent
184
+ """
185
+ if not agents:
186
+ raise AgentConfigError("At least one worker agent must be provided")
187
+
188
+ # Store agents by name for easier lookup
189
+ self.agents: Dict[str, Agent] = {}
190
+ for agent in agents:
191
+ agent_name = agent.name
192
+ self.agents[agent_name] = agent
193
+
194
+ super().__init__(config, context=context, **kwargs)
195
+
196
+ self.plan_iterations = plan_iterations
197
+
198
+ async def initialize(self) -> None:
199
+ """Initialize the orchestrator agent and worker agents."""
200
+ # Initialize all worker agents first if not already initialized
201
+ for agent_name, agent in self.agents.items():
202
+ if not getattr(agent, "initialized", False):
203
+ self.logger.debug(f"Initializing agent: {agent_name}")
204
+ await agent.initialize()
205
+
206
+ # Format agent information using agent cards with XML formatting
207
+ agent_descriptions = []
208
+ for agent_name, agent in self.agents.items():
209
+ agent_card = await agent.agent_card()
210
+ # Format as XML for better readability in prompts
211
+ xml_formatted = self._format_agent_card_as_xml(agent_card)
212
+ agent_descriptions.append(xml_formatted)
213
+
214
+ agents_str = "\n".join(agent_descriptions)
215
+
216
+ # Replace {{agents}} placeholder in the system prompt template
217
+ system_prompt = self.config.instruction.replace("{{agents}}", agents_str)
218
+
219
+ # Update the config instruction with the formatted system prompt
220
+ self.instruction = system_prompt
221
+
222
+ # Initialize the base agent with the updated system prompt
223
+ await super().initialize()
224
+
225
+ self.initialized = True
226
+
227
+ async def shutdown(self) -> None:
228
+ """Shutdown the orchestrator agent and worker agents."""
229
+ await super().shutdown()
230
+
231
+ # Shutdown all worker agents
232
+ for agent_name, agent in self.agents.items():
233
+ try:
234
+ await agent.shutdown()
235
+ except Exception as e:
236
+ self.logger.warning(f"Error shutting down agent {agent_name}: {str(e)}")
237
+
238
+ async def generate(
239
+ self,
240
+ multipart_messages: List[PromptMessageMultipart],
241
+ request_params: Optional[RequestParams] = None,
242
+ ) -> PromptMessageMultipart:
243
+ """
244
+ Execute an orchestrated plan to process the input.
245
+
246
+ Args:
247
+ multipart_messages: Messages to process
248
+ request_params: Optional request parameters
249
+
250
+ Returns:
251
+ The final synthesized response from the orchestration
252
+ """
253
+ # Extract user request
254
+ objective = multipart_messages[-1].all_text() if multipart_messages else ""
255
+ plan_result = await self._execute_plan(objective, request_params)
256
+
257
+ # Return the result
258
+ return PromptMessageMultipart(
259
+ role="assistant",
260
+ content=[TextContent(type="text", text=plan_result.result or "No result available")],
261
+ )
262
+
263
+ async def structured(
264
+ self,
265
+ multipart_messages: List[PromptMessageMultipart],
266
+ model: Type[ModelT],
267
+ request_params: Optional[RequestParams] = None,
268
+ ) -> Tuple[ModelT | None, PromptMessageMultipart]:
269
+ """
270
+ Execute an orchestration plan and parse the result into a structured format.
271
+
272
+ Args:
273
+ prompt: List of messages to process
274
+ model: Pydantic model to parse the response into
275
+ request_params: Optional request parameters
276
+
277
+ Returns:
278
+ The parsed final response, or None if parsing fails
279
+ """
280
+ # Generate orchestration result
281
+ response = await self.generate(multipart_messages, request_params)
282
+
283
+ # Try to parse the response into the specified model
284
+ try:
285
+ result_text = response.last_text()
286
+ prompt_message = PromptMessageMultipart(
287
+ role="user", content=[TextContent(type="text", text=result_text)]
288
+ )
289
+ assert self._llm
290
+ return await self._llm.structured([prompt_message], model, request_params)
291
+ except Exception as e:
292
+ self.logger.warning(f"Failed to parse orchestration result: {str(e)}")
293
+ return None, Prompt.assistant(f"Failed to parse orchestration result: {str(e)}")
294
+
295
+ async def _execute_plan(
296
+ self, objective: str, request_params: RequestParams | None
297
+ ) -> PlanResult:
298
+ """
299
+ Execute a plan to achieve the given objective.
300
+
301
+ Args:
302
+ objective: The objective to achieve
303
+ request_params: Request parameters for execution
304
+
305
+ Returns:
306
+ PlanResult containing execution results and final output
307
+ """
308
+
309
+ objective_met: bool = False
310
+ terminate_plan: str | None = None
311
+ plan_result = PlanResult(objective=objective, step_results=[])
312
+
313
+ while not objective_met and not terminate_plan:
314
+ next_step: PlanningStep | None = await self._get_next_step(
315
+ objective, plan_result, request_params
316
+ )
317
+
318
+ if None is next_step:
319
+ terminate_plan = "Failed to generate plan, terminating early"
320
+ self.logger.error("Failed to generate next step, terminating plan early")
321
+ break
322
+
323
+ assert next_step # lets keep the indenting manageable!
324
+
325
+ if next_step.is_complete:
326
+ objective_met = True
327
+ terminate_plan = "Plan completed successfully"
328
+ break
329
+
330
+ plan = Plan(steps=[next_step], is_complete=next_step.is_complete)
331
+ invalid_agents = self._validate_agent_names(plan)
332
+ if invalid_agents:
333
+ self.logger.error(f"Plan contains invalid agent names: {', '.join(invalid_agents)}")
334
+ terminate_plan = (
335
+ f"Invalid agent names found ({', '.join(invalid_agents)}), terminating plan"
336
+ )
337
+ break
338
+
339
+ for step in plan.steps: # this will only be one for iterative (change later)
340
+ step_result = await self._execute_step(step, plan_result)
341
+ plan_result.add_step_result(step_result)
342
+
343
+ # Store plan in result
344
+ plan_result.plan = plan
345
+
346
+ if self.plan_iterations > 0:
347
+ if len(plan_result.step_results) >= self.plan_iterations:
348
+ terminate_plan = f"Reached maximum number of iterations ({self.plan_iterations}), terminating plan"
349
+
350
+ if not terminate_plan:
351
+ terminate_plan = "Unknown termination reason"
352
+ result_prompt = PLAN_RESULT_TEMPLATE.format(
353
+ plan_result=format_plan_result(plan_result), termination_reason=terminate_plan
354
+ )
355
+
356
+ # Generate final synthesis
357
+ plan_result.result = await self._planner_generate_str(result_prompt, request_params)
358
+ return plan_result
359
+
360
+ async def _execute_step(self, step: Step, previous_result: PlanResult) -> Any:
361
+ """
362
+ Execute a single step from the plan.
363
+
364
+ Args:
365
+ step: The step to execute
366
+ previous_result: Results of the plan execution so far
367
+ request_params: Request parameters
368
+
369
+ Returns:
370
+ Result of executing the step
371
+ """
372
+ from mcp_agent.agents.workflow.orchestrator_models import StepResult
373
+
374
+ # Initialize step result
375
+ step_result = StepResult(step=step, task_results=[])
376
+
377
+ # Format context for tasks
378
+ context = format_plan_result(previous_result)
379
+
380
+ # Group tasks by agent and execute different agents in parallel
381
+ from collections import defaultdict
382
+
383
+ tasks_by_agent = defaultdict(list)
384
+ for task in step.tasks:
385
+ tasks_by_agent[task.agent].append(task)
386
+
387
+ async def execute_agent_tasks(agent_name: str, agent_tasks: List) -> List[TaskWithResult]:
388
+ """Execute all tasks for a single agent sequentially (preserves history)"""
389
+ agent = self.agents.get(agent_name)
390
+ assert agent is not None
391
+
392
+ results = []
393
+ for task in agent_tasks:
394
+ try:
395
+ task_description = DELEGATED_TASK_TEMPLATE.format(
396
+ objective=previous_result.objective, task=task.description, context=context
397
+ )
398
+ result = await agent.generate(
399
+ [
400
+ PromptMessageMultipart(
401
+ role="user",
402
+ content=[TextContent(type="text", text=task_description)],
403
+ )
404
+ ]
405
+ )
406
+
407
+ task_model = task.model_dump()
408
+ results.append(
409
+ TaskWithResult(
410
+ description=task_model["description"],
411
+ agent=task_model["agent"],
412
+ result=result.last_text(),
413
+ )
414
+ )
415
+ except Exception as e:
416
+ self.logger.error(f"Error executing task: {str(e)}")
417
+ task_model = task.model_dump()
418
+ results.append(
419
+ TaskWithResult(
420
+ description=task_model["description"],
421
+ agent=task_model["agent"],
422
+ result=f"ERROR: {str(e)}",
423
+ )
424
+ )
425
+ return results
426
+
427
+ # Execute different agents in parallel, tasks within each agent sequentially
428
+ agent_futures = [
429
+ execute_agent_tasks(agent_name, agent_tasks)
430
+ for agent_name, agent_tasks in tasks_by_agent.items()
431
+ ]
432
+
433
+ all_results = await asyncio.gather(*agent_futures)
434
+ task_results = [result for agent_results in all_results for result in agent_results]
435
+
436
+ # Add all task results to step result
437
+ for task_result in task_results:
438
+ step_result.add_task_result(task_result)
439
+
440
+ # Format step result
441
+ step_result.result = format_step_result_text(step_result)
442
+ return step_result
443
+
444
+ async def _get_next_step(
445
+ self, objective: str, plan_result: PlanResult, request_params: RequestParams | None
446
+ ) -> PlanningStep | None:
447
+ """
448
+ Generate just the next step for iterative planning.
449
+
450
+ Args:
451
+ objective: The objective to achieve
452
+ plan_result: Current plan execution state
453
+ request_params: Request parameters
454
+
455
+ Returns:
456
+ Next step to execute, or None if parsing fails
457
+ """
458
+
459
+ # Determine plan status
460
+ if plan_result.is_complete:
461
+ plan_status = "Plan Status: Complete"
462
+ elif plan_result.step_results:
463
+ plan_status = "Plan Status: In Progress"
464
+ else:
465
+ plan_status = "Plan Status: Not Started"
466
+
467
+ # Calculate iteration information
468
+
469
+ if self.plan_iterations > 0:
470
+ max_iterations = self.plan_iterations
471
+ current_iteration = len(plan_result.step_results)
472
+ iterations_remaining = max_iterations - current_iteration
473
+ iterations_info = (
474
+ f"Planning Budget: {iterations_remaining} of {max_iterations} iterations remaining"
475
+ )
476
+ else:
477
+ iterations_info = "Iterating until objective is met."
478
+
479
+ # Format the planning prompt
480
+ prompt = ITERATIVE_PLAN_PROMPT_TEMPLATE2.format(
481
+ objective=objective,
482
+ plan_result=format_plan_result(plan_result),
483
+ plan_status=plan_status,
484
+ iterations_info=iterations_info,
485
+ )
486
+
487
+ # Get structured response from LLM
488
+ try:
489
+ plan_msg = PromptMessageMultipart(
490
+ role="user", content=[TextContent(type="text", text=prompt)]
491
+ )
492
+ assert self._llm
493
+ next_step, _ = await self._llm.structured([plan_msg], PlanningStep, request_params)
494
+ return next_step
495
+ except Exception as e:
496
+ self.logger.error(f"Failed to parse next step: {str(e)}")
497
+ return None
498
+
499
+ def _validate_agent_names(self, plan: Plan) -> List[str]:
500
+ """
501
+ Validate all agent names in a plan before execution.
502
+
503
+ Args:
504
+ plan: The plan to validate
505
+ """
506
+ invalid_agents = []
507
+
508
+ for step in plan.steps:
509
+ for task in step.tasks:
510
+ if task.agent not in self.agents:
511
+ invalid_agents.append(task.agent)
512
+
513
+ return invalid_agents
514
+
515
+ @staticmethod
516
+ def _format_agent_card_as_xml(agent_card) -> str:
517
+ """
518
+ Format an agent card as XML for display in prompts.
519
+
520
+ This creates a structured XML representation that's more readable than JSON
521
+ and includes all relevant agent information in a hierarchical format.
522
+
523
+ Args:
524
+ agent_card: The AgentCard object
525
+
526
+ Returns:
527
+ XML formatted agent information
528
+ """
529
+ xml_parts = [f'<fastagent:agent name="{agent_card.name}">']
530
+
531
+ # Add description if available
532
+ if agent_card.description:
533
+ xml_parts.append(
534
+ f" <fastagent:description>{agent_card.description}</fastagent:description>"
535
+ )
536
+
537
+ # Add skills if available
538
+ if hasattr(agent_card, "skills") and agent_card.skills:
539
+ xml_parts.append(" <fastagent:skills>")
540
+ for skill in agent_card.skills:
541
+ xml_parts.append(f' <fastagent:skill name="{skill.name}">')
542
+ if hasattr(skill, "description") and skill.description:
543
+ xml_parts.append(
544
+ f" <fastagent:description>{skill.description}</fastagent:description>"
545
+ )
546
+ xml_parts.append(" </fastagent:skill>")
547
+ xml_parts.append(" </fastagent:skills>")
548
+
549
+ xml_parts.append("</fastagent:agent>")
550
+
551
+ return "\n".join(xml_parts)
552
+
553
+ async def _planner_generate_str(
554
+ self, message: str, request_params: RequestParams | None
555
+ ) -> str:
556
+ """
557
+ Generate string response from the orchestrator's own LLM.
558
+
559
+ Args:
560
+ message: Message to send to the LLM
561
+ request_params: Request parameters
562
+
563
+ Returns:
564
+ String response from the LLM
565
+ """
566
+ # Create prompt message
567
+ prompt = PromptMessageMultipart(
568
+ role="user", content=[TextContent(type="text", text=message)]
569
+ )
570
+ assert self._llm, "LLM must be initialized before generating text"
571
+ response = await self._llm.generate([prompt], request_params)
572
+ return response.last_text()
@@ -12,8 +12,8 @@ from mcp.types import TextContent
12
12
  from mcp_agent.agents.agent import Agent
13
13
  from mcp_agent.agents.base_agent import BaseAgent
14
14
  from mcp_agent.agents.workflow.orchestrator_models import (
15
- NextStep,
16
15
  Plan,
16
+ PlanningStep,
17
17
  PlanResult,
18
18
  Step,
19
19
  TaskWithResult,
@@ -443,7 +443,7 @@ class OrchestratorAgent(BaseAgent):
443
443
 
444
444
  async def _get_next_step(
445
445
  self, objective: str, plan_result: PlanResult, request_params: RequestParams
446
- ) -> Optional[NextStep]:
446
+ ) -> Optional[PlanningStep]:
447
447
  """
448
448
  Generate just the next step for iterative planning.
449
449
 
@@ -490,7 +490,7 @@ class OrchestratorAgent(BaseAgent):
490
490
  plan_msg = PromptMessageMultipart(
491
491
  role="user", content=[TextContent(type="text", text=prompt)]
492
492
  )
493
- next_step, _ = await self._llm.structured([plan_msg], NextStep, request_params)
493
+ next_step, _ = await self._llm.structured([plan_msg], PlanningStep, request_params)
494
494
  return next_step
495
495
  except Exception as e:
496
496
  self.logger.error(f"Failed to parse next step: {str(e)}")
@@ -43,6 +43,12 @@ class Step(BaseModel):
43
43
  )
44
44
 
45
45
 
46
+ class PlanningStep(Step):
47
+ """Single next step in iterative planning"""
48
+
49
+ is_complete: bool = Field(description="Whether the overall plan objective is complete")
50
+
51
+
46
52
  class Plan(BaseModel):
47
53
  """Plan generated by the orchestrator planner."""
48
54
 
@@ -107,12 +113,6 @@ class PlanResult(BaseModel):
107
113
  self.step_results.append(step_result)
108
114
 
109
115
 
110
- class NextStep(Step):
111
- """Single next step in iterative planning"""
112
-
113
- is_complete: bool = Field(description="Whether the overall plan objective is complete")
114
-
115
-
116
116
  def format_task_result_text(task_result: TaskWithResult) -> str:
117
117
  """Format a task result as plain text for display"""
118
118
  return TASK_RESULT_TEMPLATE.format(
@@ -22,6 +22,7 @@ class AgentType(Enum):
22
22
  EVALUATOR_OPTIMIZER = "evaluator_optimizer"
23
23
  ROUTER = "router"
24
24
  CHAIN = "chain"
25
+ ITERATIVE_PLANNER = "iterative_planner"
25
26
 
26
27
 
27
28
  @dataclass
@@ -25,6 +25,7 @@ from mcp.client.session import ElicitationFnT
25
25
  from pydantic import AnyUrl
26
26
 
27
27
  from mcp_agent.agents.agent import AgentConfig
28
+ from mcp_agent.agents.workflow.iterative_planner import ITERATIVE_PLAN_SYSTEM_PROMPT_TEMPLATE
28
29
  from mcp_agent.agents.workflow.router_agent import (
29
30
  ROUTING_SYSTEM_INSTRUCTION,
30
31
  )
@@ -457,6 +458,59 @@ def orchestrator(
457
458
  )
458
459
 
459
460
 
461
+ def iterative_planner(
462
+ self,
463
+ name: str,
464
+ *,
465
+ agents: List[str],
466
+ instruction: str | Path | AnyUrl = ITERATIVE_PLAN_SYSTEM_PROMPT_TEMPLATE,
467
+ model: Optional[str] = None,
468
+ request_params: RequestParams | None = None,
469
+ plan_iterations: int = -1,
470
+ default: bool = False,
471
+ api_key: str | None = None,
472
+ ) -> Callable[[AgentCallable[P, R]], DecoratedOrchestratorProtocol[P, R]]:
473
+ """
474
+ Decorator to create and register an orchestrator agent with type-safe signature.
475
+
476
+ Args:
477
+ name: Name of the orchestrator
478
+ agents: List of agent names this orchestrator can use
479
+ instruction: Base instruction for the orchestrator
480
+ model: Model specification string
481
+ use_history: Whether to maintain conversation history
482
+ request_params: Additional request parameters for the LLM
483
+ human_input: Whether to enable human input capabilities
484
+ plan_type: Planning approach - "full" or "iterative"
485
+ plan_iterations: Maximum number of planning iterations (0 for unlimited)
486
+ default: Whether to mark this as the default agent
487
+
488
+ Returns:
489
+ A decorator that registers the orchestrator with proper type annotations
490
+ """
491
+
492
+ # Create final request params with plan_iterations
493
+ resolved_instruction = _resolve_instruction(instruction)
494
+
495
+ return cast(
496
+ "Callable[[AgentCallable[P, R]], DecoratedOrchestratorProtocol[P, R]]",
497
+ _decorator_impl(
498
+ self,
499
+ AgentType.ITERATIVE_PLANNER,
500
+ name=name,
501
+ instruction=resolved_instruction,
502
+ servers=[], # Orchestrators don't connect to servers directly
503
+ model=model,
504
+ use_history=False,
505
+ request_params=request_params,
506
+ child_agents=agents,
507
+ plan_iterations=plan_iterations,
508
+ default=default,
509
+ api_key=api_key,
510
+ ),
511
+ )
512
+
513
+
460
514
  def router(
461
515
  self,
462
516
  name: str,