From 1633f543b2ddffb5f0d233c27ddd9c476775a2a5 Mon Sep 17 00:00:00 2001
From: westey <164392973+westey-m@users.noreply.github.com>
Date: Mon, 25 May 2026 14:00:02 +0000
Subject: [PATCH 1/2] Align ModeProvider tool names and instructions
---
.../ToolFormatters/ModeToolFormatter.cs | 2 +-
.../Harness/AgentMode/AgentModeProvider.cs | 14 ++--
.../AgentMode/AgentModeProviderTests.cs | 22 +++---
.../core/agent_framework/_harness/_mode.py | 69 +++++++++++++------
.../core/tests/core/test_harness_mode.py | 22 +++---
5 files changed, 80 insertions(+), 49 deletions(-)
diff --git a/dotnet/samples/02-agents/Harness/Harness_Shared_Console/ToolFormatters/ModeToolFormatter.cs b/dotnet/samples/02-agents/Harness/Harness_Shared_Console/ToolFormatters/ModeToolFormatter.cs
index 940a810c59..09e6ca6ce1 100644
--- a/dotnet/samples/02-agents/Harness/Harness_Shared_Console/ToolFormatters/ModeToolFormatter.cs
+++ b/dotnet/samples/02-agents/Harness/Harness_Shared_Console/ToolFormatters/ModeToolFormatter.cs
@@ -15,7 +15,7 @@ public sealed class ModeToolFormatter : ToolCallFormatter
///
public override string? FormatDetail(FunctionCallContent call) => call.Name switch
{
- "AgentMode_Set" => FormatStringArg(call, "mode"),
+ "mode_set" => FormatStringArg(call, "mode"),
_ => null,
};
diff --git a/dotnet/src/Microsoft.Agents.AI/Harness/AgentMode/AgentModeProvider.cs b/dotnet/src/Microsoft.Agents.AI/Harness/AgentMode/AgentModeProvider.cs
index a7f4aca286..32cabc07ac 100644
--- a/dotnet/src/Microsoft.Agents.AI/Harness/AgentMode/AgentModeProvider.cs
+++ b/dotnet/src/Microsoft.Agents.AI/Harness/AgentMode/AgentModeProvider.cs
@@ -29,8 +29,8 @@ namespace Microsoft.Agents.AI;
///
/// This provider exposes the following tools to the agent:
///
-/// - AgentMode_Set — Switch the agent's operating mode.
-/// - AgentMode_Get — Retrieve the agent's current operating mode.
+/// - mode_set — Switch the agent's operating mode.
+/// - mode_get — Retrieve the agent's current operating mode.
///
///
///
@@ -49,8 +49,8 @@ public sealed class AgentModeProvider : AIContextProvider
- You must check the current mode after any user input, since the user may have changed the mode themselves,
e.g. the user may have switched to 'plan' mode after a previous research task finished in 'execute' mode, meaning they want to review a plan first before execution.
- Use the AgentMode_Get tool to check your current operating mode.
- Use the AgentMode_Set tool to switch between modes as your work progresses. Only use AgentMode_Set if the user explicitly instructs/allows you to change modes.
+ Use the mode_get tool to check your current operating mode.
+ Use the mode_set tool to switch between modes as your work progresses. Only use mode_set if the user explicitly instructs/allows you to change modes.
You are currently operating in the {current_mode} mode.
@@ -79,7 +79,7 @@ 3. Do not proceed until you have received all the needed clarifications.
4. Do short exploratory research if it helps with being able to ask sensible clarifications from the user.
5. Write the plan to a memory file, so that it is retained even if compaction happens. Make sure to update the plan file if the user requests changes.
6. Present the plan to the user and ask for approval to switch to execute mode and process the plan.
- 7. When approval is granted, always switch to execute mode (using the `AgentMode_Set` tool), and follow the steps for *Execute mode*.
+ 7. When approval is granted, always switch to execute mode (using the `mode_set` tool), and follow the steps for *Execute mode*.
"""),
new(
"execute",
@@ -263,7 +263,7 @@ private AITool[] CreateTools(AgentModeState state, AgentSession? session)
},
new AIFunctionFactoryOptions
{
- Name = "AgentMode_Set",
+ Name = "mode_set",
Description = $"Switch the agent's operating mode. Supported modes: \"{this._modeNamesDisplay}\".",
SerializerOptions = serializerOptions,
}),
@@ -272,7 +272,7 @@ private AITool[] CreateTools(AgentModeState state, AgentSession? session)
() => state.CurrentMode,
new AIFunctionFactoryOptions
{
- Name = "AgentMode_Get",
+ Name = "mode_get",
Description = "Get the agent's current operating mode.",
SerializerOptions = serializerOptions,
}),
diff --git a/dotnet/tests/Microsoft.Agents.AI.UnitTests/Harness/AgentMode/AgentModeProviderTests.cs b/dotnet/tests/Microsoft.Agents.AI.UnitTests/Harness/AgentMode/AgentModeProviderTests.cs
index 61671d4904..6bcda66980 100644
--- a/dotnet/tests/Microsoft.Agents.AI.UnitTests/Harness/AgentMode/AgentModeProviderTests.cs
+++ b/dotnet/tests/Microsoft.Agents.AI.UnitTests/Harness/AgentMode/AgentModeProviderTests.cs
@@ -73,7 +73,7 @@ public async Task SetMode_ChangesModeAsync()
{
// Arrange
var (tools, state) = await CreateToolsWithStateAsync();
- AIFunction setMode = GetTool(tools, "AgentMode_Set");
+ AIFunction setMode = GetTool(tools, "mode_set");
// Act
await setMode.InvokeAsync(new AIFunctionArguments() { ["mode"] = "execute" });
@@ -90,7 +90,7 @@ public async Task SetMode_ReturnsConfirmationAsync()
{
// Arrange
var (tools, _) = await CreateToolsWithStateAsync();
- AIFunction setMode = GetTool(tools, "AgentMode_Set");
+ AIFunction setMode = GetTool(tools, "mode_set");
// Act
object? result = await setMode.InvokeAsync(new AIFunctionArguments() { ["mode"] = "execute" });
@@ -107,8 +107,8 @@ public async Task SetMode_InvalidMode_ThrowsAsync()
{
// Arrange
var (tools, provider, session) = await CreateToolsWithProviderAndSessionAsync();
- AIFunction setMode = GetTool(tools, "AgentMode_Set");
- AIFunction getMode = GetTool(tools, "AgentMode_Get");
+ AIFunction setMode = GetTool(tools, "mode_set");
+ AIFunction getMode = GetTool(tools, "mode_get");
// Act & Assert
await Assert.ThrowsAsync(async () =>
@@ -131,7 +131,7 @@ public async Task GetMode_ReturnsDefaultModeAsync()
{
// Arrange
var (tools, _) = await CreateToolsWithStateAsync();
- AIFunction getMode = GetTool(tools, "AgentMode_Get");
+ AIFunction getMode = GetTool(tools, "mode_get");
// Act
object? result = await getMode.InvokeAsync(new AIFunctionArguments());
@@ -148,8 +148,8 @@ public async Task GetMode_ReturnsUpdatedModeAfterSetAsync()
{
// Arrange
var (tools, _) = await CreateToolsWithStateAsync();
- AIFunction setMode = GetTool(tools, "AgentMode_Set");
- AIFunction getMode = GetTool(tools, "AgentMode_Get");
+ AIFunction setMode = GetTool(tools, "mode_set");
+ AIFunction getMode = GetTool(tools, "mode_get");
// Act
await setMode.InvokeAsync(new AIFunctionArguments() { ["mode"] = "execute" });
@@ -236,7 +236,7 @@ public async Task PublicSetMode_ReflectedInToolResultsAsync()
// Act
AIContext result = await provider.InvokingAsync(context);
- AIFunction getMode = GetTool(result.Tools!, "AgentMode_Get");
+ AIFunction getMode = GetTool(result.Tools!, "mode_get");
object? modeResult = await getMode.InvokeAsync(new AIFunctionArguments());
// Assert
@@ -264,12 +264,12 @@ public async Task State_PersistsAcrossInvocationsAsync()
// Act — first invocation changes mode
AIContext result1 = await provider.InvokingAsync(context);
- AIFunction setMode = GetTool(result1.Tools!, "AgentMode_Set");
+ AIFunction setMode = GetTool(result1.Tools!, "mode_set");
await setMode.InvokeAsync(new AIFunctionArguments() { ["mode"] = "execute" });
// Second invocation should see the updated mode
AIContext result2 = await provider.InvokingAsync(context);
- AIFunction getMode = GetTool(result2.Tools!, "AgentMode_Get");
+ AIFunction getMode = GetTool(result2.Tools!, "mode_get");
object? modeResult = await getMode.InvokeAsync(new AIFunctionArguments());
// Assert
@@ -579,7 +579,7 @@ public async Task ToolModeChange_DoesNotInjectNotificationAsync()
// First call to initialize
AIContext result1 = await provider.InvokingAsync(context);
- AIFunction setMode = GetTool(result1.Tools!, "AgentMode_Set");
+ AIFunction setMode = GetTool(result1.Tools!, "mode_set");
// Change mode via the tool (agent-initiated)
await setMode.InvokeAsync(new AIFunctionArguments() { ["mode"] = "execute" });
diff --git a/python/packages/core/agent_framework/_harness/_mode.py b/python/packages/core/agent_framework/_harness/_mode.py
index e34df0dffa..decc9e7dd2 100644
--- a/python/packages/core/agent_framework/_harness/_mode.py
+++ b/python/packages/core/agent_framework/_harness/_mode.py
@@ -14,14 +14,19 @@
DEFAULT_MODE_SOURCE_ID = "agent_mode"
DEFAULT_MODE_INSTRUCTIONS = (
"## Agent Mode\n\n"
- "You can operate in different modes. Depending on the mode you are in, "
- "you will be required to follow different processes.\n\n"
- "Use the get_mode tool to check your current operating mode.\n"
- "Use the set_mode tool to switch between modes as your work progresses. "
- "Only use set_mode if the user explicitly instructs/allows you to change modes.\n\n"
+ "- You can operate in different modes. Depending on the mode you are in, "
+ "you will be required to follow different processes.\n"
+ "- You must check the current mode after any user input, since the user may have changed the mode themselves, "
+ "e.g. the user may have switched to 'plan' mode after a previous research task finished in 'execute' mode, "
+ "meaning they want to review a plan first before execution.\n\n"
+ "Use the mode_get tool to check your current operating mode.\n"
+ "Use the mode_set tool to switch between modes as your work progresses. "
+ "Only use mode_set if the user explicitly instructs/allows you to change modes.\n\n"
+ "You are currently operating in the {current_mode} mode.\n\n"
+ "### Mandatory Mode based Workflow\n\n"
+ "For every new substantive user request, including short factual questions, "
+ "your behavior is determined by the mode you are in.\n\n"
"{available_modes}\n"
- "\n"
- "You are currently operating in the {current_mode} mode.\n"
)
DEFAULT_MODE_CHANGE_NOTIFICATION = (
'[Mode changed: The operating mode has been switched from "{previous_mode}" to "{current_mode}". '
@@ -31,13 +36,37 @@
"plan": (
"Use this mode when analyzing requirements, breaking down tasks, and creating plans. "
"This is the interactive mode — ask clarifying questions, discuss options, and get user approval before "
- "proceeding."
+ "proceeding.\n\n"
+ "Process to follow when in plan mode:\n"
+ "1. Analyze the request with the purpose of building a research plan.\n"
+ "2. Create a list of todo items.\n"
+ "3. If needed, use the provided tools to do some exploratory checks to help build a plan and determine "
+ "what clarifying questions you may need from the user.\n"
+ "4. Ask for clarifications from the user where needed.\n"
+ " 1. Ask each clarification one by one.\n"
+ " 2. When asking for clarification and you have specific options in mind, present them to the user, "
+ "so they can choose the option instead of having to retype the entire response.\n"
+ " 3. Do not proceed until you have received all the needed clarifications.\n"
+ " 4. Do short exploratory research if it helps with being able to ask sensible clarifications from "
+ "the user.\n"
+ "5. Write the plan to a memory file, so that it is retained even if compaction happens. "
+ "Make sure to update the plan file if the user requests changes.\n"
+ "6. Present the plan to the user and ask for approval to switch to execute mode and process the plan.\n"
+ "7. When approval is granted, always switch to execute mode (using the `mode_set` tool), "
+ "and follow the steps for *Execute mode*."
),
"execute": (
- "Use this mode when carrying out approved plans. Work autonomously using your best judgement — do not ask "
- "the user questions or wait for feedback. Make reasonable decisions on your own so that there is a complete, "
- "useful result when the user returns. If you encounter ambiguity, choose the most reasonable option and note "
- "your choice."
+ "Use this mode when carrying out approved plans. Work autonomously using your best judgment — do not ask "
+ "the user questions or wait for feedback.\n\n"
+ "Process to follow when in execute mode:\n"
+ "1. If you don't have a plan or tasks yet, analyze the user request and create tasks and a plan. "
+ "(**Skip this step if you came from plan mode**)\n"
+ "2. Work autonomously — use your best judgment to make decisions and keep progressing without asking "
+ "the user questions. The goal is to have a complete, useful result ready when the user returns.\n"
+ "3. If you encounter ambiguity or an unexpected situation during execution, choose the most reasonable "
+ "option, note your choice, and keep going.\n"
+ "4. Mark tasks as completed as you finish them.\n"
+ "5. Continue working, thinking and calling tools until you have the research result for the user."
),
}
@@ -179,8 +208,8 @@ class AgentModeProvider(ContextProvider):
``"plan"`` (interactive planning) and ``"execute"`` (autonomous execution).
This provider exposes the following tools to the agent:
- - ``set_mode``: Switch the agent's operating mode.
- - ``get_mode``: Retrieve the agent's current operating mode.
+ - ``mode_set``: Switch the agent's operating mode.
+ - ``mode_get``: Retrieve the agent's current operating mode.
Public helper functions ``get_agent_mode`` and ``set_agent_mode`` allow external code to programmatically read
and change the mode.
@@ -223,7 +252,7 @@ def __init__(
def _build_instructions(self, current_mode: str) -> str:
"""Build the mode guidance injected for the current session."""
mode_lines = "".join(
- f'- "{self._mode_display_names[mode]}": {description}\n'
+ f"#### {self._mode_display_names[mode]}\n\n{description}\n\n"
for mode, description in self.mode_descriptions.items()
)
instructions = self.instructions or DEFAULT_MODE_INSTRUCTIONS
@@ -257,8 +286,8 @@ async def before_run(
provider_state = _get_mode_state(session, source_id=self.source_id)
previous_mode = provider_state.pop(_PREVIOUS_MODE_STATE_KEY, None)
- @tool(name="set_mode", approval_mode="never_require")
- def set_mode(mode: str) -> str:
+ @tool(name="mode_set", approval_mode="never_require")
+ def mode_set(mode: str) -> str:
"""Switch the agent's operating mode."""
# The agent invoked the tool itself, so it knows the mode just changed — bypass
# ``set_agent_mode`` to avoid triggering a notification message on the next turn.
@@ -267,8 +296,8 @@ def set_mode(mode: str) -> str:
tool_state["current_mode"] = normalized_mode
return json.dumps({"mode": normalized_mode, "message": f"Mode changed to '{normalized_mode}'."})
- @tool(name="get_mode", approval_mode="never_require")
- def get_mode() -> str:
+ @tool(name="mode_get", approval_mode="never_require")
+ def mode_get() -> str:
"""Get the agent's current operating mode."""
current_mode_value = get_agent_mode(
session,
@@ -282,7 +311,7 @@ def get_mode() -> str:
self.source_id,
[self._build_instructions(current_mode)],
)
- context.extend_tools(self.source_id, [set_mode, get_mode])
+ context.extend_tools(self.source_id, [mode_set, mode_get])
if isinstance(previous_mode, str) and previous_mode != current_mode:
# Inject a user-role message announcing the external mode change. System instructions
# always render first in the chat history, so the agent can otherwise stay anchored to
diff --git a/python/packages/core/tests/core/test_harness_mode.py b/python/packages/core/tests/core/test_harness_mode.py
index cb63007343..915379fbd8 100644
--- a/python/packages/core/tests/core/test_harness_mode.py
+++ b/python/packages/core/tests/core/test_harness_mode.py
@@ -95,8 +95,10 @@ async def test_agent_mode_context_provider_normalizes_custom_modes(
)
instructions = options["instructions"]
assert isinstance(instructions, str)
- assert '"Draft": Draft it.' in instructions
- assert '"Final": Finalize it.' in instructions
+ assert "#### Draft" in instructions
+ assert "Draft it." in instructions
+ assert "#### Final" in instructions
+ assert "Finalize it." in instructions
assert "You are currently operating in the draft mode." in instructions
assert (
@@ -125,8 +127,8 @@ async def test_agent_mode_context_provider_serializes_tool_outputs_as_json(
)
tools = options["tools"]
assert isinstance(tools, list)
- get_mode_tool = _tool_by_name(tools, "get_mode")
- set_mode_tool = _tool_by_name(tools, "set_mode")
+ get_mode_tool = _tool_by_name(tools, "mode_get")
+ set_mode_tool = _tool_by_name(tools, "mode_set")
initial_mode = await get_mode_tool.invoke()
assert json.loads(initial_mode[0].text) == {"mode": mode_name}
@@ -152,13 +154,13 @@ async def test_agent_mode_context_provider_updates_agent_mode(
instructions = options["instructions"]
assert isinstance(instructions, str)
assert "## Agent Mode" in instructions
- assert "Use the set_mode tool to switch between modes as your work progresses." in instructions
+ assert "Use the mode_set tool to switch between modes as your work progresses." in instructions
assert "ask clarifying questions, discuss options, and get user approval before proceeding" in instructions
- assert "If you encounter ambiguity, choose the most reasonable option and note your choice" in instructions
+ assert "If you encounter ambiguity" in instructions
assert "You are currently operating in the plan mode." in instructions
- get_mode_tool = _tool_by_name(tools, "get_mode")
- set_mode_tool = _tool_by_name(tools, "set_mode")
+ get_mode_tool = _tool_by_name(tools, "mode_get")
+ set_mode_tool = _tool_by_name(tools, "mode_set")
initial_mode = await get_mode_tool.invoke()
assert json.loads(initial_mode[0].text) == {"mode": "plan"}
@@ -218,13 +220,13 @@ async def test_agent_mode_provider_injects_user_message_after_external_change(
provider = AgentModeProvider()
agent = Agent(client=chat_client_base, context_providers=[provider])
- # First run: agent uses set_mode tool to switch to execute. The tool path must NOT queue a
+ # First run: agent uses mode_set tool to switch to execute. The tool path must NOT queue a
# notification because the agent already saw its own tool call in the chat history.
_, first_options = await agent._prepare_session_and_messages( # type: ignore[reportPrivateUsage]
session=session,
input_messages=[Message(role="user", contents=["Plan first."])],
)
- set_mode_tool = _tool_by_name(first_options["tools"], "set_mode")
+ set_mode_tool = _tool_by_name(first_options["tools"], "mode_set")
await set_mode_tool.invoke(arguments={"mode": "execute"})
assert "previous_mode_for_notification" not in session.state[provider.source_id]
From cb5c4d1bb5b806f82e0f1f6c81a79145445f6fb7 Mon Sep 17 00:00:00 2001
From: westey <164392973+westey-m@users.noreply.github.com>
Date: Mon, 25 May 2026 14:23:56 +0000
Subject: [PATCH 2/2] Address PR comments
---
.../ToolFormatters/ModeToolFormatter.cs | 4 ++--
python/packages/core/agent_framework/_harness/_mode.py | 2 +-
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/dotnet/samples/02-agents/Harness/Harness_Shared_Console/ToolFormatters/ModeToolFormatter.cs b/dotnet/samples/02-agents/Harness/Harness_Shared_Console/ToolFormatters/ModeToolFormatter.cs
index 09e6ca6ce1..25ccaf113a 100644
--- a/dotnet/samples/02-agents/Harness/Harness_Shared_Console/ToolFormatters/ModeToolFormatter.cs
+++ b/dotnet/samples/02-agents/Harness/Harness_Shared_Console/ToolFormatters/ModeToolFormatter.cs
@@ -5,12 +5,12 @@
namespace Harness.Shared.Console.ToolFormatters;
///
-/// Formats AgentMode_* tool calls, showing the target mode for Set operations.
+/// Formats mode_* tool calls, showing the target mode for Set operations.
///
public sealed class ModeToolFormatter : ToolCallFormatter
{
///
- public override bool CanFormat(FunctionCallContent call) => call.Name.StartsWith("AgentMode_", StringComparison.Ordinal);
+ public override bool CanFormat(FunctionCallContent call) => call.Name.StartsWith("mode_", StringComparison.Ordinal);
///
public override string? FormatDetail(FunctionCallContent call) => call.Name switch
diff --git a/python/packages/core/agent_framework/_harness/_mode.py b/python/packages/core/agent_framework/_harness/_mode.py
index decc9e7dd2..3278f73f6d 100644
--- a/python/packages/core/agent_framework/_harness/_mode.py
+++ b/python/packages/core/agent_framework/_harness/_mode.py
@@ -315,7 +315,7 @@ def mode_get() -> str:
if isinstance(previous_mode, str) and previous_mode != current_mode:
# Inject a user-role message announcing the external mode change. System instructions
# always render first in the chat history, so the agent can otherwise stay anchored to
- # the most recent ``set_mode`` tool call rather than the new mode.
+ # the most recent ``mode_set`` tool call rather than the new mode.
previous_display = self._mode_display_names.get(previous_mode, previous_mode)
current_display = self._mode_display_names.get(current_mode, current_mode)
notification = DEFAULT_MODE_CHANGE_NOTIFICATION.format(