Part 2 of Build your own claw and agent harness with Microsoft Agent Framework.
In Part 1 we stood up a harness and gave our personal finance assistant its first abilities: a custom tool, web search, and planning. It can talk about the markets – but it can’t yet touch your data, and nothing stops it from taking a sensitive action on a whim.
This part fixes both, using three abilities that are all included in the harness:
- File access – read your portfolio from a CSV and write reports back to disk.
- Approvals – gate risky actions (like placing a trade) behind a human’s explicit OK.
- Durable memory – remember things across sessions, in two complementary ways.
As before, we only supply what makes our agent ours; the harness provides the machinery. Let’s take the three in turn.
Give it your data: file access
Our assistant should work from the user’s actual holdings, not made-up numbers. The harness includes a file-access provider that exposes a set of file_access_* tools – list, read, write, search – over a default folder. We point it at a working/ folder that already contains a portfolio.csv.
C#:
AIAgent agent = chatClient.AsHarnessAgent(new HarnessAgentOptions
{
// The agent can read/write files under this folder via the file_access_* tools.
FileAccessStore = new FileSystemAgentFileStore(Path.Combine(AppContext.BaseDirectory, "working")),
ChatOptions = new ChatOptions { Instructions = instructions, Tools = [/* ... */] },
});
Python:
from agent_framework import FileSystemAgentFileStore
agent = create_harness_agent(
client=client,
agent_instructions=FINANCE_INSTRUCTIONS,
tools=[get_stock_price, place_trade],
# The agent can read/write files under this folder via the file_access_* tools.
file_access_store=FileSystemAgentFileStore("working"),
)
Amongst others, the agent now has six tools – file_access_read_file, file_access_save_file, file_access_list_files, file_access_list_subdirectories, file_access_search_files, and file_access_delete_file – and we include the following instructions to tell it how to use them:
The user’s holdings live in a file called portfolio.csv. Read it before answering questions about their portfolio, and never modify it unless asked. When asked for a report, write it to a Markdown file (e.g. reports/portfolio-review.md) and tell the user where you saved it.
Now “What’s in my portfolio?” reads real rows, and “Write me a portfolio report and save it” produces a file you can open.
Gate risky actions: approvals
Reading data is often safe. Placing a trade is never safe – that’s an action with consequences, and the agent should never take it without a human’s say-so. The harness has a built-in approval mechanism: mark a tool as approval-required, and the agent will pause and ask before the tool ever runs.
We add a place_trade tool (it only simulates the order). In .NET, wrap it in an ApprovalRequiredAIFunction:
public static AIFunction CreatePlaceTradeTool() =>
new ApprovalRequiredAIFunction(AIFunctionFactory.Create(PlaceTrade, "place_trade"));
In Python, set approval_mode on the @tool decorator:
from agent_framework import tool
@tool(approval_mode="always_require")
def place_trade(symbol: str, action: str, quantity: int) -> str:
"""Place a (simulated) buy or sell order."""
...
When the model decides to call place_trade, the harness emits an approval request instead of running the function, and the shared console surfaces it as an Approve / Deny prompt. (The planning observers we wired up in Part 1 already include a tool approval observer, so there’s nothing extra to add.) Approve and the trade runs; deny and the agent adapts.
Don’t ask again: always-approve
Approving the same tool over and over is its own kind of friction. The harness has built-in support for standing approvals – “don’t ask me again” decisions – so once you trust a call, you won’t be pestered for it again. There are two flavours:
- Always approve this tool (any arguments) – every future
place_tradeis approved automatically, whatever the symbol or quantity.
- Always approve this tool with these arguments – only calls whose arguments exactly match the one you approved are auto-approved; a different symbol or quantity still prompts.
These rules are recorded in the session state, so they last for the duration of the session – not baked into the agent, and not leaked into a brand-new session. Because they live in the session, they travel with it: /session-export and /session-import carry your standing approvals across a restart, just like memory.
When using the sample console, its approval prompt already offers all four options out of the box:
🔐 Tool approval: place_trade(symbol: "MSFT", action: "buy", quantity: 10)
> Approve this call
Always approve this tool (any arguments)
Always approve this tool with these arguments
Deny
Pick one of the Always approve options and the harness remembers it for the rest of the session; subsequent matching calls sail through without a prompt.
Keep the safe path frictionless: auto-approval
By default every file_access_* call (even a read) asks for approval. That’s the safe default, but always prompting to read portfolio.csv gets tedious fast, and it muddies the signal when a genuinely risky call (a write, or a trade) needs your attention.
The harness lets you supply auto-approval rules: heuristics that silently approve low-risk calls so only the risky ones interrupt you. The file-access provider ships a ready-made rule that auto-approves its read-only tools (read, list, search) while still prompting for the ones that modify the store (save and delete).
In .NET, add it via ToolApprovalAgentOptions.AutoApprovalRules:
AIAgent agent = chatClient.AsHarnessAgent(new HarnessAgentOptions
{
ToolApprovalAgentOptions = new ToolApprovalAgentOptions
{
// Auto-approve read-only file tools; saving, deleting, and place_trade still prompt.
AutoApprovalRules = [FileAccessProvider.ReadOnlyToolsAutoApprovalRule],
},
// … file access, tools …
});
In Python, pass auto_approval_rules:
from agent_framework import FileAccessProvider
agent = create_harness_agent(
client=client,
agent_instructions=FINANCE_INSTRUCTIONS,
tools=[get_stock_price, place_trade],
file_access_store=FileSystemAgentFileStore("working"),
# Auto-approve read-only file tools; saving, deleting, and place_trade still prompt.
auto_approval_rules=[FileAccessProvider.read_only_tools_auto_approval_rule],
)
Now reading your portfolio just works, while writing a report, deleting a file, or placing a trade still pauses for your OK.
A rule of your own: auto-approve small trades
A rule is just a callback over the pending function call, so you can encode whatever policy you like. A natural one for our claw: auto-approve trades under $1,000, ask for the rest – keeping only the genuinely consequential orders in front of the user. (place_trade takes a symbol and a quantity, so we estimate the order value from the current price.)
In .NET, a rule is a Func<FunctionCallContent, ValueTask<bool>>:
// Returns true to auto-approve; false falls through to the normal approval prompt.
static async ValueTask<bool> AutoApproveSmallTrades(FunctionCallContent call)
{
if (call.Name != "place_trade" || call.Arguments is null)
{
return false; // Not our tool — let it prompt as usual.
}
var symbol = call.Arguments["symbol"]?.ToString();
var quantity = Convert.ToInt32(call.Arguments["quantity"]);
var estimate = quantity * await GetPriceAsync(symbol!); // your own pricing logic
return estimate < 1000m; // under $1,000 → auto-approve
}
// Wire it in alongside the read-only rule:
AutoApprovalRules = [FileAccessProvider.ReadOnlyToolsAutoApprovalRule, AutoApproveSmallTrades],
In Python, a rule is a callable taking the function call (sync or async both work):
async def auto_approve_small_trades(call: FunctionCallContent) -> bool:
"""Return True to auto-approve; False falls through to the normal prompt."""
if call.name != "place_trade":
return False # Not our tool — let it prompt as usual.
args = call.parse_arguments() or {}
estimate = int(args.get("quantity", 0)) * await get_price(args["symbol"]) # your pricing logic
return estimate < 1000 # under $1,000 -> auto-approve
# Wire it in alongside the read-only rule:
auto_approval_rules=[
FileAccessProvider.read_only_tools_auto_approval_rule,
auto_approve_small_trades,
],
Rules are evaluated in order; the first to return true wins, and returning false simply lets the next rule (or the manual prompt) take over.
Remember across sessions: durable memory
Our claw should remember the user’s watchlist and the things they tell us about themselves – across sessions, not just within one. In this post we demonstrate two kinds of memory, and they’re deliberately different:
| File memory | Foundry memory | |
|---|---|---|
| Granularity | Coarse – whole files | Fine – individual facts |
| Written by | The agent, explicitly (it decides to save a file) | Microsoft Foundry, automatically (facts are extracted) |
| Good for | Documents the agent curates: a watchlist, notes, drafts | Ambient facts: “prefers low-risk ETFs”, “saving for a house” |
| Where it lives | A folder you control | A managed Foundry memory store |
You usually want both: file memory for things the agent deliberately maintains, and Foundry memory for the small facts that should just stick without anyone managing a file.
File memory (coarse, explicit)
File memory is a harness built-in – it’s on by default, and you never construct or inject the provider yourself. The agent gets a set of file_memory_* tools and decides, on its own, when to write a file like watchlist.md.
Its files live on disk under the configured working folder, in a sub-folder keyed by session id (by default {cwd}/agent-file-memory/<session-id>/). That means the files persist across runs on the same machine automatically. A brand-new session gets a new id – and so starts with empty memory. You may configure the root folder under which these session-specific sub-folders are created by supplying a file memory store with a specific path.
To pick the memory back up after a console app restart, save and reload the session with the console’s session commands (the same save/resume flow from Part 1):
/session-export my-finance-session.json # before you quit
/session-import my-finance-session.json # after you relaunch
The export file holds the session state (including its id and conversation), not the memory files themselves. Re-importing restores the session’s identity, so the relaunched session re-links to its still-on-disk memory files. On a fresh checkout or a different machine you’d also copy the agent-file-memory folder for the files to come along.
You don’t need to set anything up for the default behavior. If you want the memory files to land in a specific folder, point the store there – in .NET with HarnessAgentOptions.FileMemoryStore:
AIAgent agent = chatClient.AsHarnessAgent(new HarnessAgentOptions
{
FileMemoryStore = new FileSystemAgentFileStore(Path.Combine(AppContext.BaseDirectory, "agent-memory")),
// … file access, tools …
});
and in Python with the file_memory_store argument:
from agent_framework import FileSystemAgentFileStore
agent = create_harness_agent(
...,
file_memory_store=FileSystemAgentFileStore("agent-memory"),
)
Foundry memory (fine, automatic)
Foundry memory works differently: you don’t tell the agent, and it does not decide to “save” anything. As the conversation flows, Microsoft Foundry extracts durable facts and recalls the relevant ones on later turns – even in a brand-new session. Because it needs a memory store and an embedding model, we make it opt-in via environment variables, so the sample still runs without that setup.
For more on Foundry memory, see What is memory? and Use memory. Foundry memory is currently only available in certain regions, so check that your Foundry project is in a supported region before relying on it.
In .NET, create a FoundryMemoryProvider scoped to a user and add it to the agent’s context providers:
var foundryMemory = new FoundryMemoryProvider(
projectClient,
memoryStoreName,
stateInitializer: _ => new(new FoundryMemoryProviderScope("claw-sample-user")));
await foundryMemory.EnsureMemoryStoreCreatedAsync(deploymentName, embeddingModel, "…");
AIAgent agent = chatClient.AsHarnessAgent(new HarnessAgentOptions
{
AIContextProviders = [foundryMemory],
// … file access, file memory, tools …
});
In Python, it’s another context provider:
from agent_framework.foundry import FoundryMemoryProvider
foundry_memory = FoundryMemoryProvider(
project_client=project_client,
memory_store_name=store_name,
scope="claw-sample-user",
update_delay=0,
)
agent = create_harness_agent(
...,
context_providers=[foundry_memory],
)
Now tell the assistant “I’m a conservative investor saving for a house in two years” in one session, restart, and ask “What do you know about me?” – it remembers, because Foundry quietly stored the fact and recalled it.
Run it
.NET
cd dotnet
dotnet run --project samples/02-agents/Harness/BuildYourOwnClaw/Claw_Step02_WorkingWithData
Python
uv run python/samples/02-agents/harness/build_your_own_claw/claw_step02_working_with_data.py
Then try these in order (the sample starts in execute mode – quick lookups don’t need a plan):
What's in my portfolio?– the agent readsportfolio.csvwith the file_access tools.Write me a short report on my portfolio and save it.– it drafts the report, then prompts you to approve the save (writes aren’t auto-approved), and on approval writes a Markdown file underworking/.I'm a conservative investor saving for a house in two years.– a durable fact, remembered by Foundry memory (when enabled).Buy 10 shares of MSFT.– the agent callsplace_trade; you’re prompted to approve or deny before anything happens.Add SPY to my watchlist.– saved towatchlist.mdin file memory.
To see memory persist across a relaunch, ask “What’s on my watchlist?” or “What do you know about me?” after restarting:
- Foundry memory (when enabled) recalls the facts about you in any new session – it’s scoped to the hardcoded “claw-sample-user” scope, not the session.
- File memory (the watchlist) lives on disk keyed by session id in both languages, so
/session-exportbefore you quit and/session-importafter relaunching to re-link the relaunched session to its files.
The runnable samples
- .NET:
dotnet/samples/02-agents/Harness/BuildYourOwnClaw/Claw_Step02_WorkingWithData - Python:
python/samples/02-agents/harness/build_your_own_claw
Use these building blocks in your own agent
The harness assembles all of this for you, but none of it is locked inside the harness. Each feature is a plain context provider, middleware, or agent decorator that you can pick up on its own and add to agent – so even if you don’t adopt the full harness, you can reuse exactly the pieces you need. Here’s where to find them:
| Feature | .NET (type — namespace) | Python (import) |
|---|---|---|
| File access | FileAccessProvider — Microsoft.Agents.AI |
from agent_framework import FileAccessProvider |
| Approvals (don’t-ask-again, auto-approval rules) | ToolApprovalAgent / ToolApprovalAgentOptions, via builder.UseToolApproval(...) — Microsoft.Agents.AI |
from agent_framework import ToolApprovalMiddleware, ToolApprovalRuleCallback |
| File memory | FileMemoryProvider — Microsoft.Agents.AI |
from agent_framework import FileMemoryProvider |
| Foundry memory | FoundryMemoryProvider — Microsoft.Agents.AI.Foundry |
from agent_framework.foundry import FoundryMemoryProvider |
In .NET, file access, approvals, and file memory ship in the Microsoft.Agents.AI package, and Foundry memory in Microsoft.Agents.AI.Foundry. In Python, the first three come from the agent-framework package and Foundry memory from agent-framework-foundry. The providers plug in through an agent’s context providers, the approval pieces through middleware – the same wiring the harness does on your behalf.
What’s next
Our claw now works with your data, asks before it acts, and remembers what matters. Next we make it more capable: skills it can load on demand (including Foundry-managed skills), background agents for concurrent work, shell access to reorganize files, and CodeAct for computation it can write and run itself.
The series
Part of Build your own claw and agent harness with Microsoft Agent Framework:
- Overview: Build your own claw and agent harness with Microsoft Agent Framework
- Part 1 – Meet your agent harness and claw
- Part 2 – Working with your data, safely (you are here)
- Part 3 – Scaling its capabilities (coming soon)
- Part 4 – Production-ready (coming soon)
The post Agent Harness: Working with your data, safely appeared first on Microsoft Agent Framework.


