5151from ..utils .content_utils import to_user_content
5252from ..utils .context_utils import Aclosing
5353from ..utils .env_utils import is_enterprise_mode_enabled
54+ from ..utils .instructions_utils import inject_session_state
55+ from ..utils .instructions_utils import InstructionProvider
5456from .base_agent import BaseAgent
5557from .context import Context
5658from .invocation_context import InvocationContext
@@ -144,6 +146,16 @@ class ManagedAgent(BaseAgent):
144146 agent_config : Optional [CreateAgentInteractionAgentConfigParam ] = None
145147 """Runtime configuration passed to interactions.create."""
146148
149+ instruction : Union [str , InstructionProvider ] = ''
150+ """The system instruction sent to the Managed Agent.
151+
152+ A plain string may embed ``{var}``, ``{artifact.name}``, or ``{var?}``
153+ placeholders that are resolved from session state / artifacts at request time
154+ (see ``inject_session_state``). An ``InstructionProvider`` callable is invoked
155+ with a ``ReadonlyContext`` and bypasses placeholder injection (it manages
156+ state itself). Empty by default, in which case no system instruction is sent.
157+ """
158+
147159 tools : list [
148160 Union [types .Tool , BaseTool , Callable [..., Any ], RemoteMcpServer ]
149161 ] = Field (default_factory = list )
@@ -197,6 +209,29 @@ def api_client(self) -> Client:
197209 )
198210 return self ._api_client
199211
212+ async def canonical_instruction (
213+ self , ctx : ReadonlyContext
214+ ) -> tuple [str , bool ]:
215+ """Resolves ``self.instruction`` for the current context.
216+
217+ Mirrors ``LlmAgent.canonical_instruction``.
218+
219+ Args:
220+ ctx: The read-only context used to resolve an InstructionProvider.
221+
222+ Returns:
223+ A tuple of (instruction, bypass_state_injection).
224+ ``bypass_state_injection``
225+ is True when the instruction came from an ``InstructionProvider`` callable
226+ (which manages state itself), False for a plain string.
227+ """
228+ if isinstance (self .instruction , str ):
229+ return self .instruction , False
230+ instruction = self .instruction (ctx )
231+ if inspect .isawaitable (instruction ):
232+ instruction = await instruction
233+ return instruction , True
234+
200235 async def _resolve_backend_tools (
201236 self , ctx : InvocationContext
202237 ) -> list [ToolParam ]:
@@ -360,6 +395,15 @@ async def _run_async_impl(
360395 )
361396 interaction_tools = await self ._resolve_backend_tools (ctx )
362397
398+ raw_si , bypass_state_injection = await self .canonical_instruction (
399+ ReadonlyContext (ctx )
400+ )
401+ system_instruction = raw_si
402+ if not bypass_state_injection :
403+ system_instruction = await inject_session_state (
404+ raw_si , ReadonlyContext (ctx )
405+ )
406+
363407 create_kwargs : dict [str , Any ] = {
364408 'agent' : self .agent_id ,
365409 'input' : input_steps ,
@@ -377,6 +421,8 @@ async def _run_async_impl(
377421 create_kwargs ['agent_config' ] = self .agent_config
378422 if prev_interaction_id :
379423 create_kwargs ['previous_interaction_id' ] = prev_interaction_id
424+ if system_instruction :
425+ create_kwargs ['system_instruction' ] = system_instruction
380426
381427 # Request-time header merge, parity with google_llm.generate_content_async:
382428 # combine any RunConfig headers with ADK tracking headers, non-destructively.
@@ -402,7 +448,7 @@ async def _run_async_impl(
402448 build_interactions_request_log (
403449 model = self .agent_id ,
404450 input_steps = input_steps ,
405- system_instruction = None ,
451+ system_instruction = system_instruction or None ,
406452 tools = interaction_tools if interaction_tools else None ,
407453 generation_config = None ,
408454 previous_interaction_id = prev_interaction_id ,
0 commit comments