# Microsoft Agent Framework + ACP — Policy & Audit Install Guide

Add per-user policy, audit logging, and PII detection to Microsoft Agent Framework agents. One pip install, one function middleware, every tool call checked and logged.

# Microsoft Agent Framework + ACP — Policy & Audit Install Guide

[Microsoft Agent Framework](https://learn.microsoft.com/en-us/agent-framework/) (MAF) is Microsoft's unified successor to AutoGen and Semantic Kernel — GA since April 2026 — for building agents and multi-agent workflows in Python and .NET. It ships three middleware scopes (agent run, chat, function); the function scope wraps every tool invocation with a `call_next` delegate. That's exactly the seam ACP needs.

`acp-governance` plugs into it with one class. A single `FunctionMiddleware` passed at agent construction routes **every** tool call through ACP: policy check before execution, audit + PII scan after, the end user's identity on every row. Same `/govern/tool-use` endpoint, same workspace policies as Claude Code.

> **Starter · 5-minute install.** `pip install acp-governance agent-framework-core agent-framework-anthropic`, add one middleware, bind the JWT per request. See [the runnable starter](https://github.com/agentic-control-plane/acp-governance-sdks/tree/main/examples/starters/microsoft-agent-framework), [the policy model](/docs/governance-model), or the [frameworks index](/frameworks).

## Install

```bash
pip install acp-governance agent-framework-core agent-framework-anthropic
```

## Minimal agent, fully covered

```python
import json
from typing import Any, Awaitable, Callable

from pydantic import BaseModel

from acp_governance import configure, post_tool_output, pre_tool_use, set_context
from agent_framework import Agent, FunctionInvocationContext, FunctionMiddleware
from agent_framework.anthropic import AnthropicClient

configure(base_url="https://api.agenticcontrolplane.com")


class ACPFunctionMiddleware(FunctionMiddleware):
    """One instance governs every tool the agent owns."""

    async def process(
        self,
        context: FunctionInvocationContext,
        call_next: Callable[[], Awaitable[None]],
    ) -> None:
        tool_name = context.function.name
        args = context.arguments
        tool_input = args.model_dump() if isinstance(args, BaseModel) else dict(args or {})

        allowed, reason = pre_tool_use(tool_name, tool_input)
        if not allowed:
            # Never call call_next(): the tool function does not execute.
            context.result = f"tool_error: {reason or 'denied by policy'}"
            return

        await call_next()

        output = context.result
        serialized = output if isinstance(output, str) else json.dumps(output, default=str)
        verdict = post_tool_output(tool_name, tool_input, serialized)
        if verdict:
            if verdict.get("action") == "redact":
                context.result = verdict.get("modified_output", "[ACP] Redacted")
            elif verdict.get("action") == "block":
                context.result = f"[ACP] Blocked: {verdict.get('reason', 'policy')}"


def lookup_record(id: str) -> str:
    """Look up a record by ID."""
    return json.dumps(db.records.find_one({"id": id}))


def send_email(to: str, subject: str, body: str) -> str:
    """Send an email."""
    return mailer.send(to=to, subject=subject, body=body)


agent = Agent(
    client=AnthropicClient(model_id="claude-sonnet-4-6"),
    instructions="You are an ACP-governed agent. Use the tools available.",
    tools=[lookup_record, send_email],
    middleware=ACPFunctionMiddleware(),
)
```

Per request, bind the end user's identity before the agent runs:

```python
@app.post("/run")
async def run(prompt: str, authorization: str = Header(...)):
    set_context(
        user_token=authorization.removeprefix("Bearer ").strip(),
        agent_name="my-maf-agent",
        agent_tier="interactive",
    )
    response = await agent.run(prompt)
    return {"result": response.text}
```

## What happens on every tool call

1. MAF resolves the model's function call and enters the middleware chain; ACP POSTs to `/govern/tool-use` with the tool name, validated input, and the user JWT bound by `set_context`.
2. **Deny** → the middleware skips `call_next()`, so your function never runs. The model receives `tool_error: <reason>` as the tool output and adapts.
3. **Allow** → `call_next()` executes your function.
4. Post-audit: ACP scans the output for PII / secrets. `redact` swaps in the redacted version; `block` replaces the output entirely. Audit row written, rooted in the end user's identity.

## One middleware vs. per-tool decorators

In decorator-based frameworks, coverage is opt-in per function — forget the decorator on tool #7 and it runs unchecked. MAF's function middleware inverts that: coverage is a property of the **agent**, not of each tool. Add tools freely — plain functions, `@tool`-decorated functions, dynamically added tools — the middleware wraps all of them. You can also pass the middleware per-run (`agent.run(..., middleware=...)`) to scope it to specific invocations.

## Per-tier policy

`set_context(agent_tier="...")` controls the policy tier:

- **`interactive`** — human at the keyboard, permissive default.
- **`subagent`** — invoked by another agent, no human in the immediate loop.
- **`background`** — autonomous, most restrictive.
- **`api`** — programmatic call from your backend.

## MAF-specific notes

- **Package layout.** `agent-framework` on PyPI is a meta-package that pulls every connector; `agent-framework-core` covers agents + tools + middleware. Provider chat clients install separately (`agent-framework-anthropic`, `agent-framework-openai`, `agent-framework-azure-ai`, ...) and load under `agent_framework.<provider>` namespaces — swapping providers doesn't touch the middleware.
- **Native approvals compose.** MAF ships `approval_mode` on tools and `ToolApprovalMiddleware` for in-process human sign-off. ACP doesn't replace that UX — it adds the workspace layer: standing rules, per-user policy, and an audit trail that spans every framework your fleet runs.
- **AutoGen / Semantic Kernel migrations.** If you're consolidating AutoGen or SK agents onto MAF, this is the moment to standardize control in one place — the middleware travels with the agent definition, not with each tool you port.
- **Exception semantics.** An ordinary exception raised in function middleware becomes a tool-error result and the run continues; `MiddlewareFailure` aborts fail-closed. ACP's middleware uses the `context.result` override so denials stay legible to the model.

## Limitations

- **Tool-layer, not token-layer.** LLM calls go direct to your provider with your own key. Route model traffic through the ACP proxy separately if you want per-user cost metering.
- **Python first.** This guide covers MAF's Python package; the .NET middleware surface is equivalent (`IFunctionMiddleware`); there is no .NET example on this page.
- **Pre-release SDK.** `acp-governance` is on 0.x. The class above is ~30 lines and yours to own.

## Related

- [MAF docs — middleware](https://learn.microsoft.com/en-us/agent-framework/agents/middleware/)
- [MAF docs — function tools](https://learn.microsoft.com/en-us/agent-framework/agents/tools/function-tools)
- [`acp-governance` (core SDK)](https://pypi.org/project/acp-governance)
- [Pydantic AI integration](/integrations/pydantic-ai)
- [LangChain / LangGraph integration](/integrations/langgraph)

<script type="application/ld+json">
{
  "@context": "https://schema.org",
  "@type": "HowTo",
  "name": "Add policy and audit to Microsoft Agent Framework with Agentic Control Plane",
  "totalTime": "PT5M",
  "step": [
    {"@type": "HowToStep", "name": "Install acp-governance", "text": "pip install acp-governance agent-framework-core agent-framework-anthropic"},
    {"@type": "HowToStep", "name": "Attach the function middleware", "text": "Pass ACPFunctionMiddleware() to Agent(middleware=...) — one instance covers every tool the agent owns."},
    {"@type": "HowToStep", "name": "Bind the user's JWT per request", "text": "Call set_context(user_token=...) at the start of each request."}
  ]
}
</script>

---
