From 1395b936d061878eee2d32e36c6cd7017d341657 Mon Sep 17 00:00:00 2001 From: Ruiming Zhao Date: Mon, 10 Aug 2026 09:03:09 -0700 Subject: [PATCH 1/5] fix(python): include constructor tools in agent hook startup --- .../core/agent_framework/_agent_hooks.py | 8 +++++++- .../packages/core/tests/core/test_agent_hooks.py | 16 ++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/python/packages/core/agent_framework/_agent_hooks.py b/python/packages/core/agent_framework/_agent_hooks.py index 31c52f235d..5958ffe47d 100644 --- a/python/packages/core/agent_framework/_agent_hooks.py +++ b/python/packages/core/agent_framework/_agent_hooks.py @@ -860,7 +860,13 @@ def _tool_names(context: AgentContext) -> list[str]: """Project the registered tool names for ``agent_startup`` (spec ``tools_registered``).""" from ._tools import _get_tool_name, normalize_tools # type: ignore[reportPrivateUsage] - tools: Any = context.tools if context.tools is not None else getattr(context.agent, "tools", None) + if context.tools is not None: + tools: Any = context.tools + else: + tools = getattr(context.agent, "tools", None) + default_options = getattr(context.agent, "default_options", None) + if tools is None and isinstance(default_options, Mapping): + tools = default_options.get("tools") if tools is None: return [] try: diff --git a/python/packages/core/tests/core/test_agent_hooks.py b/python/packages/core/tests/core/test_agent_hooks.py index a13b96baa0..7ae28dea87 100644 --- a/python/packages/core/tests/core/test_agent_hooks.py +++ b/python/packages/core/tests/core/test_agent_hooks.py @@ -293,6 +293,22 @@ async def test_full_tool_run_emits_complete_ordered_session(chat_client_base: Mo assert pre_tool["tool_call"]["id"] == "call_1" +@requires_sdk +async def test_agent_startup_projects_constructor_registered_tools(chat_client_base: MockBaseChatClient) -> None: + guard = AllowGuard() + agent = Agent( + client=chat_client_base, + tools=[weather_tool], + middleware=[create_agent_hooks_middleware([guard])], + ) + + await agent.run("hello") + + startup = guard.contexts_for("agent_startup") + assert len(startup) == 1 + assert startup[0]["agent_init"]["tools_registered"] == ["weather_tool"] + + @requires_sdk async def test_input_projection_is_faithful(chat_client_base: MockBaseChatClient) -> None: guard = AllowGuard() From 08094a00fa12ba798c7f99d48240b91b5e1feca1 Mon Sep 17 00:00:00 2001 From: Ruiming Zhao Date: Mon, 10 Aug 2026 09:34:57 -0700 Subject: [PATCH 2/5] fix(python): merge configured tools in hook projection --- .../core/agent_framework/_agent_hooks.py | 29 +++++--- .../core/tests/core/test_agent_hooks.py | 68 +++++++++++++++++++ 2 files changed, 86 insertions(+), 11 deletions(-) diff --git a/python/packages/core/agent_framework/_agent_hooks.py b/python/packages/core/agent_framework/_agent_hooks.py index 5958ffe47d..342aaa28c1 100644 --- a/python/packages/core/agent_framework/_agent_hooks.py +++ b/python/packages/core/agent_framework/_agent_hooks.py @@ -858,21 +858,28 @@ def _agent_updates_from_response(response: AgentResponse[Any]) -> list[AgentResp def _tool_names(context: AgentContext) -> list[str]: """Project the registered tool names for ``agent_startup`` (spec ``tools_registered``).""" - from ._tools import _get_tool_name, normalize_tools # type: ignore[reportPrivateUsage] + from ._tools import _append_unique_tools, _get_tool_name, normalize_tools # type: ignore[reportPrivateUsage] - if context.tools is not None: - tools: Any = context.tools + default_options = getattr(context.agent, "default_options", None) + if isinstance(default_options, Mapping) and "tools" in default_options: + configured_tools: Any = default_options["tools"] else: - tools = getattr(context.agent, "tools", None) - default_options = getattr(context.agent, "default_options", None) - if tools is None and isinstance(default_options, Mapping): - tools = default_options.get("tools") - if tools is None: - return [] + configured_tools = getattr(context.agent, "tools", None) + + # Agent._prepare_run_context uses the named run-level tools when present and + # otherwise consumes options["tools"]. Mirror that precedence here so the + # startup projection describes the same run that reaches the model. + run_tools = context.tools + if run_tools is None and isinstance(context.options, Mapping): + run_tools = context.options.get("tools") + try: - normalized = normalize_tools(tools) + normalized = _append_unique_tools( + normalize_tools(configured_tools), + normalize_tools(run_tools), + ) except Exception: - logger.warning("agent-hooks could not normalize the run's tools for the agent_startup projection.") + logger.warning("agent-hooks could not normalize the agent's tools for the agent_startup projection.") return [] names: list[str] = [] for item in normalized: diff --git a/python/packages/core/tests/core/test_agent_hooks.py b/python/packages/core/tests/core/test_agent_hooks.py index 7ae28dea87..74215bd3b3 100644 --- a/python/packages/core/tests/core/test_agent_hooks.py +++ b/python/packages/core/tests/core/test_agent_hooks.py @@ -309,6 +309,74 @@ async def test_agent_startup_projects_constructor_registered_tools(chat_client_b assert startup[0]["agent_init"]["tools_registered"] == ["weather_tool"] +@requires_sdk +async def test_agent_startup_projects_configured_and_run_tools(chat_client_base: MockBaseChatClient) -> None: + @tool(approval_mode="never_require") + def runtime_tool(location: str) -> str: + """Look up a location supplied at runtime.""" + return f"runtime weather in {location}" + + guard = AllowGuard() + agent = Agent( + client=chat_client_base, + tools=[weather_tool], + middleware=[create_agent_hooks_middleware([guard])], + ) + + await agent.run("hello", tools=[runtime_tool]) + + startup = guard.contexts_for("agent_startup") + assert len(startup) == 1 + assert startup[0]["agent_init"]["tools_registered"] == ["weather_tool", "runtime_tool"] + + +@requires_sdk +async def test_agent_startup_projects_configured_and_options_tools(chat_client_base: MockBaseChatClient) -> None: + @tool(approval_mode="never_require") + def options_tool(location: str) -> str: + """Look up a location supplied through run options.""" + return f"options weather in {location}" + + guard = AllowGuard() + agent = Agent( + client=chat_client_base, + tools=[weather_tool], + middleware=[create_agent_hooks_middleware([guard])], + ) + + await agent.run("hello", options={"tools": [options_tool]}) + + startup = guard.contexts_for("agent_startup") + assert len(startup) == 1 + assert startup[0]["agent_init"]["tools_registered"] == ["weather_tool", "options_tool"] + + +@requires_sdk +async def test_agent_startup_prefers_named_run_tools_over_options(chat_client_base: MockBaseChatClient) -> None: + @tool(approval_mode="never_require") + def named_tool(location: str) -> str: + """Look up a location supplied through the named argument.""" + return f"named weather in {location}" + + @tool(approval_mode="never_require") + def ignored_options_tool(location: str) -> str: + """Look up a location supplied through run options.""" + return f"ignored options weather in {location}" + + guard = AllowGuard() + agent = Agent( + client=chat_client_base, + tools=[weather_tool], + middleware=[create_agent_hooks_middleware([guard])], + ) + + await agent.run("hello", tools=[named_tool], options={"tools": [ignored_options_tool]}) + + startup = guard.contexts_for("agent_startup") + assert len(startup) == 1 + assert startup[0]["agent_init"]["tools_registered"] == ["weather_tool", "named_tool"] + + @requires_sdk async def test_input_projection_is_faithful(chat_client_base: MockBaseChatClient) -> None: guard = AllowGuard() From 9e75ecd87e5b1483aae6347d8599630d730d7c67 Mon Sep 17 00:00:00 2001 From: Ruiming Zhao Date: Tue, 11 Aug 2026 13:44:53 -0700 Subject: [PATCH 3/5] fix(python): include MCP tools in hook startup --- .../core/agent_framework/_agent_hooks.py | 58 ++++++++++++++++--- .../core/tests/core/test_agent_hooks.py | 22 +++++++ 2 files changed, 73 insertions(+), 7 deletions(-) diff --git a/python/packages/core/agent_framework/_agent_hooks.py b/python/packages/core/agent_framework/_agent_hooks.py index 342aaa28c1..39814d1f16 100644 --- a/python/packages/core/agent_framework/_agent_hooks.py +++ b/python/packages/core/agent_framework/_agent_hooks.py @@ -856,9 +856,10 @@ def _agent_updates_from_response(response: AgentResponse[Any]) -> list[AgentResp return updates -def _tool_names(context: AgentContext) -> list[str]: +async def _tool_names(context: AgentContext) -> list[str]: """Project the registered tool names for ``agent_startup`` (spec ``tools_registered``).""" - from ._tools import _append_unique_tools, _get_tool_name, normalize_tools # type: ignore[reportPrivateUsage] + from ._mcp import MCPTool + from ._tools import _append_unique_tools, _get_tool_name, normalize_tools # pyright: ignore[reportPrivateUsage] default_options = getattr(context.agent, "default_options", None) if isinstance(default_options, Mapping) and "tools" in default_options: @@ -874,13 +875,56 @@ def _tool_names(context: AgentContext) -> list[str]: run_tools = context.options.get("tools") try: - normalized = _append_unique_tools( - normalize_tools(configured_tools), - normalize_tools(run_tools), - ) + configured = normalize_tools(configured_tools) + run = normalize_tools(run_tools) except Exception: logger.warning("agent-hooks could not normalize the agent's tools for the agent_startup projection.") return [] + + normalized: list[Any] = [] + seen_mcp_tools: list[MCPTool] = [] + mcp_duplicate_message = "Tool names must be unique. Consider setting `tool_name_prefix` on the MCPTool." + + async def append_mcp_tools(mcp_tool: MCPTool) -> None: + if any(mcp_tool is seen_tool for seen_tool in seen_mcp_tools): + return + seen_mcp_tools.append(mcp_tool) + + if not mcp_tool.is_connected: + exit_stack = getattr(context.agent, "_async_exit_stack", None) + if exit_stack is None: + logger.warning( + "agent-hooks could not connect MCP tool %r for the agent_startup projection.", mcp_tool.name + ) + return + await exit_stack.enter_async_context(mcp_tool) + + _append_unique_tools( + normalized, + mcp_tool.functions, + duplicate_error_message=mcp_duplicate_message, + ) + + try: + for item in configured: + if isinstance(item, MCPTool): + await append_mcp_tools(item) + else: + _append_unique_tools(normalized, [item]) + + for item in run: + if isinstance(item, MCPTool): + await append_mcp_tools(item) + else: + _append_unique_tools(normalized, [item]) + + for item in getattr(context.agent, "mcp_tools", ()) or (): + if isinstance(item, MCPTool): + await append_mcp_tools(item) + except Exception: + logger.warning("agent-hooks could not resolve the agent's tools for the agent_startup projection.") + return [] + names: list[str] = [] for item in normalized: name = _get_tool_name(item) @@ -986,7 +1030,7 @@ def _new_run_state(self, context: AgentContext) -> _RunState: async def _emit_run_start(self, context: AgentContext, state: _RunState) -> None: """Emit ``agent_startup`` (per-run sessions) and ``input``; apply input transforms.""" if not state.session_scoped: - await state.emitter.emit(state.builder.agent_startup(tools_registered=_tool_names(context))) + await state.emitter.emit(state.builder.agent_startup(tools_registered=await _tool_names(context))) before = _InputCodec.to_wire(context.messages) outcome: EmitOutcome = await state.emitter.emit( state.builder.input(content=before["content"], role=before["role"]) diff --git a/python/packages/core/tests/core/test_agent_hooks.py b/python/packages/core/tests/core/test_agent_hooks.py index 74215bd3b3..dd9ff83b55 100644 --- a/python/packages/core/tests/core/test_agent_hooks.py +++ b/python/packages/core/tests/core/test_agent_hooks.py @@ -309,6 +309,28 @@ async def test_agent_startup_projects_constructor_registered_tools(chat_client_b assert startup[0]["agent_init"]["tools_registered"] == ["weather_tool"] +@requires_sdk +async def test_agent_startup_projects_constructor_mcp_tools(chat_client_base: MockBaseChatClient) -> None: + from agent_framework._mcp import MCPTool + + mcp_tool = MCPTool(name="weather-server", load_tools=False, load_prompts=False) # type: ignore[abstract] + mcp_tool.functions.append(weather_tool) + mcp_tool.is_connected = True + + guard = AllowGuard() + agent = Agent( + client=chat_client_base, + tools=[mcp_tool], + middleware=[create_agent_hooks_middleware([guard])], + ) + + await agent.run("hello") + + startup = guard.contexts_for("agent_startup") + assert len(startup) == 1 + assert startup[0]["agent_init"]["tools_registered"] == ["weather_tool"] + + @requires_sdk async def test_agent_startup_projects_configured_and_run_tools(chat_client_base: MockBaseChatClient) -> None: @tool(approval_mode="never_require") From eb00f36328bdbb08aed4669b479b5ccaee06e56a Mon Sep 17 00:00:00 2001 From: Ruiming Zhao Date: Tue, 11 Aug 2026 13:59:29 -0700 Subject: [PATCH 4/5] fix(python): keep hook projection focused --- .../core/agent_framework/_agent_hooks.py | 75 +++------------- .../core/tests/core/test_agent_hooks.py | 90 ------------------- 2 files changed, 12 insertions(+), 153 deletions(-) diff --git a/python/packages/core/agent_framework/_agent_hooks.py b/python/packages/core/agent_framework/_agent_hooks.py index 39814d1f16..e690d49543 100644 --- a/python/packages/core/agent_framework/_agent_hooks.py +++ b/python/packages/core/agent_framework/_agent_hooks.py @@ -856,75 +856,24 @@ def _agent_updates_from_response(response: AgentResponse[Any]) -> list[AgentResp return updates -async def _tool_names(context: AgentContext) -> list[str]: +def _tool_names(context: AgentContext) -> list[str]: """Project the registered tool names for ``agent_startup`` (spec ``tools_registered``).""" - from ._mcp import MCPTool - from ._tools import _append_unique_tools, _get_tool_name, normalize_tools # pyright: ignore[reportPrivateUsage] + from ._tools import _get_tool_name, normalize_tools # pyright: ignore[reportPrivateUsage] - default_options = getattr(context.agent, "default_options", None) - if isinstance(default_options, Mapping) and "tools" in default_options: - configured_tools: Any = default_options["tools"] + if context.tools is not None: + tools: Any = context.tools else: - configured_tools = getattr(context.agent, "tools", None) - - # Agent._prepare_run_context uses the named run-level tools when present and - # otherwise consumes options["tools"]. Mirror that precedence here so the - # startup projection describes the same run that reaches the model. - run_tools = context.tools - if run_tools is None and isinstance(context.options, Mapping): - run_tools = context.options.get("tools") - - try: - configured = normalize_tools(configured_tools) - run = normalize_tools(run_tools) - except Exception: - logger.warning("agent-hooks could not normalize the agent's tools for the agent_startup projection.") + tools = getattr(context.agent, "tools", None) + default_options = getattr(context.agent, "default_options", None) + if tools is None and isinstance(default_options, Mapping): + tools = cast(Any, cast(Mapping[str, Any], default_options).get("tools")) + if tools is None: return [] - - normalized: list[Any] = [] - seen_mcp_tools: list[MCPTool] = [] - mcp_duplicate_message = "Tool names must be unique. Consider setting `tool_name_prefix` on the MCPTool." - - async def append_mcp_tools(mcp_tool: MCPTool) -> None: - if any(mcp_tool is seen_tool for seen_tool in seen_mcp_tools): - return - seen_mcp_tools.append(mcp_tool) - - if not mcp_tool.is_connected: - exit_stack = getattr(context.agent, "_async_exit_stack", None) - if exit_stack is None: - logger.warning( - "agent-hooks could not connect MCP tool %r for the agent_startup projection.", mcp_tool.name - ) - return - await exit_stack.enter_async_context(mcp_tool) - - _append_unique_tools( - normalized, - mcp_tool.functions, - duplicate_error_message=mcp_duplicate_message, - ) - try: - for item in configured: - if isinstance(item, MCPTool): - await append_mcp_tools(item) - else: - _append_unique_tools(normalized, [item]) - - for item in run: - if isinstance(item, MCPTool): - await append_mcp_tools(item) - else: - _append_unique_tools(normalized, [item]) - - for item in getattr(context.agent, "mcp_tools", ()) or (): - if isinstance(item, MCPTool): - await append_mcp_tools(item) + normalized = normalize_tools(tools) except Exception: - logger.warning("agent-hooks could not resolve the agent's tools for the agent_startup projection.") + logger.warning("agent-hooks could not normalize the run's tools for the agent_startup projection.") return [] - names: list[str] = [] for item in normalized: name = _get_tool_name(item) @@ -1030,7 +979,7 @@ def _new_run_state(self, context: AgentContext) -> _RunState: async def _emit_run_start(self, context: AgentContext, state: _RunState) -> None: """Emit ``agent_startup`` (per-run sessions) and ``input``; apply input transforms.""" if not state.session_scoped: - await state.emitter.emit(state.builder.agent_startup(tools_registered=await _tool_names(context))) + await state.emitter.emit(state.builder.agent_startup(tools_registered=_tool_names(context))) before = _InputCodec.to_wire(context.messages) outcome: EmitOutcome = await state.emitter.emit( state.builder.input(content=before["content"], role=before["role"]) diff --git a/python/packages/core/tests/core/test_agent_hooks.py b/python/packages/core/tests/core/test_agent_hooks.py index dd9ff83b55..7ae28dea87 100644 --- a/python/packages/core/tests/core/test_agent_hooks.py +++ b/python/packages/core/tests/core/test_agent_hooks.py @@ -309,96 +309,6 @@ async def test_agent_startup_projects_constructor_registered_tools(chat_client_b assert startup[0]["agent_init"]["tools_registered"] == ["weather_tool"] -@requires_sdk -async def test_agent_startup_projects_constructor_mcp_tools(chat_client_base: MockBaseChatClient) -> None: - from agent_framework._mcp import MCPTool - - mcp_tool = MCPTool(name="weather-server", load_tools=False, load_prompts=False) # type: ignore[abstract] - mcp_tool.functions.append(weather_tool) - mcp_tool.is_connected = True - - guard = AllowGuard() - agent = Agent( - client=chat_client_base, - tools=[mcp_tool], - middleware=[create_agent_hooks_middleware([guard])], - ) - - await agent.run("hello") - - startup = guard.contexts_for("agent_startup") - assert len(startup) == 1 - assert startup[0]["agent_init"]["tools_registered"] == ["weather_tool"] - - -@requires_sdk -async def test_agent_startup_projects_configured_and_run_tools(chat_client_base: MockBaseChatClient) -> None: - @tool(approval_mode="never_require") - def runtime_tool(location: str) -> str: - """Look up a location supplied at runtime.""" - return f"runtime weather in {location}" - - guard = AllowGuard() - agent = Agent( - client=chat_client_base, - tools=[weather_tool], - middleware=[create_agent_hooks_middleware([guard])], - ) - - await agent.run("hello", tools=[runtime_tool]) - - startup = guard.contexts_for("agent_startup") - assert len(startup) == 1 - assert startup[0]["agent_init"]["tools_registered"] == ["weather_tool", "runtime_tool"] - - -@requires_sdk -async def test_agent_startup_projects_configured_and_options_tools(chat_client_base: MockBaseChatClient) -> None: - @tool(approval_mode="never_require") - def options_tool(location: str) -> str: - """Look up a location supplied through run options.""" - return f"options weather in {location}" - - guard = AllowGuard() - agent = Agent( - client=chat_client_base, - tools=[weather_tool], - middleware=[create_agent_hooks_middleware([guard])], - ) - - await agent.run("hello", options={"tools": [options_tool]}) - - startup = guard.contexts_for("agent_startup") - assert len(startup) == 1 - assert startup[0]["agent_init"]["tools_registered"] == ["weather_tool", "options_tool"] - - -@requires_sdk -async def test_agent_startup_prefers_named_run_tools_over_options(chat_client_base: MockBaseChatClient) -> None: - @tool(approval_mode="never_require") - def named_tool(location: str) -> str: - """Look up a location supplied through the named argument.""" - return f"named weather in {location}" - - @tool(approval_mode="never_require") - def ignored_options_tool(location: str) -> str: - """Look up a location supplied through run options.""" - return f"ignored options weather in {location}" - - guard = AllowGuard() - agent = Agent( - client=chat_client_base, - tools=[weather_tool], - middleware=[create_agent_hooks_middleware([guard])], - ) - - await agent.run("hello", tools=[named_tool], options={"tools": [ignored_options_tool]}) - - startup = guard.contexts_for("agent_startup") - assert len(startup) == 1 - assert startup[0]["agent_init"]["tools_registered"] == ["weather_tool", "named_tool"] - - @requires_sdk async def test_input_projection_is_faithful(chat_client_base: MockBaseChatClient) -> None: guard = AllowGuard() From b212dc913ae38c92b4916ea7db6293bd2ff18f63 Mon Sep 17 00:00:00 2001 From: Ruiming Zhao Date: Fri, 14 Aug 2026 16:32:08 -0700 Subject: [PATCH 5/5] Python: project configured tools in agent_startup tools_registered --- .../core/agent_framework/_agent_hooks.py | 51 ++++++++++++------- .../core/tests/core/test_agent_hooks.py | 24 +++++++++ 2 files changed, 58 insertions(+), 17 deletions(-) diff --git a/python/packages/core/agent_framework/_agent_hooks.py b/python/packages/core/agent_framework/_agent_hooks.py index e690d49543..a8fbdfd7f1 100644 --- a/python/packages/core/agent_framework/_agent_hooks.py +++ b/python/packages/core/agent_framework/_agent_hooks.py @@ -857,27 +857,44 @@ def _agent_updates_from_response(response: AgentResponse[Any]) -> list[AgentResp def _tool_names(context: AgentContext) -> list[str]: - """Project the registered tool names for ``agent_startup`` (spec ``tools_registered``).""" + """Project the registered tool names for ``agent_startup`` (spec ``tools_registered``). + + The projection mirrors run preparation: constructor tools, agent/run options tools, + and the run-level tool overrides are all combined, so a run that supplies extra + tools still reports the agent's configured tools alongside them. + """ from ._tools import _get_tool_name, normalize_tools # pyright: ignore[reportPrivateUsage] - if context.tools is not None: - tools: Any = context.tools - else: - tools = getattr(context.agent, "tools", None) - default_options = getattr(context.agent, "default_options", None) - if tools is None and isinstance(default_options, Mapping): - tools = cast(Any, cast(Mapping[str, Any], default_options).get("tools")) - if tools is None: - return [] - try: - normalized = normalize_tools(tools) - except Exception: - logger.warning("agent-hooks could not normalize the run's tools for the agent_startup projection.") - return [] + merged: list[Any] = [] + + def _extend(source: Any) -> None: + if source is None: + return + try: + merged.extend(normalize_tools(source)) + except Exception: + logger.warning( + "agent-hooks could not normalize the run's tools for the agent_startup projection." + ) + + agent = context.agent + _extend(getattr(agent, "tools", None)) + default_options = getattr(agent, "default_options", None) + if isinstance(default_options, Mapping): + _extend(cast(Mapping[str, Any], default_options).get("tools")) + options = context.options + if isinstance(options, Mapping): + _extend(cast(Mapping[str, Any], options).get("tools")) + _extend(context.tools) + names: list[str] = [] - for item in normalized: + seen: set[str] = set() + for item in merged: name = _get_tool_name(item) - names.append(name if name else type(item).__name__) + label = name if name else type(item).__name__ + if label not in seen: + seen.add(label) + names.append(label) return names diff --git a/python/packages/core/tests/core/test_agent_hooks.py b/python/packages/core/tests/core/test_agent_hooks.py index 7ae28dea87..44a80f88c3 100644 --- a/python/packages/core/tests/core/test_agent_hooks.py +++ b/python/packages/core/tests/core/test_agent_hooks.py @@ -106,6 +106,12 @@ def weather_tool(location: str) -> str: return f"weather in {location}" +@tool(approval_mode="never_require") +def search_tool(query: str) -> str: + """Run a search.""" + return f"results for {query}" + + weather_tool_calls: list[str] = [] @@ -309,6 +315,24 @@ async def test_agent_startup_projects_constructor_registered_tools(chat_client_b assert startup[0]["agent_init"]["tools_registered"] == ["weather_tool"] +@requires_sdk +async def test_agent_startup_merges_run_tools_with_constructor_tools( + chat_client_base: MockBaseChatClient, +) -> None: + guard = AllowGuard() + agent = Agent( + client=chat_client_base, + tools=[weather_tool], + middleware=[create_agent_hooks_middleware([guard])], + ) + + await agent.run("hello", tools=[search_tool]) + + startup = guard.contexts_for("agent_startup") + assert len(startup) == 1 + assert startup[0]["agent_init"]["tools_registered"] == ["weather_tool", "search_tool"] + + @requires_sdk async def test_input_projection_is_faithful(chat_client_base: MockBaseChatClient) -> None: guard = AllowGuard()