@@ -178,12 +178,25 @@ class AgentConfig:
178178 temperature: Generation temperature
179179 top_p: Nucleus-sampling threshold; overridden per call by run(top_p=...)
180180 top_k: Top-k sampling cutoff (providers that don't support it ignore it)
181- seed: Sampling seed; a fixed seed plus temperature=0 reproduces a
182- generation exactly, on providers that support it
181+ seed: Sampling seed. A fixed seed plus temperature=0 reproduces a
182+ generation exactly on Gemini, Groq, and local engines
183+ (transformers/vllm/gguf/mlx). OpenAI's chat models accept
184+ ``seed`` and typically reproduce output, but the same
185+ seed+temperature=0 request can still return a different
186+ completion — OpenAI documents this as best-effort determinism,
187+ not a guarantee, especially for reasoning-tier models. Treat an
188+ OpenAI ``seed`` as "usually reproducible," not "always."
183189 presence_penalty: Penalizes tokens already present anywhere in the text
184190 frequency_penalty: Penalizes tokens proportionally to how often they
185191 already appeared (the standard anti-repetition knob for long text)
186192 repetition_penalty: Multiplicative repeat penalty used by local/HF engines
193+ mode: Default execution mode for run()/run_async() when the call
194+ site doesn't pass its own ``mode=``. Defaults to
195+ ``AgentMode.SINGLE`` so a plain ``Agent(config).run(task)`` never
196+ switches to sub-agent decomposition on its own; set to
197+ ``AgentMode.AUTO`` to have the router decide per call, or pass
198+ ``mode=`` on an individual ``run()`` call to override this
199+ default just for that call.
187200 enable_sub_agents: Enable sub-agent spawning
188201 enable_memory: Enable memory systems
189202 enable_streaming: Enable response streaming
@@ -223,6 +236,7 @@ class AgentConfig:
223236 presence_penalty : float = 0.0
224237 frequency_penalty : float = 0.0
225238 repetition_penalty : float = 1.0
239+ mode : AgentMode = AgentMode .SINGLE
226240 enable_sub_agents : bool = True
227241 enable_memory : bool = True
228242 enable_streaming : bool = False
@@ -472,6 +486,19 @@ def to_dict(self) -> dict[str, Any]:
472486from .agent_react import AgentReActMixin # noqa: E402
473487
474488
489+ class _AgentDecompositionClient :
490+ """Adapts an :class:`Agent` to the ``generate(prompt, **kwargs) -> str``
491+ interface :class:`~effgen.core.decomposition_engine.DecompositionEngine`
492+ expects for LLM-assisted task decomposition, by routing through the
493+ agent's own model-generation path (:meth:`Agent._generate`)."""
494+
495+ def __init__ (self , agent : "Agent" ) -> None :
496+ self ._agent = agent
497+
498+ def generate (self , prompt : str , ** kwargs : Any ) -> str :
499+ return self ._agent ._generate (prompt , ** kwargs ).get ("text" , "" )
500+
501+
475502class Agent (AgentGenerationMixin , AgentReActMixin , AgentRuntimeMixin ):
476503 """
477504 Main Agent implementation with ReAct loop and sub-agent support.
@@ -749,7 +776,7 @@ def __init__(self, config: AgentConfig | None = None, session_id: str | None = N
749776 if config .enable_sub_agents :
750777 self .router = SubAgentRouter (
751778 config = config .router_config ,
752- llm_client = self # Pass self as LLM client
779+ llm_client = _AgentDecompositionClient ( self )
753780 )
754781 self .sub_agent_manager = SubAgentManager (
755782 parent_agent = self ,
@@ -1057,7 +1084,7 @@ def _auto_detect_verbose(self) -> bool:
10571084
10581085 def run (self ,
10591086 task : "str | Message | list[Any]" ,
1060- mode : AgentMode = AgentMode . AUTO ,
1087+ mode : AgentMode | None = None ,
10611088 context : dict [str , Any ] | None = None ,
10621089 output_schema : dict [str , Any ] | None = None ,
10631090 output_model : Any = None ,
@@ -1071,7 +1098,11 @@ def run(self,
10711098 ``Message``, or a ``list[ContentPart]``; text is extracted and
10721099 any image/audio/video parts are routed through the multimodal
10731100 path.
1074- mode: Execution mode (single, sub_agents, auto)
1101+ mode: Execution mode (single, sub_agents, auto). Defaults to
1102+ ``self.config.mode`` (``AgentMode.SINGLE`` unless the config
1103+ sets otherwise) when omitted — pass ``mode=AgentMode.AUTO``
1104+ to let the router decide based on task complexity for this
1105+ call only.
10751106 context: Optional context
10761107 output_schema: A JSON-Schema ``dict`` **or** a Pydantic
10771108 ``BaseModel`` subclass — when provided, the final output is
@@ -1095,6 +1126,8 @@ def run(self,
10951126 Returns:
10961127 AgentResponse with results
10971128 """
1129+ if mode is None :
1130+ mode = self .config .mode
10981131 unrecognized = {k for k in kwargs if k not in _RUN_KWARGS and not k .startswith ("_" )}
10991132 if unrecognized :
11001133 bad = sorted (unrecognized )[0 ]
@@ -1748,7 +1781,7 @@ def synthesize(self, synthesis_data: dict[str, Any]) -> str:
17481781
17491782 async def run_async (self ,
17501783 task : "str | Message | list[Any]" ,
1751- mode : AgentMode = AgentMode . AUTO ,
1784+ mode : AgentMode | None = None ,
17521785 context : dict [str , Any ] | None = None ,
17531786 output_schema : dict [str , Any ] | None = None ,
17541787 output_model : Any = None ,
@@ -1763,7 +1796,8 @@ async def run_async(self,
17631796
17641797 Args:
17651798 task: Task description (str, Message, or list[ContentPart])
1766- mode: Execution mode
1799+ mode: Execution mode. Defaults to ``self.config.mode`` when
1800+ omitted (see ``run()``).
17671801 context: Optional context
17681802 output_schema: A JSON-Schema ``dict`` or a Pydantic ``BaseModel``
17691803 subclass (see ``run``).
@@ -1911,7 +1945,7 @@ def _stream_direct(self, task: str, on_answer: Callable[[str], None] | None = No
19111945
19121946 def stream (self ,
19131947 task : "str | Message | list[Any]" ,
1914- mode : AgentMode = AgentMode . AUTO ,
1948+ mode : AgentMode | None = None ,
19151949 context : dict [str , Any ] | None = None ,
19161950 on_thought : Callable [[str ], None ] | None = None ,
19171951 on_tool_call : Callable [[str , str ], None ] | None = None ,
0 commit comments