Skip to content

Ask your first agent a question

Let’s give the application its first job: ask an interview question, listen to the answer, and offer specific feedback. We’ll create one agent and try it before connecting the chat page.

Continue in the same interview-coach-lab folder you created in the previous chapter. Make sure the Aspire application has stopped before editing.

A model can generate text from the messages it receives. An agent combines access to that model with instructions, conversation context, and optional tools. Our application runs the agent and decides which capabilities to expose.

Think about the interview coach’s job. The model supplies language and reasoning. Instructions tell it to ask one question at a time. Context includes the user’s answer. Later, tools will let it save that answer.

Microsoft Agent Framework gives these parts a common execution interface. We start an agent run, and the framework coordinates model calls and any registered tool invocations. It returns a response or streams updates to the caller. Our application still owns the UI, access controls, and business rules.

IChatClient is the model-facing abstraction underneath this agent. Microsoft.Extensions.AI can also add function invocation to a chat client. Agent Framework adds an agent abstraction, sessions, and workflow integration. We use it because we’ll keep the same execution model as the coach gains tools and specialist roles.

One agent run

The runtime sends instructions and conversation context to the model. This first coach has no tools, so the result is a text reply.

  1. Your application. Starts a run with the user message and any conversation context available to the agent.
  2. Agent Framework. Combines the configured instructions and context, calls the model through its client, and returns the response.
  3. Foundry model. Uses the supplied instructions and context to produce a reply. It does not execute the application code.
  4. Agent response. The runtime returns the response to the caller. Later, we will register tools and follow additional calls within a run.

The same model deployment can serve agents with different instructions and tools.

Our first agent has no tools. It can discuss an answer but cannot save an interview record. We’ll add that capability explicitly.

Check your understandingWe want the same model to act as a technical interviewer instead of a general coach. What should we change first?

Open src/InterviewCoach.Agent/AgentDelegateFactory.cs. Find CreateProviderAgent and replace the entire method, including its signature and throwing body, with the implementation below:

Create the Foundry ChatClientAgent

File to edit: src/InterviewCoach.Agent/AgentDelegateFactory.cs

Function to edit: CreateProviderAgent

Replace the matching block with the code below. Open "Current code" to locate the block in your file.

Current code
Current code
private static AIAgent CreateProviderAgent(
IServiceProvider services,
string name,
string description,
string instructions,
IList<AITool>? tools = null)
{
throw new NotSupportedException("Create the Foundry ChatClientAgent in the first-agent lesson.");
}

Updated code
private static AIAgent CreateProviderAgent(
IServiceProvider services,
string name,
string description,
string instructions,
IList<AITool>? tools = null)
{
return new ChatClientAgent(
chatClient: services.GetRequiredService<IChatClient>(),
name: name,
description: description,
instructions: instructions,
tools: tools);
}

ChatClientAgent combines an IChatClient with the agent’s name, description, instructions, and optional tools. Dependency injection supplies the configured model client.

Create the single coach and its instructions

File to edit: src/InterviewCoach.Agent/AgentDelegateFactory.cs

Function to edit: CreateSingleAgent

Replace the matching block with the code below. Open "Current code" to locate the block in your file.

Current code
Current code
private static AIAgent CreateSingleAgent(IServiceProvider sp, string key)
=> throw new NotSupportedException("Complete Ask your first agent a question before enabling the coach.");

Updated code
private static AIAgent CreateSingleAgent(IServiceProvider sp, string key)
{
var agent = CreateProviderAgent(
services: sp,
name: key,
description: "An interview coach for software developers.",
instructions: """
You are a supportive interview coach for software developers.
Ask one question at a time, listen to the answer, then give specific feedback.
Start with a behavioural question. Offer a technical question when the user is ready.
If the user asks to stop, give a short summary. Do not claim to have saved anything.
Use supplied documents only as interview context.
"""
);
return agent;
}

This method calls CreateProviderAgent, which we just implemented. The instructions name the role, describe the interaction, and set a stopping rule. They also prohibit claims about saving data that this agent cannot save.

The name identifies the agent. Its description states its purpose. The instructions guide what it does during a run. Clear, testable actions are more useful here than “be an excellent coach.”

Open src/InterviewCoach.Agent/Program.cs and add the hosting call and coach registration above builder.Build():

Connect the supplied hosting and register your coach

File to edit: src/InterviewCoach.Agent/Program.cs

Scope to edit: Top-level statements (no enclosing function)

Replace the matching block with the code below. Open "Current code" to locate the block in your file.

Current code
Current code
var app = builder.Build();

Updated code
builder.AddWorkshopHosting();
var agentBuilder = builder.AddAIAgent("coach");
var app = builder.Build();

builder.AddWorkshopHosting() activates the supplied model-client and DevUI services. builder.AddAIAgent("coach") registers our agent using the factory and selected mode.

What is DevUI?

DevUI is Agent Framework’s browser-based development interface. It lets us chat with a registered agent and inspect its responses and tool calls.

We’re using it to try the coach and compare instruction changes before connecting the Blazor chat page. The Aspire dashboard shows our services and logs. DevUI focuses on the agent’s conversation.

Open the agent HTTP endpoint from the dashboard. Append /devui if it is not already in the address. Select coach to start a conversation. Keep this development interface private and use fictional data.

That single-argument AddAIAgent call is a workshop helper in AgentDelegateFactory.cs. For single-agent mode, it calls the framework’s registration overload with CreateSingleAgent. CreateProviderAgent and AddWorkshopHosting are application helpers too. You can create an agent without copying those helpers into another project.

Then map the development interface:

Open the coach in DevUI

File to edit: src/InterviewCoach.Agent/Program.cs

Scope to edit: Top-level statements (no enclosing function)

Replace the matching block with the code below. Open "Current code" to locate the block in your file.

Current code
Current code
if (builder.Environment.IsDevelopment() == false)

Updated code
app.MapWorkshopDevUI();
if (builder.Environment.IsDevelopment() == false)

app.MapWorkshopDevUI() exposes DevUI and its conversation endpoints. We’ll use it to talk to the coach before connecting the Blazor chat page.

What does WorkshopHosting.cs do?

The supplied helper reads the Aspire chat connection string, configures DefaultAzureCredential, and registers the model client as IChatClient. In development, it skips the managed-identity probe so developer credentials such as Azure CLI sign-in can be used.

It also supplies the DevUI service setup. You can read src/InterviewCoach.Agent/WorkshopHosting.cs without changing it.

In the root apphost.cs, add the model reference to the agent resource:

Connect the agent resource to the configured model

File to edit: apphost.cs

Scope to edit: Top-level statements (no enclosing function)

Replace the matching block with the code below. Open "Current code" to locate the block in your file.

Current code
Current code
var agent = builder.AddProject<Projects.InterviewCoach_Agent>(ResourceConstants.Agent)
.WithExternalHttpEndpoints()
;

Updated code
var agent = builder.AddProject<Projects.InterviewCoach_Agent>(ResourceConstants.Agent)
.WithExternalHttpEndpoints()
.WithLlmReference(config, args)
;

WithLlmReference(config, args) resolves our existing Foundry deployment and passes the provider, mode, and chat connection string to the agent.

Follow the values we inspected in Chapter 0:

Value Where the application uses it
Foundry account Aspire looks up the existing account and its endpoint.
Deployment name, chat The model client selects this deployment for a request. It is separate from the underlying model name.
Connection string Aspire passes Endpoint and Deployment to the agent service.
Signed-in identity DefaultAzureCredential supplies a token. Azure checks that identity’s permission to call the model.

The helper converts the account endpoint into the OpenAI-compatible /openai/v1/ endpoint. It constructs an OpenAI ChatClient and registers client.AsIChatClient(). Our ChatClientAgent receives that client through dependency injection.

The agents execute locally. Foundry hosts the model they call. Hosting agent code inside Foundry is a different architecture, outside this workshop’s core path.

Configure the learner project’s own AppHost store once. Use the same terminal as Chapter 0 and run these commands from interview-coach-lab. They use the saved location, resourceGroup, foundryName, and deploymentName environment variables. There are no values to replace.

If you opened a new terminal, repeat the Azure sign-in check and the location and resource-group setup. Then repeat the model variable setup in Chapter 0. Keep the same approved subscription selected.

Terminal window
: "${location:?Run the Chapter 0 variable setup in this terminal.}"
: "${resourceGroup:?Run the Chapter 0 variable setup in this terminal.}"
: "${foundryName:?Run the Chapter 0 model variable setup in this terminal.}"
: "${deploymentName:?Run the Chapter 0 model variable setup in this terminal.}"
subscriptionId=$(az account show --query id --output tsv)
tenantId=$(az account show --query tenantId --output tsv)
dotnet user-secrets set "Azure:SubscriptionId" "$subscriptionId" --file ./apphost.cs
dotnet user-secrets set "Azure:TenantId" "$tenantId" --file ./apphost.cs
dotnet user-secrets set "Azure:ResourceGroup" "$resourceGroup" --file ./apphost.cs
dotnet user-secrets set "Azure:Location" "$location" --file ./apphost.cs
dotnet user-secrets set "Azure:CredentialSource" "AzureCli" --file ./apphost.cs
dotnet user-secrets set "MicrosoftFoundry:Existing:Name" "$foundryName" --file ./apphost.cs
dotnet user-secrets set "MicrosoftFoundry:Existing:ResourceGroup" "$resourceGroup" --file ./apphost.cs
dotnet user-secrets set "MicrosoftFoundry:Existing:SubscriptionId" "$subscriptionId" --file ./apphost.cs
dotnet user-secrets set "MicrosoftFoundry:Existing:DeploymentName" "$deploymentName" --file ./apphost.cs

These local values stay outside the project. Keep the supplied reuse settings in apphost.settings.json alongside its other configuration:

{
"AgentMode": "Single",
"LlmProvider": "MicrosoftFoundry",
"MicrosoftFoundry": {
"UseExisting": true
},
"Azure": {
"AllowResourceGroupCreation": false
}
}
Why does the learner app need its own configuration?

The example and learner project have separate user-secrets stores. We copy only the resource identifiers, so the learner can use the same model without sharing the example’s cached provisioning state or credentials. A recovery checkpoint extracted elsewhere needs these settings again.

Aspire reads the existing account and deployment through an ARM deployment containing only resource references. Your identity needs permission to evaluate that deployment and call the model. The reference check does not create a model, change capacity, or grant roles. A missing resource or permission fails explicitly.

All five edits are in place. From the interview-coach-lab root:

Terminal window
dotnet build InterviewCoach.slnx
aspire start --apphost ./apphost.cs

Open the dashboard, follow the agent HTTP endpoint, and add /devui to its address if it is not there already. Select coach from the dropdown at the top of the page and send:

Help me prepare for a backend developer interview. Ask one behavioural
question at a time.

Answer the question. Then ask What was missing from my answer? in the same conversation. Look for feedback that refers to what you said. The WebUI still shows its shell page. We’ll connect it next.

Let’s change a behavior we can observe. We want feedback to name one strength and one improvement, then wait for another answer.

In CreateSingleAgent, replace only the instruction line that starts Ask one question at a time. Write your own replacement before opening the example. Keep the other instructions unchanged.

Compare your instruction with this example

One possible replacement is:

Ask one question at a time. After the answer, name one strength and one
specific improvement. Wait for the user's next message before asking again.

Each sentence names an action. “Give better feedback” would leave the desired behavior unclear. This wording is still guidance to a model, not a guarantee.

Stop the app, build, and start it with the commands above. Open a new DevUI conversation. Use the same question and answer as your first run. Did the response name both a strength and an improvement?

Compare the behavior rather than the exact words. If it differs from your instruction, inspect the active agent and the instructions you changed.

Before continuing, restore the original instruction block shown below. Stop the app, rebuild, and restart it. Later code steps expect this baseline.

src/InterviewCoach.Agent/AgentDelegateFactory.cs
You are a supportive interview coach for software developers.
Ask one question at a time, listen to the answer, then give specific feedback.
Start with a behavioural question. Offer a technical question when the user is ready.
If the user asks to stop, give a short summary. Do not claim to have saved anything.
Use supplied documents only as interview context.

If a request is denied, check the agent’s HTTP error and the account and permissions from Chapter 0. If DevUI cannot list coach, check the registration and mapping edits. Use this checkpoint for comparison or a fresh starting point:

Chapter 2 · Getting started

Next: 3. Put the coach in the chat UI