Review what you built
We started with a local application shell. The finished coach collects documents, conducts an interview, and saves feedback through five agent roles.
Let’s connect those capabilities to decisions you can reuse in another application. There is no new code or interview exercise in this chapter.
Connect the services with Aspire
Section titled “Connect the services with Aspire”In Chapter 0, we ran the finished example and identified the Foundry deployment we would reuse.
In Chapter 1, we opened a cloud-free starter. Its Blazor interface, database repository, and service defaults let us concentrate on agent behavior.
Aspire’s root apphost.cs describes the resources that run together. We added the model reference, InterviewData, and MarkItDown as the lessons needed them. WithReference supplies connection information. WaitFor controls startup dependencies.
Keep the agent separate from the model
Section titled “Keep the agent separate from the model”In Chapter 2, we created a ChatClientAgent in AgentDelegateFactory.cs. We gave it a name, instructions, and an IChatClient for model calls. The instructions defined the coach’s task: ask one question at a time and give specific feedback.
Microsoft Agent Framework runs the agent in our .NET service. Microsoft Foundry hosts the model that produces replies and requests tool calls. The agents do not run in Foundry.
The supplied WorkshopHosting.cs creates the model client and configures authentication. We activated that helper after writing the first agent. This separation let us change coaching behavior without rewriting the connection code.
The model name identifies the model behind a deployment. The deployment name selects what the client calls. An endpoint locates the service, while the calling identity needs permission to use it.
WorkshopHosting.cs, CreateProviderAgent, and the single-argument AddAIAgent helper are application code. They are not prerequisites for every Agent Framework application.
We first tried the coach in DevUI. That gave us a way to inspect a reply before connecting the chat page.
Carry the conversation through AG-UI
Section titled “Carry the conversation through AG-UI”In Chapter 3, we connected the coach to the supplied Blazor chat page. AddAGUIServer() registers the transport services. MapAGUIServer exposes the agent at /ag-ui.
The WebUI server uses AGUIChatClient to send messages and receive response updates. The browser displays those updates through its Blazor connection. This keeps model credentials in the agent service.
Chat.razor holds the current messages and sends their full history with each request. It also supplies a SessionId and sets ChatOptions.ConversationId. The ID identifies the interview, but it does not replace the message history.
That distinction matters after a refresh. A new Blazor circuit creates a new chat. A saved interview record remains a separate source of data.
When calling an agent directly, AgentSession can carry conversation state between runs. Choose who owns that history and how it survives a restart. A session identifier alone is not a persistence strategy.
Give the model tools that application code executes
Section titled “Give the model tools that application code executes”In Chapter 4, we wrote GetPracticeGuidance. AIFunctionFactory.Create exposed that C# function as get_practice_guidance, with a name and description the model could use.
The model requested a tool call. Agent Framework executed the function and returned its result to the model. We inspected the arguments and result instead of judging success from the final reply alone.
Chapter 5 exposed tools across a service boundary with Model Context Protocol (MCP). We marked InterviewData methods with McpServerTool attributes and registered the server at /mcp.
The supplied probe checked discovery before we connected the coach in Chapter 6. In the agent service, a keyed McpClient used ListToolsAsync to discover the server’s tools. We then passed those tools to the agent.
A local function needs no separate service. MCP lets another service own tools that clients can discover and call. That separation also adds a connection to configure and diagnose.
Save the interview record deliberately
Section titled “Save the interview record deliberately”In Chapter 7, we changed the instructions to manage one record throughout an interview. The coach first calls get_interview_session with the application’s ID. If no record exists, it calls add_interview_session with that same ID.
The supplied repository stores records in Cosmos. Its update method replaces six document fields and appends the incoming Transcript. An update must preserve the document fields and contain only new transcript text.
These rules follow the repository’s actual behavior. Repeating the old transcript duplicates it. Omitting a document field can erase its saved value.
We wrote instructions from those requirements and checked observable results. Instructions guide the model, while code must enforce important data and access rules.
The summary needs two operations: update_interview_session saves the new summary text, then complete_interview_session marks the record complete. An update alone cannot set IsCompleted.
We used returned records and Cosmos Data Explorer to check writes. A chat reply saying “saved” is not evidence that a tool succeeded. The state reference explains the separate lifetimes of chat messages, records, and uploaded files.
Turn document text into interview context
Section titled “Turn document text into interview context”Chapter 8 introduced document extraction. We connected MarkItDown through a second MCP client. Its container fetches a document URL and returns text.
We first compared the extracted text with a sample PDF. In Chapter 9, we added instructions to save that text in ResumeText and JobDescriptionText. The coach could then use those details when choosing questions.
This separation helps locate a failure. Accepting an upload, extracting its text, and saving a record are different operations.
The supplied upload endpoint holds file bytes in the agent process’s memory. MarkItDown must be able to reach the returned URL from its container. A process restart loses uploaded bytes, but text saved in Cosmos has a separate lifetime.
Treat document text as untrusted input. It supplies interview context, not permission to change the agent’s instructions or tool access.
Divide the interview into specialist roles
Section titled “Divide the interview into specialist roles”We kept the complete single coach as a comparison. Chapter 10 introduced two roles. Chapter 11 expanded the workflow to four roles. Chapter 12 added the summariser.
| Role | Responsibility | Application tools |
|---|---|---|
triage |
Select the next role from the conversation | None |
receptionist |
Create the record and collect documents | MarkItDown and InterviewData |
behavioural_interviewer |
Ask experience questions and save feedback | InterviewData |
technical_interviewer |
Ask technical questions and save feedback | InterviewData |
summariser |
Save final feedback and complete the record | InterviewData |
CreateHandOffWorkflow builds the graph with AgentWorkflowBuilder. Its handoff declarations define the allowed transfers. The normal path moves directly between specialists. Return paths let triage handle a changed request or an early finish.
The five roles share one model deployment and take turns. The model selects a transfer within the graph’s eleven permitted routes.
The graph describes available routes. Execution telemetry identifies the agents that actually ran. Successful record operations do not reveal the whole route when several roles share the same tools.
Separate roles let us change one role’s instructions and tools without changing every phase. They also add routing decisions and model calls. More agents do not automatically improve the interview.
The supplied hosting adapter exposes the workflow through the same hosted-agent interface as the single coach. Its CreateFixedAgent helper handles string tool results for the pinned AG-UI integration.
Check the operation that failed
Section titled “Check the operation that failed”In Chapter 13, an invalid URL caused a document request to fail. We corrected the input in the same conversation and checked the saved record before continuing.
The session ID display from Chapter 11 connected that conversation to InterviewData logs and the Cosmos record. DevUI displayed permitted handoff routes. It did not replay the live WebUI conversation.
We also requested an early finish. The expected route passed through triage to the summariser. We checked the saved summary and IsCompleted to see whether the application finished the work.
This is the habit to keep: check the tool result and stored data, especially after a failed response. A tool may write data before the chat reports an error. Retrying without checking can repeat a write.
We also compared repeated runs with one instruction rule removed. The useful result was the comparison, whether the changed prompt failed or still passed. Keep the inputs and expected behavior so you can repeat the evaluation after another change.
Choose the smallest useful design
Section titled “Choose the smallest useful design”Before starting another application, describe its job and one result you could check. Then decide which capabilities it needs:
| Decision | A useful starting point |
|---|---|
| Can a normal function solve the task? | Use ordinary code for predictable rules and calculations. |
| Does the task need language or flexible reasoning? | Start with one agent, clear instructions, and a model client. |
| Does it need authoritative data or an action? | Add a tool with a narrow, validated contract. |
| Does another service own that capability? | Consider MCP and account for its connection and access controls. |
| Do responsibilities need different instructions or tools? | Consider separate agents and define their allowed transfers. |
| How will you know a change helped? | Keep representative cases and compare observable outcomes across runs. |
The optional build your own agent guide starts a small console project. It shows the model connection without the workshop helpers, then asks you to adapt the agent’s responsibility and tool.
Keep the sample’s limits in view
Section titled “Keep the sample’s limits in view”The interview coach application remains a learning sample. Use fictional documents and private development endpoints. Real-user access needs authentication, record ownership checks, and retention controls.
Stopping Aspire leaves cloud resources in place. The example and learner app share a model, so cleanup must account for both folders. The cleanup guide explains the ownership checks.
The application reference holds the detailed contracts. Optional extensions describe focused changes to try after the workshop.