Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,20 @@ namespace Microsoft.Agents.AI;
[Experimental(DiagnosticIds.Experiments.AgentsAIExperiments)]
public sealed class AgentModeProvider : AIContextProvider
{
private const string DefaultInstructions =
"""
## Agent Mode

You can operate in different modes. Dependeing on the mode you are in, you will be required to follow different processes.
Comment thread
westey-m marked this conversation as resolved.
Outdated

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.

{available_modes}

You are currently operating in the {current_mode} mode.
""";

private static readonly IReadOnlyList<AgentModeProviderOptions.AgentMode> s_defaultModes =
[
new("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."),
Expand All @@ -50,7 +64,7 @@ public sealed class AgentModeProvider : AIContextProvider
private readonly ProviderSessionState<AgentModeState> _sessionState;
private readonly IReadOnlyList<AgentModeProviderOptions.AgentMode> _modes;
private readonly string _defaultMode;
private readonly string? _customInstructions;
private readonly string? _instructions;
private readonly HashSet<string> _validModeNames;
private readonly string _modeNamesDisplay;
private IReadOnlyList<string>? _stateKeys;
Expand All @@ -68,7 +82,7 @@ public AgentModeProvider(AgentModeProviderOptions? options = null)
throw new ArgumentException("At least one mode must be configured.", nameof(options));
}

this._customInstructions = options?.Instructions;
this._instructions = options?.Instructions ?? DefaultInstructions;

this._validModeNames = new HashSet<string>(StringComparer.Ordinal);
var modeNamesList = new List<string>(this._modes.Count);
Expand Down Expand Up @@ -147,7 +161,7 @@ protected override ValueTask<AIContext> ProvideAIContextAsync(InvokingContext co
{
AgentModeState state = this._sessionState.GetOrInitializeState(context.Session);

string instructions = this._customInstructions ?? this.BuildDefaultInstructions(state.CurrentMode);
string instructions = this.BuildInstructions(state.CurrentMode);

var aiContext = new AIContext
{
Expand All @@ -171,22 +185,20 @@ protected override ValueTask<AIContext> ProvideAIContextAsync(InvokingContext co
return new ValueTask<AIContext>(aiContext);
}

private string BuildDefaultInstructions(string currentMode)
private string BuildInstructions(string currentMode)
{
var sb = new StringBuilder();
sb.Append($"You are currently operating in \"{currentMode}\" mode.");
sb.AppendLine();
sb.AppendLine("Available modes:");

// Build list of modes text:
var modesListBuilder = new StringBuilder();
foreach (var mode in this._modes)
{
sb.AppendLine($"- \"{mode.Name}\": {mode.Description}");
modesListBuilder.AppendLine($"- \"{mode.Name}\": {mode.Description}");
}
var modesListText = modesListBuilder.ToString();

sb.AppendLine("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.");
sb.Append("Use the AgentMode_Get tool to check your current operating mode.");

return sb.ToString();
return new StringBuilder(this._instructions)
.Replace("{available_modes}", modesListText)
.Replace("{current_mode}", currentMode)
.ToString();
}

private void ValidateMode(string mode)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@ public sealed class AgentModeProviderOptions
/// <summary>
/// Gets or sets custom instructions provided to the agent for using the mode tools.
/// </summary>
/// <remarks>
/// The instructions must contain a <c>{available_modes}</c> placeholder for the provider to inject the
/// curently available list of modes, and a <c>{current_mode}</c> placeholder to inject the currently
Comment thread
westey-m marked this conversation as resolved.
Outdated
/// active mode.
/// </remarks>
/// <value>
/// When <see langword="null"/> (the default), the provider generates instructions dynamically
/// from the configured <see cref="Modes"/> list.
Comment thread
westey-m marked this conversation as resolved.
Outdated
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,18 +42,22 @@ namespace Microsoft.Agents.AI;
public sealed class FileMemoryProvider : AIContextProvider
{
private const string DescriptionSuffix = "_description.md";
private const string MemoryIndexFileName = "memories.md";
private const int MaxIndexEntries = 50;

private const string DefaultInstructions =
"""
You have access to a file-based memory system via the FileMemory_* tools for storing and retrieving information across interactions.
Use FileMemory_SaveFile to store one memory per file with a clear, descriptive file name (e.g., "projectarchitecture.md", "userpreferences.md").
For large files, include a description when saving to provide a summary that helps with discovery.
Before starting new tasks, use FileMemory_ListFiles and FileMemory_SearchFiles to check for relevant existing memories.
Use FileMemory_ReadFile to retrieve file contents and FileMemory_DeleteFile to remove outdated memories.
Keep memories up-to-date by overwriting files when information changes.
When you receive large amounts of data (e.g., downloaded web pages, API responses, research results),
save them to files if they will be required later, so that they are not lost when older context is compacted or truncated.
This ensures important data remains accessible across long-running sessions.
## File Based Memory
You have access to a file-based memory system via the `FileMemory_*` tools for storing and retrieving information across interactions.
Use these tools to store plans, memories, processing results, or downloaded data.

- Use descriptive file names (e.g., "projectarchitecture.md", "userpreferences.md").
- Include a description when saving a file to help with future discovery.
- Before starting new tasks, use FileMemory_ListFiles and FileMemory_SearchFiles to check for relevant existing memories.
- Keep memories up-to-date by overwriting files when information changes.
- When you receive large amounts of data (e.g., downloaded web pages, API responses, research results),
save them to files if they will be required later, so that they are not lost when older context is compacted or truncated.
This ensures important data remains accessible across long-running sessions.
""";

private readonly AgentFileStore _fileStore;
Expand Down Expand Up @@ -99,11 +103,27 @@ protected override async ValueTask<AIContext> ProvideAIContextAsync(InvokingCont
await this._fileStore.CreateDirectoryAsync(state.WorkingFolder, cancellationToken).ConfigureAwait(false);
}

return new AIContext
var aiContext = new AIContext
{
Instructions = this._instructions,
Tools = this._tools ??= this.CreateTools(),
};

// Inject the memory index as a user message so the agent knows what memories are available.
string indexPath = CombinePaths(state.WorkingFolder, MemoryIndexFileName);
string? indexContent = await this._fileStore.ReadFileAsync(indexPath, cancellationToken).ConfigureAwait(false);
if (!string.IsNullOrWhiteSpace(indexContent))
{
aiContext.Messages =
[
new ChatMessage(ChatRole.User,
"The following is your memory index — a list of files you have previously saved. " +
"You can read any of these files using the FileMemory_ReadFile tool.\n\n" +
indexContent),
];
}

return aiContext;
}

/// <summary>
Expand Down Expand Up @@ -135,9 +155,12 @@ private async Task<string> SaveFileAsync(string fileName, string content, string
await this._fileStore.DeleteFileAsync(descPath, cancellationToken).ConfigureAwait(false);
}

return string.IsNullOrWhiteSpace(description)
string result = string.IsNullOrWhiteSpace(description)
? $"File '{fileName}' saved."
: $"File '{fileName}' saved with description.";

await this.RebuildMemoryIndexAsync(state, cancellationToken).ConfigureAwait(false);
return result;
}

/// <summary>
Expand Down Expand Up @@ -173,6 +196,7 @@ private async Task<string> DeleteFileAsync(string fileName, CancellationToken ca
string descPath = ResolvePath(state.WorkingFolder, GetDescriptionFileName(fileName));
await this._fileStore.DeleteFileAsync(descPath, cancellationToken).ConfigureAwait(false);

await this.RebuildMemoryIndexAsync(state, cancellationToken).ConfigureAwait(false);
return deleted ? $"File '{fileName}' deleted." : $"File '{fileName}' not found.";
}

Expand Down Expand Up @@ -204,6 +228,12 @@ private async Task<List<FileListEntry>> ListFilesAsync(CancellationToken cancell
continue;
}

// Hide the memory index file from the listing.
if (file.Equals(MemoryIndexFileName, StringComparison.OrdinalIgnoreCase))
{
continue;
}

string? fileDescription = null;
string descFileName = GetDescriptionFileName(file);

Expand Down Expand Up @@ -251,6 +281,54 @@ private AITool[] CreateTools()
];
}

/// <summary>
/// Rebuilds the <c>memories.md</c> index file by listing all user files in the working folder,
/// reading their companion description files, and writing a markdown summary capped at <see cref="MaxIndexEntries"/> entries.
/// </summary>
private async Task RebuildMemoryIndexAsync(FileMemoryState state, CancellationToken cancellationToken)
Comment thread
westey-m marked this conversation as resolved.
{
IReadOnlyList<string> fileNames = await this._fileStore.ListFilesAsync(state.WorkingFolder, cancellationToken).ConfigureAwait(false);

var sb = new System.Text.StringBuilder();
sb.AppendLine("# Memory Index");
sb.AppendLine();

int count = 0;
foreach (string file in fileNames)
{
// Skip system files: description companions and the index itself.
if (file.EndsWith(DescriptionSuffix, StringComparison.OrdinalIgnoreCase) ||
file.Equals(MemoryIndexFileName, StringComparison.OrdinalIgnoreCase))
{
continue;
}
Comment thread
westey-m marked this conversation as resolved.

if (count >= MaxIndexEntries)
{
break;
}

string? description = null;
string descFileName = GetDescriptionFileName(file);
string descPath = CombinePaths(state.WorkingFolder, descFileName);
description = await this._fileStore.ReadFileAsync(descPath, cancellationToken).ConfigureAwait(false);

if (!string.IsNullOrWhiteSpace(description))
{
sb.AppendLine($"- **{file}**: {description}");
}
else
{
sb.AppendLine($"- **{file}**");
}

count++;
}

string indexPath = CombinePaths(state.WorkingFolder, MemoryIndexFileName);
await this._fileStore.WriteFileAsync(indexPath, sb.ToString(), cancellationToken).ConfigureAwait(false);
}

private static string GetDescriptionFileName(string fileName)
{
int extIndex = fileName.LastIndexOf('.');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,18 +40,15 @@ public sealed class SubAgentsProvider : AIContextProvider
{
private const string DefaultInstructions =
"""
## SubAgents
You have access to sub-agents that can perform work on your behalf.
Use the `SubAgents_*` list of tools to start tasks on sub agents and check their results.
Creating a sub task does not block, and sub-tasks run concurrently.
Important: Always wait for outstanding tasks to finish before you finish processing.
Important: After retrieving results from a completed task, clear it with SubAgents_ClearCompletedTask to free memory, unless you plan to continue it with SubAgents_ContinueTask.

Use SubAgents_StartTask to delegate work to a sub-agent. This will send the task to the sub agent and return immediately. Sub-tasks run concurrently.
Use SubAgents_WaitForFirstCompletion to block until one of the specified tasks finishes.
Use SubAgents_GetTaskResults to retrieve the output of a completed task.
Use SubAgents_GetAllTasks to see the status of all sub-tasks.
Use SubAgents_ContinueTask to send follow-up input to a completed sub-task (e.g., provide clarification or additional instructions).
Use SubAgents_ClearCompletedTask to remove a completed task and free its memory after you no longer need its results or session.

- Use the `SubAgents_*` list of tools to start tasks on sub agents and check their results.
- Creating a sub task does not block, and sub-tasks run concurrently.
- Important: Always wait for outstanding tasks to finish before you finish processing.
- Important: After retrieving results from a completed task, clear it with SubAgents_ClearCompletedTask to free memory, unless you plan to continue it with SubAgents_ContinueTask.

{sub_agents}
""";

private readonly Dictionary<string, AIAgent> _agents;
Expand All @@ -77,7 +74,7 @@ public SubAgentsProvider(IEnumerable<AIAgent> agents, SubAgentsProviderOptions?
string agentListText = options?.AgentListBuilder is not null
? options.AgentListBuilder(this._agents)
: BuildDefaultAgentListText(this._agents);
this._instructions = baseInstructions + "\n" + agentListText;
this._instructions = baseInstructions.Replace("{sub_agents}", agentListText);

this._sessionState = new ProviderSessionState<SubAgentState>(
_ => new SubAgentState(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ public sealed class SubAgentsProviderOptions
/// <summary>
/// Gets or sets custom instructions provided to the agent for using the sub-agent tools.
/// </summary>
/// <remarks>
/// Use the <c>{sub_agents}</c> placeholder to allow the provider to inject
/// the formatted list of available sub agents.
/// </remarks>
/// <value>
/// When <see langword="null"/> (the default), the provider uses built-in instructions
/// that guide the agent on how to use the sub-agent tools.
Expand Down
2 changes: 2 additions & 0 deletions dotnet/src/Microsoft.Agents.AI/Harness/Todo/TodoProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ public sealed class TodoProvider : AIContextProvider
{
private const string DefaultInstructions =
"""
## Todo Items

You have access to a todo list for tracking work items.
While planning, make sure that you break down complex tasks into manageable todo items and add them to the list.
Ask questions from the user where clarification is needed to create effective todos.
Expand Down
Loading
Loading