Microsoft Agent Framework 1.0 in .NET: Design Agents at the Boundary

An AI agent is easy to demo when it has one tool, one prompt, and no consequences. Ask it to summarize a document, call a weather API, or draft a support reply, and the interaction feels almost magical.

The engineering work begins when the agent can affect the world.

Once it can create tickets, query customer records, issue refunds, modify infrastructure, or trigger a deployment, it is no longer simply a conversational feature. It becomes a component in a distributed system: probabilistic at its reasoning layer, deterministic at its boundaries, and accountable for every action it requests.

Microsoft Agent Framework 1.0 gives .NET teams a foundation for building this kind of application. Its value is not merely that it can connect a model to tools. Its more important promise is architectural: agents, tools, orchestration, memory, and execution can be represented as application components rather than improvised prompt logic scattered through controllers and services.

The most useful way to approach the framework is boundary-first design.

The agent is not the system of record

A language model can decide that a tool should be called. It should not become the authority that decides whether the call is permitted, valid, or final.

That distinction matters because models operate through inference. They generate a plausible next action from the context they receive. Business systems operate through explicit rules: authorization checks, transactional constraints, validation, rate limits, audit requirements, and idempotency guarantees.

A healthy design puts the agent inside a carefully shaped execution space:

  • The agent interprets intent and proposes a plan.
  • A tool contract defines the actions it may request.
  • An application service enforces business rules and authorization.
  • A workflow or orchestration layer owns long-running state and retries.
  • An audit trail records the request, approval, action, and outcome.

This turns the model from an unchecked operator into a constrained planner.

Consider an internal support agent asked to “close all duplicate incidents from today.” The agent may be useful for identifying likely duplicates. But it should not receive broad database access or a generic ExecuteSql tool. Instead, it can invoke a narrowly defined operation such as FindPotentialDuplicates and, after review, CloseIncident for a specific incident ID and a specific resolution code.

The tool boundary is the real security boundary.

Start with verbs, not data access

A common early mistake is exposing low-level capabilities because they appear flexible:

  • Run arbitrary SQL
  • Call any internal endpoint
  • Read an entire customer record
  • Execute a shell command
  • Send an unrestricted HTTP request

These tools make an agent powerful, but they also make it difficult to reason about least privilege, prompt injection, and unexpected side effects.

Prefer domain verbs. A tool should describe a business capability, not an implementation escape hatch.

csharp

The framework can expose these capabilities to an agent through its tool integration model, but the implementation remains ordinary .NET application code. That is an advantage. Existing validation, dependency injection, logging, policy enforcement, and data-access patterns do not need to be replaced just because an LLM is now involved.

Notice that RequestClosureAsync includes UserContext. The identity of the chat user must survive the model interaction. An agent should never silently inherit a broad service identity when acting on behalf of a user. The downstream service needs enough context to enforce the same rules it would apply if the user clicked a button in a conventional web application.

Treat tool descriptions as public APIs

For an agent, a tool schema and description are part of the user interface. They shape what the model believes is possible, when it chooses an action, and what arguments it generates.

That means descriptions deserve the same attention as REST API contracts.

A strong tool contract answers four questions clearly:

  1. What does this action do?
  2. What inputs are required?
  3. What does it explicitly not do?
  4. What happens when the request is ambiguous or unsafe?

For example, a CreateRefundRequest tool should state that it creates a request for approval, not that it transfers money. Its schema should use explicit fields such as orderId, amount, currency, and reason. It should reject unsupported currencies and amounts beyond a permitted threshold before any external action occurs.

Avoid arguments such as instructions, rawQuery, or options when a typed alternative is possible. Ambiguous string fields create a channel through which untrusted text can influence sensitive behavior.

This is especially important when agents process documents, emails, web pages, or ticket descriptions. Any retrieved content may include instructions intended to manipulate the model. The model should be told that retrieved text is data, but prompts alone are not a security control. Typed, narrow tools are.

Separate conversation state from operational state

Agent applications typically need more than one kind of memory.

Conversation state contains messages, summaries, and working context. It helps the agent sustain a useful dialogue. Operational state contains facts that must be durable and authoritative: approval status, workflow stage, external operation IDs, retry counts, and timestamps.

Do not store both in the same conceptual bucket.

A conversation summary might say, “The user wants a refund for order 4812.” An operational record should say, “Refund request rf_91c was submitted by user u_203, approved by manager u_018, and has not yet been executed.” The first helps reasoning. The second supports correctness.

In a .NET application, the operational record belongs in the database and should be updated through normal transactional application logic. Conversation state can use a separate store with retention policies appropriate for chat history. The framework helps coordinate agent execution, but durable business state remains the responsibility of the application architecture.

This separation also makes recovery practical. If a model call fails halfway through a workflow, the system can reload the authoritative workflow record, determine what has already happened, and continue safely.

Make irreversible actions a two-step protocol

The most reliable agent pattern for side effects is propose, validate, approve, execute.

First, the agent creates a structured proposal. Second, application code validates it. Third, a human or policy engine approves it when needed. Finally, a deterministic service executes the action.

For example:

text

The idempotency key is essential. Models may retry, workflows may resume after failure, and users may submit the same request twice. The executing service must be able to answer, “Have I already performed this exact operation?” without relying on the agent’s memory.

This approach may feel less autonomous, but it is more useful in production. Autonomy is not the absence of controls. It is the ability to make progress safely within controls.

Observe decisions, not just exceptions

Traditional application monitoring focuses on latency, errors, and resource use. Agent systems need those signals, plus a record of decisions.

For each run, capture structured telemetry such as:

  • Agent and model configuration version
  • Correlation ID and authenticated user identity
  • Tool names requested and arguments after redaction
  • Policy decisions and approval outcomes
  • Latency per model and tool invocation
  • Token and cost estimates where available
  • Final result, failure category, and retry history

Distributed tracing is particularly valuable here. A single user request can cross an ASP.NET endpoint, an agent invocation, several tool calls, a queue, a database transaction, and an external provider. A shared trace ID turns an opaque “the agent did something strange” report into an investigable sequence.

Be deliberate about redaction. Prompts and tool results can contain customer data, credentials, or proprietary material. Logging everything is not observability; it is often a data-retention incident waiting to happen.

Test the seams where models are weakest

Do not limit testing to whether the agent produces a pleasant answer. Test whether the system behaves safely when the answer is wrong, incomplete, or manipulated.

Build a scenario suite that includes ambiguous requests, malformed tool arguments, conflicting instructions in retrieved documents, unauthorized users, duplicate submissions, unavailable dependencies, and attempts to exceed business limits.

Then assert on deterministic outcomes: no tool call occurred, approval was required, validation failed, an idempotent response was returned, or the workflow entered a recoverable state.

For model behavior, use evaluation sets rather than a single golden response. Measure whether the agent selects an allowed tool, gathers missing information, declines unsafe requests, and routes high-risk actions to review. Treat prompt and tool-description changes as deployable artifacts that require regression testing.

Build the stage, not the actor

Microsoft Agent Framework 1.0 is most compelling when it is used to compose agents into an established .NET architecture, not when it is expected to replace one.

Keep business rules in services. Keep authorization at the boundary. Keep durable facts in systems of record. Give agents narrow verbs, structured choices, and clear escape routes when uncertainty is high.

The model may be the visible actor, but the application is the stage manager: setting limits, recording events, checking credentials, and ensuring that every meaningful action can be explained afterward.

That is how an agent becomes more than an impressive conversation. It becomes a dependable participant in a production system.

Comments (0)

No comments yet. Be the first to share your thoughts!