-
Notifications
You must be signed in to change notification settings - Fork 2k
Python/.Net: Agent Harness blog post accompanying samples part 2 #6692
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
westey-m
wants to merge
5
commits into
microsoft:main
Choose a base branch
from
westey-m:harness-blog-part2-samples
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
a0a3633
Add samples for the harness blog part 2
westey-m 8f97e78
Address PR comments
westey-m 894846b
Fix blog links.
westey-m f42a859
Address PR comments
westey-m 387a3cd
Fix bug where mode was incorrectly defaulted when reading the mode be…
westey-m File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
26 changes: 26 additions & 0 deletions
26
...s/Harness/BuildYourOwnClaw/Claw_Step02_WorkingWithData/Claw_Step02_WorkingWithData.csproj
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,26 @@ | ||
| <Project Sdk="Microsoft.NET.Sdk"> | ||
|
|
||
| <PropertyGroup> | ||
| <OutputType>Exe</OutputType> | ||
| <TargetFrameworks>net10.0</TargetFrameworks> | ||
|
|
||
| <Nullable>enable</Nullable> | ||
| <ImplicitUsings>enable</ImplicitUsings> | ||
| </PropertyGroup> | ||
|
|
||
| <ItemGroup> | ||
| <PackageReference Include="Azure.Identity" /> | ||
| </ItemGroup> | ||
|
|
||
| <ItemGroup> | ||
| <ProjectReference Include="..\..\..\..\..\src\Microsoft.Agents.AI.Foundry\Microsoft.Agents.AI.Foundry.csproj" /> | ||
| <ProjectReference Include="..\..\..\..\..\src\Microsoft.Agents.AI.Harness\Microsoft.Agents.AI.Harness.csproj" /> | ||
| <ProjectReference Include="..\..\Harness_Shared_Console\Harness_Shared_Console.csproj" /> | ||
| <ProjectReference Include="..\..\Harness_Shared_Console_OpenAI\Harness_Shared_Console_OpenAI.csproj" /> | ||
| </ItemGroup> | ||
|
|
||
| <ItemGroup> | ||
| <Content Include="working\**\*" CopyToOutputDirectory="PreserveNewest" /> | ||
| </ItemGroup> | ||
|
|
||
| </Project> |
180 changes: 180 additions & 0 deletions
180
dotnet/samples/02-agents/Harness/BuildYourOwnClaw/Claw_Step02_WorkingWithData/Program.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,180 @@ | ||
| // Copyright (c) Microsoft. All rights reserved. | ||
|
|
||
| // "Working with your data, safely" — Post 2 of the "Build your own claw and agent harness with Microsoft Agent Framework" series. | ||
| // See: https://devblogs.microsoft.com/agent-framework/agent-harness-working-with-your-data-safely. | ||
| // | ||
| // This sample builds on Post 1's personal finance assistant and adds three abilities: | ||
| // 1. File access — read the user's portfolio.csv and write report files (file_access_* tools). | ||
| // 2. Approvals — the place_trade tool is wrapped so it requires human approval before running. | ||
| // 3. Durable memory — two complementary kinds: | ||
| // * File memory (coarse-grained, explicit) — the agent reads/writes files like | ||
| // watchlist.md. Its files live on disk under {cwd}/agent-file-memory/<session-id>/, | ||
| // so they persist across runs on this machine; /session-export and /session-import | ||
| // preserve the session id so a relaunched session re-links to its memory files. | ||
| // * Foundry memory (fine-grained, automatic) — Microsoft Foundry extracts durable facts | ||
| // (e.g. the user's risk tolerance) from the conversation. Opt-in: enabled | ||
| // only when FOUNDRY_MEMORY_STORE and FOUNDRY_EMBEDDING_MODEL are set. | ||
| // | ||
| // Special commands (handled by the shared HarnessConsole): | ||
| // /todos — Display the current todo list without invoking the agent. | ||
| // /mode — Get or set the current agent mode. | ||
| // /exit — End the session. | ||
|
|
||
| #pragma warning disable OPENAI001 // Suppress experimental API warnings for Responses API usage. | ||
| #pragma warning disable MAAI001 // Suppress experimental API warnings for Agents AI experiments. | ||
|
|
||
| using System.ClientModel.Primitives; | ||
| using Azure.AI.Projects; | ||
| using Azure.Identity; | ||
| using ClawSample; | ||
| using Harness.Shared.Console; | ||
| using Harness.Shared.Console.OpenAI; | ||
| using Harness.Shared.Console.ToolFormatters; | ||
| using Microsoft.Agents.AI; | ||
| using Microsoft.Agents.AI.Foundry; | ||
| using Microsoft.Extensions.AI; | ||
|
|
||
| var endpoint = Environment.GetEnvironmentVariable("FOUNDRY_PROJECT_ENDPOINT") ?? throw new InvalidOperationException("FOUNDRY_PROJECT_ENDPOINT is not set."); | ||
| var deploymentName = Environment.GetEnvironmentVariable("FOUNDRY_MODEL") ?? "gpt-5.4"; | ||
|
|
||
| // <instructions> | ||
| var instructions = | ||
| """ | ||
| ## Personal Finance Assistant Instructions | ||
|
|
||
| You are a personal finance and investing assistant. You help the user understand their | ||
| portfolio and watchlist, and you can place trades on their behalf. | ||
|
|
||
| ### Working style | ||
|
|
||
| - The user's holdings live in a file called portfolio.csv. Read it with the file_access tools | ||
| before answering questions about their portfolio, and never modify it unless asked. | ||
| - When asked for a report or analysis, write it to a Markdown file with the file_access tools | ||
| (e.g. reports/portfolio-review.md) and tell the user where you saved it. | ||
| - Keep the user's watchlist in a memory file called watchlist.md: read it when reviewing the | ||
| watchlist, and update it whenever the user adds or removes a ticker. | ||
| - To buy or sell, use the place_trade tool. This takes a real action, so the user will be | ||
| asked to approve it before it runs — explain what you are about to do first. | ||
| - Remember durable facts the user tells you about themselves (risk tolerance, goals, | ||
| preferences) and take them into account when giving analysis. | ||
|
|
||
| ### Important | ||
|
|
||
| You provide information and analysis only — you are not a licensed financial advisor and you | ||
| must not present your output as personalized investment advice. Remind the user to do their | ||
| own research before making decisions. | ||
| """; | ||
| // </instructions> | ||
|
|
||
| // <create_client> | ||
| // Construct an IChatClient backed by a Microsoft Foundry project (see Post 1 for details). | ||
| var projectClient = new AIProjectClient( | ||
| new Uri(endpoint), | ||
| // WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production. | ||
| // In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid | ||
| // latency issues, unintended credential probing, and potential security risks from fallback mechanisms. | ||
| new DefaultAzureCredential(), | ||
| new AIProjectClientOptions { RetryPolicy = new ClientRetryPolicy(3) }); | ||
|
|
||
| IChatClient chatClient = projectClient | ||
| .GetProjectOpenAIClient() | ||
| .GetResponsesClient() | ||
| .AsIChatClient(deploymentName); | ||
| // </create_client> | ||
|
|
||
| // <memory> | ||
| // Fine-grained Foundry memory is opt-in: it needs a memory store and an embedding model. When the | ||
| // environment is configured we add a FoundryMemoryProvider scoped to a single user, so the facts it | ||
| // extracts are recalled across sessions. Otherwise we fall back to file memory only. | ||
| var memoryStoreName = Environment.GetEnvironmentVariable("FOUNDRY_MEMORY_STORE"); | ||
| var embeddingModel = Environment.GetEnvironmentVariable("FOUNDRY_EMBEDDING_MODEL"); | ||
|
|
||
| FoundryMemoryProvider? foundryMemory = null; | ||
| if (!string.IsNullOrWhiteSpace(memoryStoreName) && !string.IsNullOrWhiteSpace(embeddingModel)) | ||
| { | ||
| foundryMemory = new FoundryMemoryProvider( | ||
| projectClient, | ||
| memoryStoreName, | ||
| // In a real world scenario, "claw-sample-user" should be replaced with a unique identifier | ||
| // for the active user. | ||
| // To tie memories to the session, replace "claw-sample-user" with Guid.NewGuid().ToString(). | ||
| // stateInitializer is called once per session to define the scope for the memory provider. | ||
| stateInitializer: _ => new(new FoundryMemoryProviderScope("claw-sample-user")), | ||
| new FoundryMemoryProviderOptions() | ||
| { | ||
| // For demo purposes, configure the memory provider to extract facts immediately | ||
| // from each message. In a real-world scenario, you may want to set this to a higher value. | ||
| UpdateDelay = 0, | ||
| }); | ||
|
|
||
| // Create the memory store on first use (no-op if it already exists). | ||
| await foundryMemory.EnsureMemoryStoreCreatedAsync( | ||
| deploymentName, | ||
| embeddingModel, | ||
| "Durable memory for the Build-your-own-claw finance assistant."); | ||
|
|
||
| Console.WriteLine($"Foundry memory enabled (store: {memoryStoreName})."); | ||
| } | ||
| else | ||
| { | ||
| Console.WriteLine("Foundry memory disabled. Set FOUNDRY_MEMORY_STORE and FOUNDRY_EMBEDDING_MODEL to enable it."); | ||
| } | ||
| // </memory> | ||
|
|
||
| // <create_agent> | ||
| // Turn the chat client into a HarnessAgent. On top of Post 1's defaults we point file access at a | ||
| // fixed folder, add our approval-gated place_trade tool, and (optionally) wire in the Foundry | ||
| // memory provider for automatic, fine-grained fact extraction. File memory keeps its on-disk default | ||
| // store (see below), and we don't point it at a custom folder. | ||
| AIAgent agent = chatClient.AsHarnessAgent(new HarnessAgentOptions | ||
| { | ||
| // File access: read portfolio.csv and write reports under the sample's working/ folder. | ||
| FileAccessStore = new FileSystemAgentFileStore(Path.Combine(AppContext.BaseDirectory, "working")), | ||
| // Auto-approve the read-only file tools so reading portfolio.csv is frictionless, while saving, | ||
| // deleting, and place_trade still pause for explicit approval. | ||
| ToolApprovalAgentOptions = new ToolApprovalAgentOptions | ||
| { | ||
| AutoApprovalRules = [FileAccessProvider.ReadOnlyToolsAutoApprovalRule], | ||
| }, | ||
| // Start in "execute" mode: this assistant is mostly quick lookups and single actions, so a plan | ||
| // would be overkill. (Planning is still available — switch any time with /mode plan.) | ||
| AgentModeProviderOptions = new AgentModeProviderOptions { DefaultMode = "execute" }, | ||
| // File memory is on by default. Its files live on disk under {cwd}/agent-file-memory/<session-id>/, | ||
| // so they persist across runs on this machine. A brand-new session gets a new id (and so an empty | ||
| // memory); /session-export and /session-import preserve the session's identity so a relaunched | ||
| // session re-links to its existing on-disk memory files. The export file holds session state, not | ||
| // the memory files themselves. | ||
| // Fine-grained, automatic memory (when configured). | ||
| AIContextProviders = foundryMemory is null ? null : [foundryMemory], | ||
| ChatOptions = new ChatOptions | ||
| { | ||
| Instructions = instructions, | ||
| Tools = | ||
| [ | ||
| StockTools.CreateGetStockPriceTool(), | ||
| TradingTools.CreatePlaceTradeTool(), | ||
| ], | ||
| Reasoning = new() { Effort = ReasoningEffort.Medium }, | ||
| }, | ||
| }); | ||
| // </create_agent> | ||
|
|
||
| // <run> | ||
| // Run the interactive console session. The default planning observers already include a tool | ||
| // approval observer, so the place_trade approval prompt is surfaced automatically. | ||
| await HarnessConsole.RunAgentAsync( | ||
| agent, | ||
| userPrompt: "Ask me to review your portfolio, draft a report, update your watchlist, or place a trade.", | ||
| new HarnessConsoleOptions | ||
| { | ||
| Observers = [ | ||
| new OpenAIResponsesWebSearchDisplayObserver(), | ||
| new OpenAIResponsesErrorObserver(), | ||
| .. HarnessConsoleOptions.BuildObserversWithPlanning( | ||
| agent, | ||
| planModeName: "plan", | ||
| executionModeName: "execute", | ||
| toolFormatters: ToolCallFormatter.BuildDefaultToolFormatters())], | ||
| CommandHandlers = HarnessConsoleOptions.BuildDefaultCommandHandlers(agent), | ||
| }); | ||
| // </run> |
73 changes: 73 additions & 0 deletions
73
...amples/02-agents/Harness/BuildYourOwnClaw/Claw_Step02_WorkingWithData/README.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,73 @@ | ||
| # Working with your data, safely (Post 2) — .NET | ||
|
|
||
| The second runnable sample from the [**"Build your own claw and agent harness with Microsoft Agent Framework"** blog](https://devblogs.microsoft.com/agent-framework/build-your-own-claw-and-agent-harness-with-microsoft-agent-framework) | ||
| series ([Part 2 — Working with your data, safely](https://devblogs.microsoft.com/agent-framework/agent-harness-working-with-your-data-safely)). | ||
| It builds on Post 1's personal finance assistant and teaches it to work with *your* data safely. | ||
|
|
||
| ## What this sample demonstrates | ||
|
|
||
| - **File access** — the agent reads a pre-populated `working/portfolio.csv` and writes reports | ||
| (e.g. `reports/portfolio-review.md`) with the built-in `file_access_*` tools. A custom | ||
| `FileAccessStore` roots those tools at the sample's `working/` folder. | ||
| - **Approvals** — file-access tools require approval by default, but the sample wires the built-in | ||
| `FileAccessProvider.ReadOnlyToolsAutoApprovalRule` so reads/lists/searches are frictionless while | ||
| saving and deleting still pause for approval. The `place_trade` tool is also wrapped in an | ||
| `ApprovalRequiredAIFunction` (see `TradingTools.cs`), so the harness surfaces an approval prompt | ||
| before any trade runs. The trade itself is simulated — no real order is placed. | ||
| - **Durable memory, two ways:** | ||
| - **File memory** (coarse-grained, explicit) — the agent reads/writes files such as | ||
| `watchlist.md`. File memory is on by default; its files live on disk under | ||
| `{cwd}/agent-file-memory/<session-id>/`, so they persist across runs on this machine. A new | ||
| session starts empty; use `/session-export` and `/session-import` to preserve the session id so a | ||
| relaunch re-links to its memory files (no fixed folder required). | ||
| - **Foundry memory** (fine-grained, automatic) — Microsoft Foundry extracts durable facts (e.g. | ||
| your risk tolerance) from the conversation. Opt-in; see below. | ||
|
|
||
| ## Prerequisites | ||
|
|
||
| 1. A Microsoft Foundry project with a deployed model (e.g. `gpt-5.4`). | ||
| 2. Azure CLI installed and authenticated (`az login`). | ||
| 3. *(Optional, for Foundry memory)* a deployed embedding model and a memory store name. | ||
|
|
||
| ## Environment variables | ||
|
|
||
| ```bash | ||
| export FOUNDRY_PROJECT_ENDPOINT="https://your-project.services.ai.azure.com/api/projects/your-project" | ||
| # Optional (defaults to gpt-5.4) | ||
| export FOUNDRY_MODEL="gpt-5.4" | ||
|
|
||
| # Optional — enable fine-grained Foundry memory (both must be set): | ||
| export FOUNDRY_MEMORY_STORE="claw-finance-memory" | ||
| export FOUNDRY_EMBEDDING_MODEL="text-embedding-3-small" | ||
| ``` | ||
|
|
||
| When the Foundry memory variables are not set, the sample runs with file memory only and prints a | ||
| note. | ||
|
|
||
| ## Running | ||
|
|
||
| ```bash | ||
| cd dotnet | ||
| dotnet run --project samples/02-agents/Harness/BuildYourOwnClaw/Claw_Step02_WorkingWithData | ||
| ``` | ||
|
|
||
| ## What to expect | ||
|
|
||
| The sample starts an interactive loop in **execute** mode (quick lookups don't need a plan). Try | ||
| these in order: | ||
|
|
||
| 1. `What's in my portfolio?` — the agent reads `portfolio.csv` with the file_access tools. | ||
| 2. `Write me a short report on my portfolio and save it.` — the agent writes a Markdown file | ||
| under `working/`; saving is a write, so **you are prompted to approve** before it lands. | ||
| 3. `I'm a conservative investor saving for a house in two years.` — a durable fact (recalled later | ||
| by Foundry memory when enabled). | ||
| 4. `Buy 10 shares of MSFT.` — the agent calls `place_trade`; **you are prompted to approve or | ||
| deny** before it runs. | ||
| 5. `Add SPY to my watchlist.` — saved to `watchlist.md` in file memory. | ||
|
|
||
| Restart the app to see memory persist across runs: | ||
|
|
||
| - **Foundry memory** (when enabled) recalls facts about you in any new session — it's scoped to you, | ||
| not the session. | ||
| - **File memory** (the watchlist) lives on disk keyed by session id, so `/session-export` before you | ||
| quit and `/session-import` after relaunching to re-link the relaunched session to its files. |
58 changes: 58 additions & 0 deletions
58
dotnet/samples/02-agents/Harness/BuildYourOwnClaw/Claw_Step02_WorkingWithData/StockTools.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,58 @@ | ||
| // Copyright (c) Microsoft. All rights reserved. | ||
|
|
||
| using System.ComponentModel; | ||
| using Microsoft.Extensions.AI; | ||
|
|
||
| namespace ClawSample; | ||
|
|
||
| /// <summary> | ||
| /// A custom function tool that gives our "claw" access to (illustrative) stock prices. | ||
| /// </summary> | ||
| /// <remarks> | ||
| /// The prices returned here are mock data for demonstration purposes only and are not real | ||
| /// market quotes. In a real assistant you would call a market-data API instead. | ||
| /// </remarks> | ||
| internal static class StockTools | ||
| { | ||
| /// <summary>A delayed, illustrative stock quote.</summary> | ||
| public sealed record StockQuote(string Symbol, decimal Price, string Currency, DateTimeOffset AsOf); | ||
|
|
||
| // A tiny in-memory price book so the sample runs without any external dependency. | ||
| private static readonly Dictionary<string, decimal> s_priceBook = new(StringComparer.OrdinalIgnoreCase) | ||
| { | ||
| ["MSFT"] = 462.97m, | ||
| ["AAPL"] = 229.35m, | ||
| ["GOOGL"] = 178.12m, | ||
| ["AMZN"] = 201.45m, | ||
| ["NVDA"] = 134.81m, | ||
| ["SPY"] = 612.40m, | ||
| }; | ||
|
|
||
| /// <summary> | ||
| /// Gets the latest (delayed, illustrative) stock price for a ticker symbol. | ||
| /// </summary> | ||
| /// <param name="symbol">The stock ticker symbol, e.g. <c>MSFT</c> or <c>AAPL</c>.</param> | ||
| [Description("Gets the latest (delayed, illustrative) stock price for a ticker symbol.")] | ||
| public static StockQuote GetStockPrice( | ||
| [Description("The stock ticker symbol, e.g. MSFT or AAPL.")] string symbol) | ||
| { | ||
| if (!s_priceBook.TryGetValue(symbol, out var price)) | ||
| { | ||
| // Deterministic pseudo-price for unknown symbols so the sample stays self-contained. | ||
| // Derive a stable seed from the characters — string.GetHashCode() is randomized per | ||
| // process and Math.Abs(int.MinValue) throws, so neither is safe for repeatable output. | ||
| var seed = 0; | ||
| foreach (var ch in symbol.ToUpperInvariant()) | ||
| { | ||
| seed = (seed * 31 + ch) % 1_000_000; | ||
| } | ||
|
|
||
| price = 50m + seed % 45000 / 100m; | ||
| } | ||
|
|
||
| return new StockQuote(symbol.ToUpperInvariant(), price, "USD", DateTimeOffset.UtcNow); | ||
| } | ||
|
|
||
| /// <summary>Creates the <see cref="AIFunction"/> wrapper used to expose the tool to the agent.</summary> | ||
| public static AIFunction CreateGetStockPriceTool() => AIFunctionFactory.Create(GetStockPrice, "get_stock_price"); | ||
| } | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.