AI-103: Build Foundry Agents — Tools, Memory, Multi-Agent Workflows and Evaluation
Building an agent with Microsoft Foundry means writing an agent definition — a model deployment, instructions and tools — in Foundry Agent Service, then deciding how it tracks conversations and memory, which tools it calls (function tools, OpenAPI and MCP servers, Azure AI Search, file search, shared toolboxes), how several agents work together, where a person must approve its actions, and how you evaluate and debug what it does. This lesson covers AI-103 task 2.2 at the implementation level: the schemas, settings, SDK calls and failure modes behind each of those decisions.
On this page10 sections
- How do you define an agent's role, goals and boundaries?
- How do you write tool schemas for function tools?
- How does the function-calling loop work in an agent?
- How do you choose a conversation-tracking approach?
- How do you add conversation memory to an agent?
- How do you integrate retrieval with Azure AI Search and file search?
- How do you integrate APIs, MCP servers, content understanding and shared tools?
- How do you implement orchestrated multi-agent solutions?
- How do you build semiautonomous workflows with safeguards and approvals?
- How do you monitor, evaluate and analyze errors in deployed agents?
- Write agent instructions with a role, boundaries, tool decision rules and failure guidance, and use tool_choice when a tool call must be deterministic.
- Define function tool schemas with descriptions, required, enum and strict mode, and implement the function_call / function_call_output loop correctly.
- Choose between conversations, previous_response_id chaining and store=false, and add memory scoped per user with x-memory-user-id and TTL.
- Configure the Azure AI Search, file search, OpenAPI and MCP tools and a shared Foundry toolbox, including authentication and their limits.
- Build multi-agent solutions with the A2A tool and Microsoft Agent Framework orchestrations, hosted in Foundry as the portal workflows retire.
- Add safeguards: MCP require_approval, approval_mode always_require, request_info and checkpoints, and background mode for long work.
- Pick the agent evaluator that matches a failure and use trace content recording, @trace_function and agent_reference for error analysis.
How do you define an agent's role, goals and boundaries?
You define an agent's role, goals and boundaries in its instructions (the system message on the agent definition): what the agent is for, which topics it handles and declines, how it should use each of its tools, and what to do when a tool fails or returns nothing. In Foundry Agent Service a prompt agent is an agent definition — a model deployment, instructions and tools — and the instructions are the only place the model learns your business rules.
- Role and scope. State what the agent is for (say, a campus library assistant that answers catalogue and loan questions) and what it must not take on, with where to send people instead. Without an explicit boundary, an agent happily answers questions it has no data for.
- Tool-use guidance and decision rules. Describe each tool's purpose, and add an explicit precedence rule wherever two tools could answer the same kind of question — for a parts-ordering agent with a warehouse database and a manufacturer-website lookup, say which source to consult first for which question type. Removing one of the overlapping tools stops the confusion but also removes a capability you still need.
- Failure guidance. Spell out how to behave when a tool comes back empty or errors: admit the gap and gather more detail from the user instead of filling the hole with a plausible guess. Microsoft's example instructions include this kind of rule.
- What doesn't belong in instructions. Tool schemas live on the tool definitions (never paste an OpenAPI spec into the prompt), secrets live in project connections, and request settings such as
tool_choiceare set by the app on each request — the model can't set them.
Making tool use deterministic with tool_choice
Instructions steer the model; tool_choice on the request controls it. auto (the default) lets the model decide, required makes it call one or more tools, and none prevents tool calls. When a lookup must always happen before the agent answers — a seat-availability check before confirming a booking — set tool_choice to required on those requests. It guarantees a call, not which tool is chosen, so it doesn't fix selection between similar tools. temperature only reduces randomness, and strict mode only shapes arguments once a call is made; neither forces a call.
When one agent should become several
Tool-selection errors grow as one agent accumulates many tools from unrelated areas, and one long instruction block shared by several teams becomes hard to maintain. The fix is specialization: split the work into agents with narrow instructions and only their own tools (facilities, legal, procurement), and route between them with an orchestrator. Regrouping the same tools into toolboxes on the same agent, or moving to a model with a bigger context window, leaves the overloaded agent in place.
How do you write tool schemas for function tools?
A function tool is defined by a name, a description and a parameters object written in JSON Schema; the model reads all three to decide when to call the function and how to fill its arguments. The descriptions do most of the work: two tools both described as "Looks something up" give the model nothing to tell find_shipment from find_return. Microsoft's troubleshooting guidance for wrong tool calls and malformed parameters is to state in each description when the tool applies, and to describe each parameter with its expected format and an example value (for instance, that a tracking number is twelve digits).
| Schema keyword | What it does | What it does not do |
|---|---|---|
required | Lists properties the model must supply (for example a licence_plate on a parking-permit tool) | Doesn't restrict which values are allowed |
enum | Restricts a property to a closed set of values, such as ["metric", "imperial"] | Doesn't make the property mandatory |
type | Sets the JSON type; ["string", "null"] lets a value be empty | A plain string still allows any text |
additionalProperties: false | Blocks property names the schema doesn't define | Doesn't require the defined properties |
default | A hint only | Doesn't stop the model omitting the field |
Strict mode
Setting strict=True on a function tool makes the model's arguments always conform to the schema, but the schema must then follow two rules: every property is listed in required, and additionalProperties is false. A field the model may leave empty (like access_notes above) stays optional by allowing null in its type — it is still listed in required.
FunctionTool(
name="book_inspection",
description="Book a site safety inspection. Use when "
"the user asks to schedule an inspection.",
strict=True,
parameters={
"type": "object",
"properties": {
"site_code": {"type": "string",
"description": "Site code, e.g. LDN-04"},
"kind": {"type": "string",
"enum": ["fire", "electrical"]},
"access_notes": {"type": ["string", "null"]},
},
"required": ["site_code", "kind", "access_notes"],
"additionalProperties": False,
},
)
Strict mode applies to function tools you define. It doesn't affect which tool is chosen, and it isn't a switch on MCP or search tools.
How does the function-calling loop work in an agent?
In Foundry Agent Service the service never runs your functions: when the model decides to call one, the response's output contains a function_call item (with name, JSON arguments and a call_id) and no final text. Your application runs the function and sends the result back as a function_call_output item carrying the same call_id, normally on the same conversation; the model then writes the answer.
HANDLERS = {"check_room": check_room, "hold_room": hold_room}
pending = [i for i in response.output if i.type == "function_call"]
outputs = [
{
"type": "function_call_output",
"call_id": call.call_id, # echo the call's call_id
"output": json.dumps(
HANDLERS[call.name](**json.loads(call.arguments))),
}
for call in pending
]
# every output goes back in ONE follow-up request
response = openai.responses.create(
input=outputs,
conversation=conversation.id,
extra_body={"agent_reference": {
"name": AGENT_NAME, "type": "agent_reference"}},
)
- call_id, not id. The item's own
id, the response ID and the function name don't identify the call. (MCP approvals are different — they reference the approval request item'sid; see the safeguards section.) - Parallel calls. One response can hold several
function_callitems (say, exchange rates for three currencies requested in one question). Run all of them and return every output, each with its owncall_id, in oneresponses.createcall. Sending one output per request leaves the other call pending, and the request is rejected. Turningparallel_tool_callsoff avoids the error but costs a model round trip per call. - Polling or resending doesn't help. A response waiting on a function call won't complete by itself, and resending the question just produces another call request.
- The portal doesn't run functions. Function tools are added in code; the Foundry portal can run an agent that has them but doesn't execute the functions or let you edit their definitions, so a playground test stops at the call request. Test the whole loop from code. Code interpreter runs code in its own sandbox, not your function.
- Runs expire after 10 minutes of total elapsed time. If a function's backend can take longer (a payroll recalculation that sometimes runs for half an hour), return a job ID and status immediately and give the agent a second function that checks the job. Background mode and streaming change how the response is delivered; they don't extend the run.
How do you choose a conversation-tracking approach?
An agent tracks a conversation in one of three ways: a server-side conversation object, previous_response_id chaining of stored responses, or store=false with the application keeping the history. The choice turns on who must hold the history and whether the service may persist it at all.
| Approach | Where history lives | Fits when |
|---|---|---|
Conversation (create once, pass its ID to responses.create) | Service stores every item — messages, tool calls and tool outputs — until you delete it | Users return days later to the same thread; you want to inspect and reuse the full history without rebuilding it |
previous_response_id | Each stored response links to the one before | Simple chaining of turns where responses are stored (the default) |
store=false + app-managed history | Only in your application | Policy forbids the service persisting any history; you pass the earlier output items as input on each request |
The agent definition itself is stateless across users — reusing the same agent name and version doesn't make it remember anyone. Conversations, chained responses and memory stores all keep data on the service, so none of them satisfies a "persist nothing" rule, even with short retention or deletion afterwards.
Long conversations are truncated, not trimmed
When a conversation grows past the model's context window, Foundry truncates the input automatically: the conversation still stores every item (listing its items shows them all), but only a subset is sent to the model. That is why something a user said early on — that they're vegetarian, say — can silently stop influencing answers with no error. Raising max_output_tokens or switching to response chaining doesn't change how much history fits. Facts that must last belong in memory, which retrieves them into context when relevant.
How do you add conversation memory to an agent?
Agent memory in Foundry Agent Service is a memory store that extracts durable facts (user profile memory) and summaries (chat summary memory) from conversations and retrieves them into later sessions; an agent reads it through the memory search tool. Memory complements conversation history: it keeps what should survive across conversations and past context truncation, not the full transcript.
Scoping memories to a user
The memory search tool's scope decides whose memory partition is used. A fixed value (such as team-alpha) shares one partition by design. Setting scope to {{$userId}} resolves the user on each request:
- If the request carries an
x-memory-user-idheader, its value is the scope. - Otherwise the scope falls back to the caller's Microsoft Entra tenant ID and object ID.
So the fallback works when each person's client calls the agent with that person's own Entra token. It fails when a backend calls on behalf of many users with one identity — a managed identity or service principal — because every user then resolves to the same scope and one user's preferences leak into another's chat. That shared-identity caller must send each end user's ID in x-memory-user-id; callers that already present per-user tokens are separated without it. Conversations don't partition memory, and RBAC roles don't change scope resolution.
Memory store settings
| Setting | Controls |
|---|---|
user_profile_details | What is extracted and committed — a description of what is worth remembering (say, a patient-portal agent keeping preferred appointment times and language) and what to leave out (government ID numbers, bank details) |
update_delay | When writes happen — a debounce after activity before memories are updated |
default_ttl_seconds | How long memories live; 0 means no expiry |
scope (on the tool) | Whose partition is read and written |
Three TTL rules matter in practice. TTL applies only to memory stores created after TTL support was introduced — an older store ignores the setting, so you create a new store with the TTL and attach it to the agent. Expiry counts from a memory's last update, which consolidation resets. And on a TTL store every memory expires, with no exception for details a user explicitly asked to be kept.
How do you integrate retrieval with Azure AI Search and file search?
An agent retrieves from your content through a knowledge tool attached to the agent: the Azure AI Search tool queries an existing search index through a project connection, and the file search tool searches files you upload into a Foundry-managed vector store. (Deciding which knowledge tool fits a requirement is covered in the AI-103 lesson on choosing Foundry models, retrieval methods and agent tools; this section is about configuring them.)
Azure AI Search tool settings
- Connection and index. The tool's index resource names a project connection and an
index_name. One Azure AI Search tool can target only one index; to search a second index, add another tool (or combine the content into one index). query_typesets the retrieval method (simple,semantic,vector,vector_simple_hybridorvector_semantic_hybrid, the default). It changes how results are ranked, not which documents are eligible.top_ksets how many results come back — fewer results, same eligibility.filteris an OData expression applied to every query the agent makes, such asdepartment eq 'finance'. It is the way to enforce a hard restriction on one shared index without splitting or rebuilding it; an instruction asking the agent to prefer certain documents still depends on the model.- Citations. The tool returns documents the model can cite as
url_citationannotations, but answers show sources only when the agent's instructions ask for citations (with retrievable title and URL fields in the index).
Keyless access. With Microsoft Entra ID authentication on the connection, the tool calls the search service as the system-assigned managed identity of the Foundry account that contains the project. That identity needs Search Index Data Contributor and Search Service Contributor on the search service; without them every call fails with 403, even though the developer's own account can query the index in the portal.
Private networking. A basic agent setup can't reach a search service whose public access is disabled behind a private endpoint — that needs the standard agent setup with virtual network injection. Key-based connections also aren't supported on private networking ("Unable to connect to Azure AI Search Resource" is the symptom), so the connection must use the project managed identity. Both changes are needed.
File search tool settings
store = openai.vector_stores.create(name="warranty-docs")
openai.vector_stores.files.upload_and_poll(
vector_store_id=store.id, file=open("terms.md", "rb"))
tool = FileSearchTool(vector_store_ids=[store.id])
- Ingestion is asynchronous. Files are parsed, chunked and embedded after upload. Use the polling helpers (
upload_and_poll,create_and_poll) or wait until the vector store's status iscompletedbefore testing. A run waits up to 60 seconds for files still processing only in the conversation's vector store, not the agent's. - One store per agent, one per conversation. The agent's file search tool takes one vector store, so content every chat should search goes into that one store — combine sets rather than adding a second agent-level store. Per-chat uploads belong in a vector store attached to that conversation, which keeps them private to the chat.
- Conversation stores expire seven days after they were last used in a run, and later runs on that conversation fail; recreate a vector store with the same files and reattach it.
- Supported files include .pdf, .docx, .doc, .pptx, .md, .txt, .html, .json and common code files (text in UTF-8, UTF-16 or ASCII). Spreadsheet formats such as Excel workbooks aren't on the list.
How do you implement orchestrated multi-agent solutions?
Multi-agent solutions in Foundry are built in two ways: an agent delegates to another agent as a tool through the A2A (agent-to-agent) protocol, or a workflow orchestrates several agents in Microsoft Agent Framework. The difference is who owns the conversation.
Delegation with the A2A tool
The A2A tool connects a Foundry agent to any A2A-compatible endpoint — another Foundry agent or one built with LangGraph or another framework. The calling agent keeps the conversation: it sends the request, receives the result and combines it with its own answer, with no relay code in your app. Setup has a direction:
- Enable incoming A2A on the target agent so it exposes an endpoint.
- Add the A2A tool to the calling agent, through a project connection to that endpoint.
- For a Foundry target, use agentic identity auth with the audience
https://ai.azure.comand grant the calling agent's identity the Foundry Agent Consumer role on the target's project — the least-privilege role that allows calling agent endpoints.
Connected Agents, the classic API's sub-agent feature, isn't available in the new Foundry Agent Service. A function tool could call another agent, but it puts the protocol, authentication and loop in your code.
Orchestration patterns in Microsoft Agent Framework
| Pattern | How agents interact | Fits |
|---|---|---|
| Sequential | Fixed order; each agent gets the previous output | Pipelines such as extract → translate → summarise |
| Concurrent | Agents run in parallel on the same input; results are combined | Independent analyses of the same input, merged when speed matters |
| Handoff | Agents transfer control by context; the active agent talks to the user directly over several turns and can hand back | A concierge agent passing a guest to a booking specialist who then talks to them |
| Group chat | Agents share one conversation; a manager picks the next speaker | Collaborative drafting and review |
| Magentic | A manager builds a plan, chooses who acts next, tracks progress in a ledger and replans when progress stalls | Open-ended problems whose steps aren't known in advance; enable_plan_review=True lets a person approve the plan first |
A handoff transfers the user to another agent; the A2A tool doesn't. Choose by whether the specialist must talk to the user itself.
Where orchestration runs
Foundry portal (visual) workflows retire on 1 December 2026. For new or existing workflows, Microsoft recommends Microsoft Agent Framework workflows — defined in code (or declarative YAML) and kept in source control, with built-in orchestrations and human-in-the-loop support — deployed to Foundry as a hosted agent. Agent Framework has no visual graph editor, so moving a portal workflow is a rebuild in code. Azure Logic Apps is the documented route for teams that want a low-code visual designer. A single prompt agent whose instructions describe several steps can't enforce their order or an approval gate.
How do you build semiautonomous workflows with safeguards and approvals?
A semiautonomous agent acts on its own for low-risk steps and pauses for a person before risky ones. The safeguard must be enforced by the platform or framework, not requested in the prompt: an instruction to "check with someone first" is a suggestion to the model, and a flag check inside the function only makes the call fail instead of pausing for a decision.
MCP tool approvals
The MCP tool's require_approval takes four forms:
| Value | Effect |
|---|---|
"always" (default) | Every tool on the server needs approval |
"never" | No tool needs approval |
{"never": [...]} | Listed tools run freely; everything else, including tools the server adds later, needs approval |
{"always": [...]} | Only listed tools need approval; unlisted and future tools run freely |
allowed_tools is a different control: it removes tools from the agent entirely rather than gating them. When approval is needed, the response contains an mcp_approval_request item (server label, tool name, arguments). The app answers with an mcp_approval_response whose approval_request_id is that item's id, in a follow-up request that passes previous_response_id:
decisions = [
{
"type": "mcp_approval_response",
"approval_request_id": req.id, # the request item's id
"approve": policy_allows(req.server_label, req.name),
}
for req in response.output
if req.type == "mcp_approval_request"
]
response = openai.responses.create(
input=decisions, previous_response_id=response.id, ...)
Human-in-the-loop in Microsoft Agent Framework
| Mechanism | Use it when |
|---|---|
@tool(approval_mode="always_require") | A specific function call (say, cancel_subscription or delete_tenant) must be approved or rejected before it executes; the call surfaces as an approval request carrying its arguments, and other tools stay automatic |
ctx.request_info(...) + a @response_handler method | An executor needs information only a person can supply (a budget code, a choice between drafts) that isn't tied to a tool call; the workflow emits a request_info event and pauses until the response is sent back |
| Checkpointing | Approvals take hours or days and the host process restarts: checkpoints save workflow state with its pending requests; restoring re-emits them, and workflow.run with the checkpoint_id and the responses resumes without re-running finished executors |
Approval mechanisms decide whether a step may run; checkpoints make the wait durable. A long wait across restarts usually needs both. An open streaming connection or a background response doesn't survive a process restart or gate anything.
Long-running agent work
- Background mode (
background=True) returns immediately while the agent runs asynchronously; pollresponses.retrievewhile the status isqueuedorin_progress, then read the output. - Slow MCP tools hit the 100-second synchronous timeout. Run the agent in background mode (a preview capability, available only on models that support it) and have the MCP server run the job as an MCP task that returns a related-task ID the runtime can poll.
- Slow function tools hit the 10-minute run expiry; return a job status at once and add a status-check function.
How do you monitor, evaluate and analyze errors in deployed agents?
You monitor and evaluate an agent at two levels: agent evaluators score whether its behaviour was right (intent, task, each step of tool use), and traces show what actually happened in each run so you can find where it went wrong. (Setting up tracing, token analytics and latency breakdowns is covered in the AI-103 lesson on optimizing and operationalizing generative AI systems; this section uses them for error analysis.)
Choosing an agent evaluator
| Evaluator | Checks |
|---|---|
| Intent Resolution | Did the agent understand what the user wanted? |
| Task Adherence | Did its actions follow the rules, procedures and policy constraints in its instructions (for example a rule never to promise delivery dates)? |
| Task Completion | Did it finish the job? |
| Task Navigation Efficiency | Did the steps match labelled expected_actions? Returns pass/fail plus precision, recall and F1 |
| Tool Selection / Tool Call Accuracy | Were the right tools chosen and called appropriately (LLM-judged, no expected sequence)? |
| Tool Input Accuracy | Do parameters meet six criteria: grounded, correct type, correct format, required present, no unexpected parameters, appropriate values? |
| Tool Call Success | Did calls run without technical errors? |
| Tool Output Utilization | Did the agent correctly use what the tools returned in its reasoning and answer? |
Read a failure step by step: calls that succeed but an answer quoting a total the tool never returned is an output-use problem; an amount written as "fifty euros" where the schema wants a number, or an undefined extra parameter, is an input problem; a complete answer that breaks a stated rule is an adherence problem. Task Navigation Efficiency's matching_mode sets strictness: exact_match (same steps, same order, nothing extra), in_order_match (all expected steps in order, extra steps allowed) and any_order_match (order ignored).
Error analysis with traces
- Tool spans without arguments. With
AZURE_EXPERIMENTAL_ENABLE_GENAI_TRACINGon, the SDK emits GenAI spans such asexecute_tool, but message content and tool-call arguments are recorded only whenOTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENTis true, set before instrumenting. Content recording can capture personal data, so keep it to development unless compliance allows it. - Your own code between calls. The instrumentor traces SDK calls only. Decorate helpers with
@trace_function(fromazure.ai.projects.telemetry) to get a span per call, with parameters ascode.function.parameter.<name>and the return value. These spans record parameters regardless of the content-recording setting, so keep sensitive values out of traced functions' arguments (pass an opaque record key rather than names or personal details). - Linking traces to the agent. Client-side traces correlate with an agent in the Foundry portal when
responses.createpassesagent_referencewith both the agent's name and id.
Tip. Expect Microsoft-style scenarios: a company, a Foundry agent with some tools, a symptom (a function call that never produces an answer, a 401 or 403, an "Invalid tool schema" error, memories leaking between users, a run that expires, an early detail the agent forgets) and a constraint such as least privilege, no stored history, least custom code, no redeployment, or enforcement by the platform rather than the model. Some items show a short Python snippet with a blank and ask which line or value completes it, so read SDK field names precisely rather than recognising them loosely. Others describe a failure and ask for the evaluator, orchestration pattern or tool setting that fits, and some are select-TWO items where a complete fix needs two separate changes — judge each option on whether it is necessary on its own.
- Instructions carry the agent's role, scope, tool decision rules and what to do on empty results; tool_choice=required (set by the app) is the deterministic way to force a tool call.
- Strict function schemas list every property in required and set additionalProperties to false; optional fields allow null. enum restricts values, required makes a field mandatory.
- The app runs functions: return a function_call_output per call with the matching call_id, all outputs in one request. Runs expire after 10 minutes, so slow work returns a job status.
- Conversations store every item but truncate the model input past the context window; store=false with app-held history is the only option when nothing may be persisted.
- Memory scope {{$userId}} uses x-memory-user-id if sent, else the caller's Entra identity — a backend serving many users must send the header. TTL only applies to stores created after TTL support.
- Azure AI Search tool: filter applies to every query; keyless access needs Search Index Data Contributor + Search Service Contributor on the Foundry account's managed identity. File search allows one vector store per agent and per conversation.
- OpenAPI tools need OpenAPI 3.0/3.1, letter/-/_ operationIds and JSON bodies; API keys live in a project connection plus securitySchemes. Toolboxes share tools through one MCP endpoint but can't hold function tools.
- The A2A tool keeps the calling agent in charge (incoming A2A on the target, Foundry Agent Consumer for the caller); handoff transfers the user. Connected Agents isn't in the new Agent Service, and portal workflows retire on 1 December 2026.
- Enforce approvals in the platform: require_approval {"never": [...]} gates everything unlisted; approval_mode="always_require" gates a function; request_info asks for input; checkpoints make waits survive restarts.
- Match evaluators to the failing step (Task Adherence, Tool Input Accuracy, Tool Output Utilization, Task Navigation Efficiency with matching_mode) and keep content recording to development.
Frequently asked questions
Does Foundry Agent Service run my function tools?
No. When the model calls a function tool, Foundry Agent Service returns a function_call item with the function name, JSON arguments and a call_id, and stops. Your application runs the function and sends a function_call_output item with the same call_id back to the agent, returning every output from one response in a single request. The Foundry portal doesn't execute function tools either, so test function calling from code.
What is the difference between a conversation and previous_response_id in Foundry agents?
A conversation is a server-side object that stores every item of a chat — messages, tool calls and tool outputs — until you delete it, and you pass its ID on each request. previous_response_id instead chains a new response to an earlier stored response. Both keep history on the service; if policy forbids that, set store to false and resend the earlier output items from your application.
How do I keep each user's agent memories separate in Foundry?
Set the memory search tool's scope to {{$userId}}. Foundry then uses the x-memory-user-id header when a request carries it and otherwise falls back to the caller's Microsoft Entra tenant and object IDs. Users who call with their own Entra tokens are separated automatically, but a backend that calls on behalf of many users with one identity must send each user's ID in x-memory-user-id.
When should I use the A2A tool instead of a handoff orchestration?
Use the A2A tool when one agent should ask another agent for a result and keep talking to the user itself: the calling agent receives the remote agent's answer and combines it with its own. Use Microsoft Agent Framework's handoff orchestration when a specialist agent should take over the conversation with the user and possibly hand it back later.
What replaces Foundry portal workflows after they retire?
Foundry portal workflows retire on 1 December 2026. Microsoft recommends rebuilding them as Microsoft Agent Framework workflows, defined in code or declarative YAML with built-in orchestration patterns and human-in-the-loop support, and deploying them to Foundry as hosted agents. Teams that want a low-code visual designer can use Azure Logic Apps instead.
How do I require human approval before an agent calls a risky tool?
For an MCP tool, set require_approval: "always" (the default) gates every tool, while the object form {"never": [list]} lets only the listed tools run freely and gates everything else, including tools the server adds later. Answer each mcp_approval_request item with an mcp_approval_response whose approval_request_id is that item's id. In Microsoft Agent Framework, declare the function with approval_mode set to always_require, and enable checkpointing if approvals can outlast the hosting process.
Which tools can't be added to a Foundry toolbox?
Function tools can't go in a Foundry toolbox, because the client application runs them. Some built-in tools — including SharePoint, Grounding with Bing, image generation and computer use — are supported only when attached directly to an agent. MCP, OpenAPI, Azure AI Search, file search, web search, code interpreter and A2A tools can be shared through a toolbox.
Why don't my agent traces show tool call arguments?
Foundry's client-side tracing records message content and tool-call arguments only when OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT is set to true before instrumentation; by default spans such as execute_tool carry no content. Because recorded content can include personal data, Microsoft recommends enabling it only in development unless your compliance rules allow otherwise.
Source
This lesson covers the "Implement generative AI and agentic solutions" domain of the official AI-103 exam guide. Vendors revise their guides — check the source for the current version.
- Microsoft AI-103 study guide — Microsoft Learn
Sign up free to mark lessons complete, bookmark topics and track your exam readiness.