Adding commercial guardrails to AutoGen agents
You add commercial guardrails to AutoGen agents by wrapping the tool functions themselves: the function an executor agent runs for send_email or issue_refund calls EvalLayer's /authorize endpoint first, performs the side effect only on allow, substitutes the approved rewrite when one is returned, and otherwise reports back to the conversation without sending. Because the function body is the choke point, the guardrail holds no matter which agent, chat pattern, or framework version invokes it.
Why AutoGen conversations create commercial risk
AutoGen's founding idea is agents that talk to each other: assistant and user-proxy pairs, group chats, nested conversations, with tool execution split across roles so one agent proposes a call and another executes it. The ecosystem now has two lineages, the community-run AG2 continuing the ConversableAgent line and Microsoft's rearchitected stack that fed into its Agent Framework, but both keep the same commercial exposure: somewhere in the loop, a Python function fires that emails a customer, issues a credit, or posts content, and the text it carries was negotiated by models talking to models.
Multi-agent conversation adds a specific failure mode. Agents build on each other's messages, so a concession one agent floats ("we could offer 20 percent to close this") becomes context a later agent treats as settled, and the executor faithfully ships it. The framework's native brake is human_input_mode, which can require a person at every turn, plus the reply-function chain that lets you intercept messages. Those are blunt instruments: all-or-nothing attention, with no notion of a discount cap or an approved claim. The precise instrument is a policy check at the moment of execution.
The interception point: the registered tool function
In the ConversableAgent model, a tool is registered twice: for the LLM (so the model can propose calls) and for execution (so an executor agent runs the function). Whatever proposes the call, execution funnels through your function, so wrap it there:
import httpx
POLICY = {
"max_discount_pct": 12,
"commitment_authority_usd": 1500,
"forbidden_commitments": ["delivery_date", "refund_promise"],
"approved_claims": ["gdpr_compliant"],
"required_disclaimers": [],
"blocked_topics": [],
}
def with_guardrail(action_type, send_fn):
def guarded(recipient: str, body: str) -> str:
r = httpx.post(
"https://api.evallayer.ai/authorize",
headers={"Authorization": "Bearer " + EVALLAYER_KEY},
json={"action_type": action_type, "content": body,
"context": {"recipient": recipient}, "policy": POLICY},
timeout=10,
)
data = r.json()
if data["decision"] == "allow":
send_fn(recipient, body)
return "Sent. Authorization " + data["authorization_id"]
if data["decision"] == "rewrite":
send_fn(recipient, data["approved_rewrite"])
return "Sent with compliant rewrite. Authorization " + data["authorization_id"]
if data["decision"] == "require_approval":
queue_for_human(recipient, body, data["authorization_id"])
return "Held for human approval: " + data["violations"][0]["detail"]
log_blocked(data["authorization_id"], data["violations"])
return "Blocked by commercial policy: " + data["violations"][0]["detail"]
return guarded
guarded_send = with_guardrail("send_email", real_send_email)
assistant.register_for_llm(
name="send_email", description="Send an email to a customer"
)(guarded_send)
executor.register_for_execution(name="send_email")(guarded_send)
The registration calls above follow the ConversableAgent pattern; if you are on a different AutoGen lineage or version, register the guarded function however that surface expects. The invariant is the wrapper, not the registration syntax.
Decision handling in the conversation
- allow: the side effect runs; the tool result confirms it with the
authorization_id. - rewrite: the side effect runs with
approved_rewritesubstituted. The conversation continues; the customer gets the compliant text. - require_approval: nothing sends. The draft and
authorization_idland in a human queue, and the returned string tells the agents the action is pending, so they can wrap up honestly instead of hallucinating success. - block: nothing sends. The violation detail returns as the tool result, giving the group chat what it needs to change course.
Latency notes
Wrap only functions with external side effects; calculators, retrievers, and code runners stay untouched. Deterministic-only policies (caps, authority, required text) add milliseconds. Semantic checks add one to two seconds, which disappears inside a multi-agent exchange that already spends that per turn. Set a timeout and pick failure modes per action: fail closed on refunds, fail open with logging where availability wins.
What to log
Keep the authorization_id in the tool result string (as above) so it is preserved in the conversation history AutoGen already records, and mirror it to your own store with the chat ID. Multi-agent transcripts are long; the authorization trail is the short index of every moment that could have cost money, with the verdict attached.
Try it with the demo endpoint
Wrap one function against /demo/authorize (three free checks per day, no key needed) before adding a production key, or preview the decision card at the interactive demo. For the decision model, read the authorization guide; for setting caps that match real human authority, read policy design.