Skip to content

Microsoft Agent Framework

Available · FunctionTool per spec · approval from your port · pip install "agent-adapter[agent-framework]" (agent-framework-core)

Functions

Function (agent_adapter.integrations.agent_framework) Returns
agent_framework_tool(spec, *, executor=None) one FunctionTool (a subclass) running spec through the executor
agent_framework_tools(registry, *, executor=None) one per registry tool exported to agent_framework
await resolve_approvals(response, port, registry, *, session=None) the function_approval_responses answering response's approval requests
INTEGRATION get_integration("agent_framework"); bind_context(invocation_context) gives ids only

The tool takes the neutral JSON schema as input_model. Every call runs the full executor pipeline: validation, hooks, cache, retries, ledger. agent_framework_tool refuses (IntegrationError) an MAF outside the extra's range or without what the bridge reads.

Wiring

port = CallbackApproval(reviewer)  # a DeferredApproval store makes the run-time check silent


def build_agent(client: BaseChatClient) -> Agent:
    tools = agent_framework_tools(registry, executor=Executor(registry, approval=port))
    # send_invoice has confirm=True, so it is exported with approval_mode="always_require"
    return Agent(client=client, tools=tools)


async def run_with_approvals(agent: Agent, prompt: str) -> str:
    session = agent.create_session()
    response = await agent.run(prompt, session=session)
    while response.user_input_requests:  # MAF paused: a confirm tool wants to run
        answers = await resolve_approvals(response, port, registry, session=session)
        response = await agent.run(Message(role="user", contents=answers), session=session)
    return response.text

Approval

Any confirm rule exports the tool with approval_mode="always_require", so MAF pauses the run with a function_approval_request (a callable rule is asked every time: MAF has no per-call rule). MAF's approval is only an extra gate: answer it with resolve_approvals, which asks your approval port.

sequenceDiagram
  participant App
  participant MAF as MAF Agent
  participant P as ApprovalPort
  participant Tool as FunctionTool
  participant Ex as Executor
  App->>MAF: agent.run(prompt, session)
  MAF-->>App: user_input_requests
  App->>P: resolve_approvals(response, port, registry, session)
  P-->>App: decisions (+ one-use token per approval)
  App->>MAF: agent.run(responses, session)
  MAF->>Tool: invoke(context, tool_call_id)
  Tool->>Ex: run_spec(ctx.approval = token, if it matches)
  Ex-->>Tool: no token: asks its own port
Case What happens
Approved by resolve_approvals a one-use token bound to the session, the tool_call_id, the tool and the validated arguments; the call that matches takes it and the executor does not ask again
Any other approval (a hand-made or forged function_approval_response, a caller-built FunctionInvocationContext, changed arguments, a replay) no token: the executor asks its own port (no port: denied)
DeferredApproval port two-phase: ApprovalPending until every request is decided, then each record is consumed once
Edited arguments refused (denied): MAF cannot apply them

Trust assumptions

Pass the run's session to resolve_approvals (its id is the run id the token is bound to). Never enable MAF's disable_approval_response_binding: it lets any message claim an approval. The adapter still asks its port then, but MAF's own gate is gone.

Calls and errors

Item Behaviour
Call id MAF's tool_call_id → ctx.call_id
Run, thread the AgentSession.session_id → ctx.run_id and ctx.thread_id
ToolArgsInvalid, ToolError, ApprovalDenied, GuardrailBlocked, RunLimitExceeded returned as {"error": {"kind", "message"}}
Other errors (timeout, failure) raised; MAF turns them into an error result (detail only with include_detailed_errors)
CancelledError propagates

The tool's own context parameter is named _aa_invocation; tool arguments cannot use that name.