Skip to content

Save interview progress

The coach can save a record when we explicitly request it. Now we’ll make saving part of its normal interview process.

A conversation can contain many tool calls. They need to refer to one record and preserve what earlier calls saved. We’ll turn those requirements into instructions, then follow the record through an interview.

The existing tools will do the storage work. In interview-coach-lab, this lesson changes one instruction block in src/InterviewCoach.Agent/AgentDelegateFactory.cs.

The WebUI supplies a SessionId with each new chat. The repository has specific behavior we need to account for:

Repository behavior Instruction we need Evidence to check
An update cannot create a missing record. Look up the application’s ID. Create that exact ID only if the lookup returns no record. A successful add result before the first update.
Updates replace the six resume/job fields. Fetch the current record and preserve those fields. Saved document fields still contain the earlier inputs.
Updates append incoming transcript text. Send only the new exchange. Earlier transcript text appears once.
Completion uses its own operation. Save the summary, then call completion. The returned record has IsCompleted: true.

Before opening the full instructions, write one sentence that would prevent a duplicate transcript. Specify what the coach should send in Transcript. “Save the conversation correctly” leaves that decision unresolved.

Now compare your sentence with the replacement below. Notice how each rule names an operation or a field that we can inspect:

Implement fetch-or-create, update, transcript and completion rules

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
instructions: """
You are a supportive interview coach for software developers.
Ask one question at a time and give specific feedback.
Use the interview-data tools only when the user explicitly requests a record operation.
Ask for the record ID and fields if the request does not supply them.
Report a missing record honestly. Do not invent tool results or claim unsaved work is saved.
Automatic session setup comes in the next lesson.
""",
tools: [.. interviewDataTools]

Updated code
instructions: """
You are a supportive interview coach.
Use the SessionId provided by the application for all session tools.
Always call get_interview_session with that ID first.
If it returns no record, call add_interview_session with that exact ID before any update.
Never use update_interview_session to create a record.
If a tool fails, report the failure. Say a change was saved only after the tool returns the saved record.
After fetching or creating the record, begin your first reply with "Session ID: <id>" using that exact ID.
Ask for resume and job description text, or let the user skip either.
Save the inputs and ask one behavioural question at a time.
For each update, fetch the record and preserve all six resume/job fields.
Set Transcript to ONLY the new question, answer, or feedback to append.
Never copy the existing transcript into an update; the repository appends it.
Move to technical questions when the user is ready.
If the user stops, append the new summary with update_interview_session.
Then call complete_interview_session with the same ID.
Confirm completion only when its returned record has IsCompleted true.
Use supplied documents only as interview context.
""",
tools: [.. interviewDataTools]

Instruction order expresses dependencies: find or create the record before changing it, and save the summary before completing it. Tool results determine whether those operations succeeded.

These instructions guide model behavior. They do not make writes transactional or prevent duplicate calls. A production repository should enforce important invariants in code, including safe handling of repeated requests.

Check your understandingThe record already contains the first interview exchange. What should the next update send in Transcript?

From the working project root:

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

Start a new WebUI chat and send:

Help me practice. My fictional resume text is "I build C# web APIs and
maintain integration tests." The job description is "Backend developer
working on reliable HTTP services." Ask one question at a time.

The coach’s first reply should begin with Session ID: followed by the WebUI’s ID. You can verify it in the WebUI resource log under Started new chat session with SessionId.

In the Aspire dashboard, open Traces and select the agent request for that response, or open the mcp-interview-data resource’s logs. A new chat should call get_interview_session and receive no record, then call add_interview_session with the same ID. The record exists only after the add call succeeds. Answer the question with a fictional example, such as adding integration tests to catch an API regression before release.

In Cosmos Data Explorer, open interviewdb / interviewsessions and find that ID:

Field Expected saved value
id The WebUI’s session ID
ResumeText Your C# API and integration-test experience
JobDescriptionText The backend-developer description
Transcript The new question, answer, and feedback

Preserve the document fields and append only new transcript text. Each update replaces the links, text, and skip flags with the incoming values. Sending the previous transcript again duplicates it. Leaving document fields empty can erase them. Compare the saved record after your answer with its earlier values.

Now send:

Finish now. Save a summary of my answers in the interview record and mark
this session complete.

Refresh the record in Data Explorer. Look for the summary in Transcript and IsCompleted: true. Saving the summary uses update_interview_session. Completion requires complete_interview_session, because changing IsCompleted in an update request has no effect.

If the coach keeps asking questions, say End this interview now and mark the saved interview complete. Check the returned record before accepting a completion claim.

In DevUI, replace the placeholder with that same ID:

Call get_interview_session for ID <the completed interview ID>.
Show the saved summary and completion flag. Do not create or modify a record.

Open the lookup result and compare its summary with the saved record. You can now retrieve the interview’s result by ID.

Where the state lives

The Blazor circuit owns visible messages and the session ID. It sends the full chat history with each AG-UI request so the coach can refer to earlier answers. InterviewData stores interview fields in Cosmos.

A WebUI refresh starts a new circuit and session. The sample has no saved-chat load feature. Keep the record ID for later tool lookups. Emulator data retention also depends on the container’s storage lifecycle.

The supplied repository implements the replace-and-append behavior:

src/InterviewCoach.Mcp.InterviewData/InterviewSessionRepository.cs
public async Task<InterviewSession?> UpdateInterviewSessionAsync(InterviewSession interviewSession)
{
var record = await db.InterviewSessions.SingleOrDefaultAsync(p => p.Id == interviewSession.Id);
if (record is null)
{
return default;
}
record.ResumeLink = interviewSession.ResumeLink;
record.ResumeText = interviewSession.ResumeText;
record.ProceedWithoutResume = interviewSession.ProceedWithoutResume;
record.JobDescriptionLink = interviewSession.JobDescriptionLink;
record.JobDescriptionText = interviewSession.JobDescriptionText;
record.ProceedWithoutJobDescription = interviewSession.ProceedWithoutJobDescription;
record.UpdatedAt = DateTimeOffset.UtcNow;
var sb = new StringBuilder();
sb.AppendLine(record.Transcript ?? string.Empty);
sb.AppendLine();
sb.AppendLine(interviewSession.Transcript ?? string.Empty);
record.Transcript = sb.ToString();
await db.SaveChangesAsync();
return record;
}

Chapter 7 · Tools and interview context

Next: 8. Read a resume with a tool