# AutoGen + ACP — Governance Install Guide

Add per-user policy, audit logging, and PII detection to Microsoft AutoGen agents (v0.7+). One pip install, one @governed decorator, every tool call audited and policy-checked.

# AutoGen + ACP — Governance Install Guide

> **AutoGen is in maintenance mode.** Since [Microsoft Agent Framework](https://devblogs.microsoft.com/agent-framework/migrate-your-semantic-kernel-and-autogen-projects-to-microsoft-agent-framework-release-candidate/) reached 1.0 (April 2026), Microsoft develops AutoGen for critical fixes only and ships migration tooling to the new framework — which, unlike AutoGen, has a first-class function-invocation middleware seam. This guide remains correct for existing AutoGen v0.7 deployments; for new builds, start on Microsoft Agent Framework.

[Microsoft AutoGen](https://microsoft.github.io/autogen/) is a Python multi-agent framework. The v0.4 rewrite (Jan 2025) replaced the v0.2 API entirely; v0.7+ is the current shape, with `AssistantAgent`, model clients, and tools as plain Python callables. Out of the box, a production deployment shares one backend API key across every end user's request — no per-user policy enforcement, no per-user audit trail.

`acp-governance` closes that gap. Decorate tool functions with `@governed`; bind the end user's identity per request with `set_context`. Same governance model as Claude Code — same `/govern/tool-use` endpoint, same workspace policies.

> **Starter · 5-minute install.** `pip install acp-governance autogen-agentchat`, decorate tools with `@governed`, bind the JWT per request. See [the runnable starter](https://github.com/agentic-control-plane/acp-governance-sdks/tree/main/examples/starters/autogen), [the governance model](/docs/governance-model), or the [frameworks index](/frameworks).

## Install

```bash
pip install acp-governance "autogen-agentchat" "autogen-ext[openai]"
```

For Anthropic models swap `[openai]` for `[anthropic]` and import `AnthropicChatCompletionClient` from `autogen_ext.models.anthropic`.

## Minimal governed agent

```python
import asyncio, os
from acp_governance import configure, governed, set_context
from autogen_agentchat.agents import AssistantAgent
from autogen_ext.models.openai import OpenAIChatCompletionClient

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


# @governed wraps the coroutine. AutoGen's signature introspection walks
# through __wrapped__ (set by functools.wraps) to read the original
# signature for tool schema generation.
@governed("lookup_record")
async def lookup_record(id: str) -> str:
    """Look up a record by ID."""
    return json.dumps(await db.records.find_one({"id": id}))


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


async def run(prompt: str, user_jwt: str):
    set_context(
        user_token=user_jwt,
        agent_name="my-autogen-agent",
        agent_tier="interactive",
    )

    model_client = OpenAIChatCompletionClient(model="gpt-4o-mini")
    agent = AssistantAgent(
        name="my_autogen_agent",
        model_client=model_client,
        tools=[lookup_record, send_email],
        system_message="You are an ACP-governed agent. Use the tools available.",
    )

    try:
        result = await agent.run(task=prompt)
        return result.messages[-1].content
    finally:
        await model_client.close()
```

## What `@governed` does

Every call to a decorated tool:

1. POSTs to ACP's `/govern/tool-use` with the tool name, input arguments, and the user token bound by `set_context`.
2. If ACP denies, returns `"tool_error: <reason>"` — AutoGen delivers it to the agent as tool output; the model adapts.
3. If ACP allows, runs your function.
4. POSTs the output to `/govern/tool-output` for audit logging and PII / secret scanning.
5. If ACP redacts, replaces the output with the redacted version.
6. If ACP blocks the output post-hoc, returns `"tool_error: <reason>"`.

AutoGen's `inspect.signature` walks through `__wrapped__` to read the original function signature, so tool schema generation works correctly with the decorator in place.

## 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.

For an `AssistantAgent` running inside a `SelectorGroupChat` or other multi-agent topology, set `agent_tier="subagent"` for the worker agents — distinct from the orchestrator's tier.

## AutoGen-specific notes

- **No tool-level hooks in `AssistantAgent`** (as of v0.7.5). The framework has no `on_tool_call` / `before_tool` / middleware seam. Inline-decorating each function is the documented path to add per-tool behavior.
- **Post-v0.4 rewrite.** AutoGen v0.4 (Jan 2025) was a full rewrite. Older tutorials showing `ConversableAgent`, `config_list`, or `initiate_chat` are pre-rewrite and won't work with current packages. This guide targets v0.7.5+.
- **The middleware seam shipped in Microsoft Agent Framework, not AutoGen.** Tracking issue [#6017](https://github.com/microsoft/autogen/issues/6017) was answered by succession: with AutoGen in maintenance mode, tool-dispatch middleware lands in its successor and will not come to AutoGen 0.7.x. On AutoGen, inline-decorating each tool with `@governed` remains the only interception point.
- **Sync tools too.** `@governed` detects sync vs async functions; both work. AutoGen v0.7+ also accepts sync callables in `tools=[...]`.

## Multi-agent setups

For `SelectorGroupChat`, `RoundRobinGroupChat`, or hand-rolled topologies, decorate each tool once and pass them to whichever agents need them. The governance per-call cost is the same regardless of which agent in the topology calls the tool.

```python
from autogen_agentchat.teams import SelectorGroupChat

researcher = AssistantAgent(name="researcher", model_client=client, tools=[lookup_record, ...])
writer     = AssistantAgent(name="writer",     model_client=client, tools=[send_email, ...])

team = SelectorGroupChat([researcher, writer], model_client=client)
```

The `agent_name` you pass to `set_context` shows up in the audit log; for finer attribution you can call `set_context` with different `agent_name` values inside each agent's tool handler.

## Price and meter the model calls

`@governed` is the **interception plane** — what your agent *does*. The **proxy
plane** covers what it *spends*. Both run against the same gateway, and `init()`
wires them together:

```python
import acp_governance as acp

acp.init()            # call before you construct any model client
```

Model calls now land on the same trail as tool calls, carrying real cost,
tokens, and cache economics. Constructing clients explicitly instead? Pass the
config rather than calling `init()`:

```python
from acp_governance import model_client_kwargs
from anthropic import Anthropic

client = Anthropic(**model_client_kwargs("anthropic"))
```

Shapes are `"anthropic"`, `"openai"` (chat completions), and
`"openai-responses"`. The last two are **not** interchangeable — `/v1` serves
chat completions, `/openai/v1` serves responses.

`init()` is all-or-nothing per provider: it sets the base URL *and* the key, or
it leaves that provider completely alone and tells you which one it skipped. If
`OPENAI_BASE_URL` already points at your own gateway, ACP won't silently
reroute you.

**Your coverage:** interception ✓ (decorated tools) · proxy ✓ (model calls).

## Limitations

- **Only tools wrapped with `@governed` are covered.** Plain functions in `tools=[...]` bypass governance.
- **The two planes are wired separately.** The decorator covers tool calls; the proxy covers model calls. Both ship, but each is its own edit — see [Price and meter the model calls](#price-and-meter-the-model-calls) above. Decorating tools without repointing the model client gives you control with no cost data.
- **No middleware integration today.** When AutoGen ships a tool-dispatch middleware seam, an `acp-autogen` adapter will use it; until then decorator stacking is the documented path.
- **Pre-release.** `acp-governance` is on 0.x.

## Related

- [AutoGen quickstart](https://microsoft.github.io/autogen/stable/user-guide/agentchat-user-guide/quickstart.html)
- [AutoGen AssistantAgent reference](https://microsoft.github.io/autogen/stable/reference/python/autogen_agentchat.agents.html)
- [`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": "Govern AutoGen with Agentic Control Plane",
  "totalTime": "PT5M",
  "step": [
    {"@type": "HowToStep", "name": "Install acp-governance", "text": "pip install acp-governance autogen-agentchat autogen-ext[openai]"},
    {"@type": "HowToStep", "name": "Decorate tool functions with @governed", "text": "@governed(\"tool_name\") wraps each tool coroutine."},
    {"@type": "HowToStep", "name": "Bind the user's JWT per request", "text": "Call set_context(user_token=...) at the start of each request."}
  ]
}
</script>

