Microsoft Agent Framework + ACP — Policy & Audit Install Guide
Microsoft 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, the policy model, or the frameworks index.
Install
pip install acp-governance agent-framework-core agent-framework-anthropic
Minimal agent, fully covered
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:
@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
- MAF resolves the model’s function call and enters the middleware chain; ACP POSTs to
/govern/tool-usewith the tool name, validated input, and the user JWT bound byset_context. - Deny → the middleware skips
call_next(), so your function never runs. The model receivestool_error: <reason>as the tool output and adapts. - Allow →
call_next()executes your function. - Post-audit: ACP scans the output for PII / secrets.
redactswaps in the redacted version;blockreplaces 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-frameworkon PyPI is a meta-package that pulls every connector;agent-framework-corecovers agents + tools + middleware. Provider chat clients install separately (agent-framework-anthropic,agent-framework-openai,agent-framework-azure-ai, …) and load underagent_framework.<provider>namespaces — swapping providers doesn’t touch the middleware. - Native approvals compose. MAF ships
approval_modeon tools andToolApprovalMiddlewarefor 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;
MiddlewareFailureaborts fail-closed. ACP’s middleware uses thecontext.resultoverride 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) and an example is on the roadmap. - Pre-release SDK.
acp-governanceis on 0.x. A framework-specificacp-mafhelper package may follow; the class above is ~30 lines and yours to own either way.
Related
- MAF docs — middleware
- MAF docs — function tools
acp-governance(core SDK)- Pydantic AI integration
- LangChain / LangGraph integration