Sr. Content Developer at Microsoft, working remotely in PA, TechBash conference organizer, former Microsoft MVP, Husband, Dad and Geek.
156606 stories
·
33 followers

Agent Harness: Working with your data, safely

1 Share

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:

  1. File access – read your portfolio from a CSV and write reports back to disk.
  2. Approvals – gate risky actions (like placing a trade) behind a human’s explicit OK.
  3. 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_trade is 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):

  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. – it drafts the report, then prompts you to approve the save (writes aren’t auto-approved), and on approval writes a Markdown file under working/.
  3. I'm a conservative investor saving for a house in two years. – a durable fact, remembered by Foundry memory (when enabled).
  4. Buy 10 shares of MSFT. – the agent calls place_trade; you’re prompted to approve or deny before anything happens.
  5. Add SPY to my watchlist. – saved to watchlist.md in 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-export before you quit and /session-import after relaunching to re-link the relaunched session to its files.

The runnable samples

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 FileAccessProviderMicrosoft.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 FileMemoryProviderMicrosoft.Agents.AI from agent_framework import FileMemoryProvider
Foundry memory FoundryMemoryProviderMicrosoft.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:

The post Agent Harness: Working with your data, safely appeared first on Microsoft Agent Framework.

Read the whole story
alvinashcraft
39 seconds ago
reply
Pennsylvania, USA
Share this story
Delete

Prompt Compression and Cache Tuning: Cut Your LLM API Costs by 60%

1 Share
Prompt Compression and Cache Tuning: Cut Your LLM API Costs by 60%

Cross-model guide to reducing LLM costs using prompt compression, semantic caching, chain-of-thought pruning, and output length constraints across OpenAI, Anthropic, and Google Gemini.

Continue reading Prompt Compression and Cache Tuning: Cut Your LLM API Costs by 60% on SitePoint.

Read the whole story
alvinashcraft
9 hours ago
reply
Pennsylvania, USA
Share this story
Delete

Claude Code on Ollama: How to Run a Local Coding Agent Without Burning API Credits

1 Share
Claude Code on Ollama: How to Run a Local Coding Agent Without Burning API Credits

Comprehensive guide covering this topic with practical implementation details.

Continue reading Claude Code on Ollama: How to Run a Local Coding Agent Without Burning API Credits on SitePoint.

Read the whole story
alvinashcraft
9 hours ago
reply
Pennsylvania, USA
Share this story
Delete

Connecting the Unconnected: Doreen Bogdan-Martin

1 Share

For nearly 160 years, the International Telecommunication Union has helped the world communicate across borders, from the telegraph to the telephone, television, satellite, the internet, and now AI. In this episode of Tools and Weapons, Brad Smith sits down with Doreen Bogdan, Secretary-General of the ITU, to discuss why connectivity remains one of the world’s most important foundations for opportunity.

The conversation explores the 2.2 billion people who are still unconnected, the estimated $2.8 trillion needed to connect the world by 2030, and the partnerships required to reach the hardest-to-connect communities. Doreen shares stories from the field, including a refugee camp in Chad where a small computer center gives people access to learning, health care, financial tools, and family connections.

Brad and Doreen also discuss the rise of AI for Good, the challenge of scaling solutions that address real-world needs, and the role of global cooperation in shaping responsible AI governance. From early warning systems that can help save lives during natural disasters to digital skilling and infrastructure investment, this episode examines how technology can create opportunity when access, trust, and partnership come together.

Listen to the full episode and join the conversation about building a more connected and inclusive digital future.





Download audio: https://mgln.ai/e/1457/cdn.simplecast.com/media/audio/transcoded/973e729e-d3fe-467f-b2ac-1080afa0eb0f/4b885e2a-3d0c-4a1e-b2c2-86d5e058b8cc/episodes/audio/group/59812846-8b4b-4c1a-a1b9-5408d7df1e7b/group-item/478ad2b6-5588-4208-8661-0c37abea13ee/128_default_tc.mp3?aid=rss_feed&feed=FGw2u5qj
Read the whole story
alvinashcraft
9 hours ago
reply
Pennsylvania, USA
Share this story
Delete

What the pls?

1 Share

tl;dr; The pls package on PyPI is an abandoned Python version (last released as v6.0.0 in 2023). The actively-developed pls was rewritten in Rust and now lives at pls-rs/pls. If you installed it with uv tool install pls or pipx install pls, you have the wrong one. Uninstall it and install the Rust build instead (e.g. brew install pls-rs/pls/pls).


You may have heard me sing the praises of pls. I really love the icons and colors to disambiguate files and provide more information about them, the developer workflow, and more. Here’s an example in the Warp terminal for my jinja-partials package.

pls listing the jinja-partials repo in the Warp terminal, with colored icons next to each file

But installing and managing this package is weird and kinda deceiving to say the least. pls was originally pure Python (up to v6.0.0 in 2023). It’s listed on PyPI here. So it looks like that is just the latest, right? After all, if it was rewritten in Rust, it can still be installed via PyPI and in particular, via uv tool install pls.

But no.

There are a few funky things about the PyPI listing that give it away:

  • The homepage 404s. The homepage link on the PyPI page goes nowhere. A bit sus.
  • The GitHub repo is a silent redirect. The repo seems alive, but the link https://github.com/dhruvkb/pls silently redirects to https://github.com/pls-rs/pls. Clicking the link in PyPI seems to reference the Rust/latest version. But this happens only because GitHub does the redirect.

How do I install the correct, Rust-based pls?

To get the actively-developed version, don’t let PyPI fool you. There are two steps:

  1. Uninstall the Python version if you installed it via Python: uv tool uninstall pls (or pipx uninstall pls).
  2. Then install the Rust version directly. I use Homebrew, so it’s brew install pls-rs/pls/pls. See their getting started page for the option that works best for you.

Should you switch from the PyPI version of pls?

Yes - if you installed it with a Python tool, switch to the Rust version. Normally, I’d just chalk this up to standard package / open source drift and carry on with my life. But I’ve recommended pls to enough people that I feel I should call a bit of attention here. So if you’re using pls and you used Python tools to install it, like uv, uninstall that version and jump over to the Rust-based one.

Read the whole story
alvinashcraft
9 hours ago
reply
Pennsylvania, USA
Share this story
Delete

How Writers Use The Love Interest As A Literary Device

1 Share

What is a love interest in fiction? We explore the love interest as a literary device to create tension, deepen character development, and strengthen your story.

Look Out For All The Posts In The 4 Main Characters Series:

  1. The 4 Main Characters As Literary Devices
  2. The Protagonist As A Literary Device
  3. The Antagonist As A Literary Device
  4. The Confidant As A Literary Device
  5. The Love Interest As A Literary Device

Welcome to the series on the four main characters and why they are literary devices. This week, I’m going to write about the love interest’s role in our stories.

How Writers Use The Love Interest As A Literary Device

“It gives me strength to have somebody to fight for; I can never fight for myself, but, for others, I can kill.” ~Emilie Autumn 

As A Literary Device: The love interest is the device an author uses to show the vulnerabilities and strengths of the protagonist. This device allows writers to complicate the main character’s life, and to put stumbling blocks in the way of attaining the story goal. It is the most common sub-plot. 

Story Goal: This character’s goal is always to win, or keep, the protagonist’s love. It is, by nature, a selfish goal.

Five Important Things To Remember About Love Interests

  1. Only you. The love interest has an intimate relationship with your main character. This person shows us an aspect of the protagonist that we are unlikely to find in their interactions with the other characters. This character is the object of your protagonist’s romantic and/or sexual interest. They are somebody who makes them act irrationally and unreliably.
  2. The object of my affection. This character does not have to be a lover. They are best defined as the object of your protagonist’s affection. It is easier if  you choose a person or an animal for your protagonist to love. In 6 Sub-Plots Every Writer Should Know, we said: ‘It could be a friend, a pet, or a family member. Writers use love interests to support protagonists and to thwart them by threatening their well-being.’
  3. Holding out for a hero. A love interest can make fools or heroes of all of us. Please note that this character can be called a romantic interest for a good reason. (Look at the definition of romantic according to the Merriam-Webster Dictionary.) Nothing worth having comes easily. Our heroes should move from idealistic to realistic relationships through struggle and conflict. True love is something a protagonist earns.
  4. We all need a muse. Use the difficulties of romantic relationships to create tension and increase conflicts. This character motivates, provokes, challenges, inspires, and pushes the protagonist. A love interest can add to the texture and complexity of your protagonist’s personality.
  5. Challenge and change. The love interest shows readers the protagonist as a flawed and vulnerable human being. They there to challenge the protagonist to rise above their weaknesses and to change. A love interest can be an existing relationship or introduced as a new character in the story. This relationship will change your protagonist’s everyday life and their story goal.

If your protagonist breaks up with your love interest, read: 9 Ways To Set Up Believable Fictional Breakups

How Do You Find Your Protagonist’s Love Interest?

List three possible characters who would realistically complicate your protagonist’s life.

How do I find the love interest?

Now fill this in:

  • The one with the greatest potential for an intimate relationship with the protagonist is:
  • The one I find most interesting is:
  • The one I would most enjoy writing about is:
  • The one who would complicate the protagonist’s life most is:

One of your characters will dominate these answers. This is the character you should consider using as your love interest. If the second character differs from the character you chose in the lists, you may be giving this role to the wrong character.

The Last Word

The love interest is more than just a romantic sub-plot. It is a versatile literary device that can shape the emotional core of your story. If you use it effectively, it adds tension, raises the stakes, and creates conflict that challenges your protagonist’s goals and decisions. It helps with character development by revealing vulnerabilities. A well-crafted love interest also helps drive the story toward a more emotionally satisfying conclusion – whether or not they are still in the protagonist’s life.


by Amanda Patterson
© Amanda Patterson

If you enjoyed this blogger’s article, read:

  1. Make Readers Care: 9 Ways To Create An Unforgettable Story
  2. 30 Practical Tips To Beat Writer’s Block
  3. What Is Backstory? How To Make It Work Like Scar Tissue In Your Book
  4. 15 Fascinating Fictional Fathers Every Writer Should Know
  5. How Writers Use The Confidant As A Literary Device
  6. 10 Powerful Recurring Themes In Children’s Stories & Why They Matter
  7. What Is An Epistolary Novel? & Tips For Writing One
  8. What Is A Plot? – A Writer’s Resource
  9. How Writers Use The Antagonist As A Literary Device
  10. How Using A Timeline Can Help You Plot Your Novel

Top Tip: Sign up for our free daily writing links.

The post How Writers Use The Love Interest As A Literary Device appeared first on Writers Write.

Read the whole story
alvinashcraft
9 hours ago
reply
Pennsylvania, USA
Share this story
Delete
Next Page of Stories