MentorAgent Docs
Menu

Questa pagina non è ancora tradotta in italiano: qui la trovi in inglese.

TutorialTime to complete: 10 minPackage: Novior.MentorAgent

3. Hand off to a specialist agent

Tutorial step 3: add a specialist agent that owns case questions, and let the coordinator hand requests over to it.

In questo tutorial:

  • Turn the case service into a specialist agent with [MentorAgent]
  • Register the specialist so the coordinator can hand requests to it
  • Check that roles and confirmation still apply inside the specialist
  • Count what a handoff costs in model calls
On this page

So far the coordinator, the main agent that answers you, holds every action. In a larger app, this means many tools to choose from on every request. A specialist is an agent for one area, with its own tools and instructions. The coordinator passes it the requests of that area: this is called a handoff. In this step, the case actions move into a specialist.

Before you start: finish 2. Require roles and confirmation.

Create CaseSpecialist.cs. It has the same four methods as CaseService, and the [MentorAgent] attribute on the class. The attribute has the same name as the library: it makes the class a specialist agent.

CaseSpecialist.cs
using System.ComponentModel;
using Novior.MentorAgent.Attributes;

[MentorAgent(
    Name = "CaseSpecialist",
    Description = "Handles support cases: finds, lists, notes and closes them")]
public class CaseSpecialist(CaseStore store)
{
    [MentorAction(
        Description = "Gets a support case: its status, priority and notes",
        ReadOnly = true)]
    public SupportCase? GetCase(
        [Description("The case number, for example C-1")] string caseNumber)
        => store.Find(caseNumber);

    [MentorAction(
        Description = "Lists support cases, optionally by status",
        ReadOnly = true)]
    public IReadOnlyList<SupportCase> ListCases(
        [Description("Open, Waiting for customer or Closed; empty for all")]
        string? status = null)
        => store.List(status);

    [MentorAction(
        Description = "Adds a note to a support case",
        RequiresConfirmation = true,
        RequiredRoles = ["Agents", "Supervisors"])]
    public string AddNote(string caseNumber, string text)
    {
        store.AddNote(caseNumber, text);
        return $"Note added to case {caseNumber}.";
    }

    [MentorAction(
        Description = "Closes a support case with a resolution",
        RequiresConfirmation = true,
        RequiredRoles = ["Supervisors"])]
    public string CloseCase(string caseNumber, string resolution)
    {
        store.Close(caseNumber, resolution);
        return $"Case {caseNumber} closed.";
    }
}
  • Name identifies the specialist. The coordinator's instructions list it, and the handoff uses it.
  • Description tells the coordinator which requests belong here. Write it as you write the description of an action.
  • Without Instructions, MentorAgent writes the specialist's instructions from Name and Description. They include the grounding rule: state only facts that its tools returned. If you write your own Instructions, they replace all of this, including the grounding rule.
  • The methods keep their attributes, so RequiredRoles and RequiresConfirmation move with them.

Delete CaseService.cs. The specialist replaces it, and the same tool names must not exist twice.

A call follows the roles and the confirmation of the method that it runs. But the model calls a tool by its name: when two methods share a name, each agent is offered only one of them, the first one found, and a warning names the copies. Tool filtering and the RequiresApproval and IsReadOnlyAction options also decide by name. When MentorAgent first reads your actions, a warning in the log names every tool name that more than one method carries. Keep each tool name unique across your actions, specialists and teams.

In Program.cs, replace the AddScoped<CaseService>() line with AddScoped<CaseSpecialist>(). The registrations now read:

Program.cs
builder.Services.AddSingleton<CaseStore>();
builder.Services.AddScoped<CaseSpecialist>();   // instead of CaseService

MentorAgent finds the specialist through ScanAssemblies, like your actions, and gets its instance from dependency injection. Without the registration, the specialist has no tools, and a warning in the log names the class.

The coordinator does not call the specialist directly. It calls route_to_specialist, a tool that MentorAgent adds. A small router agent then picks the specialist. The router is one more model call, as What the handoff costs shows.

Handoff needs options.ChatClient, which the Cases app sets. When you pass a prebuilt options.Agent instead, MentorAgent skips the specialist, and a warning in the log names it.

Run the app and sign in as Sam: /dev/sign-in?name=Sam&role=Supervisors.

  1. Type "Which cases are open?". The coordinator has no case tools any more, so it calls route_to_specialist. A router passes the request to CaseSpecialist, which calls list_cases. The specialist's answer goes back to the coordinator, which writes the reply you see.
  2. Type "Close case C-1: the customer can sign in again". The confirmation box appears as before, although the action now runs inside the specialist. Click Confirm.
  3. Sign in as Alex with /dev/sign-in?name=Alex&role=Agents, and ask to close case C-2. The specialist is not offered close_case for Alex, who is not a supervisor, so the case stays open, as in step 2 of the tutorial. The words of the reply vary.

If no specialist takes a request, the coordinator is told so. It then answers with its own tools, or says that it could not handle the request.

If you click Cancel on the confirmation of an action inside the specialist, the coordinator is told that you declined, and to say in one sentence that it did not proceed.

Every agent that works on a request makes its own model calls. In step 2 of the tutorial, closing a case took two calls: the coordinator called close_case, then wrote the reply. Now it takes five:

  1. the coordinator calls route_to_specialist;
  2. the router picks CaseSpecialist;
  3. the specialist calls close_case;
  4. the specialist answers;
  5. the coordinator writes the reply.

All of them run on your ChatClient and count in the token usage. A specialist is worth the extra calls when an area has many actions, or needs instructions of its own. With four actions, as here, one agent answers faster and costs less: the tutorial moves them to show how a handoff works.

In 4. Put agents in a team, two agents review a case together before the specialist closes it.

Level 2: specialists explains how a handoff works in Agent Framework, and Specialists and teams are gated too explains why the checks travel with the method.

  • Turn the case service into a specialist agent with [MentorAgent]
  • Register the specialist so the coordinator can hand requests to it
  • Check that roles and confirmation still apply inside the specialist
  • Count what a handoff costs in model calls
  • Attributes — The parameters of MentorAction, MentorAgent, MentorTeam, TeamMember, MentorPage and MentorSkill, with their defaults and rules.
  • The three agent levels — Actions, specialists and teams: how the three levels of MentorAgent map to Agent Framework, and when to use each one.