The three agent levels
Actions, specialists and teams: how the three levels of MentorAgent map to Agent Framework, and when to use each one.
On this page
Every request reaches the coordinator first: the main agent that answers the user, built on Microsoft Agent Framework (external site). MentorAgent gives it three ways to get work done: actions, specialists and teams. They go from the simplest and cheapest to the slowest and most expensive, and they differ in what the coordinator calls.
| Level | You write | The coordinator sees | What Agent Framework runs |
|---|---|---|---|
| 1 · Action | A method with [Description] or [MentorAction] | One tool per method, named after it | A function tool (AIFunction) |
| 2 · Specialist | A class with [MentorAgent] | One route_to_specialist tool for all specialists | A ChatClientAgent per specialist, in a handoff workflow |
| 3 · Team | A class with [MentorTeam] and one [TeamMember] property per member | One tool per team, named after it | A ChatClientAgent per member, in a group chat workflow |
The coordinator is a ChatClientAgent built from your ChatClient, or the AIAgent you pass in Agent. Specialists and teams need a ChatClient: with your own AIAgent, the coordinator gets your actions but no specialists or teams. In Agent Framework, a handoff workflow lets one agent pass a request to another agent, and a group chat workflow lets several agents speak in turn on the same request.
Level 1: actions
Section titled “Level 1: actions”A public method of one of your services becomes a tool the coordinator can call. [MentorAction] sets roles, confirmation and navigation. A method that only reads declares ReadOnly = true. A method with [Description] alone is an action too, but it has no rules.
public class CaseService(ICaseStore store)
{
[MentorAction(
Description = "Gets the status of a support case",
ReadOnly = true)]
public Task<string> GetCaseStatusAsync(string caseNumber)
=> store.GetStatusAsync(caseNumber);
}The tool is named after the method, in snake case and without a trailing Async: GetCaseStatusAsync becomes get_case_status. The coordinator calls it like any Agent Framework function tool, and reads the result in the same turn, that is, while it answers the same message. The same list also holds the tools of the Model Context Protocol (MCP) servers you connect, in McpServers, and the methods of skills.
MentorAgent finds the class in the assemblies listed in ScanAssemblies. It gets the instance from dependency injection, so also register the class in the container, for example with AddScoped. Roles, confirmation and governance explains the checks that run before each call.
Level 2: specialists
Section titled “Level 2: specialists”A class marked with [MentorAgent] is a specialist: an agent with its own instructions and its own tools, which are the [Description] and [MentorAction] methods of the class.
[MentorAgent(
Name = "ContractsAgent",
Description = "Handles support contracts: coverage, renewal dates and refunds")]
public class ContractsAgent(IContractStore contracts)
{
[MentorAction(
Description = "Gets the support contract of a customer",
ReadOnly = true)]
public Task<string> GetContractAsync(string customerId)
=> contracts.GetAsync(customerId);
[MentorAction(
Description = "Issues a refund on a support contract",
RequiresConfirmation = true,
RequiredRoles = ["Supervisors"])]
public Task IssueRefundAsync(string customerId, decimal amount)
=> contracts.RefundAsync(customerId, amount);
}MentorAgent builds a ChatClientAgent from the class. Its prompt comes from Name and Description, unless you write your own in Instructions. All the specialists are part of one Agent Framework handoff workflow. The workflow starts with a small router agent: it has none of your tools, and only chooses the specialist.
The coordinator does not see the specialist's methods. It sees one tool, route_to_specialist, whose description lists every specialist with its description. When the coordinator calls it, the router hands the request to one specialist, which runs its own tool loop. What the specialist says comes back as the tool's result, and the coordinator writes the reply.
If no specialist takes the request, the result starts with NOT DELEGATED, and the coordinator answers with its own tools or says it cannot.
If the user declines an action inside a specialist, the result starts with DECLINED BY THE USER. The coordinator is then told to say in one sentence that it did not proceed, to give no reason and not to try again.
A specialist can pass the request on to the specialists named in its HandoffTo. Two other kinds of agent join the same workflow: the specialists defined in YAML, and the remote agents in RemoteAgents, which run on other hosts and answer over the Agent-to-Agent (A2A) protocol.
The specialist's tools keep their checks. In the example, issue_refund still requires the Supervisors role and a confirmation, even though the coordinator never calls it directly: see Specialists and teams are gated too.
Level 3: teams
Section titled “Level 3: teams”A class marked with [MentorTeam] is a group chat: several agents discuss a request before the coordinator answers.
[MentorTeam(
Name = "RefundReview",
Description = "Reviews a refund request on a support contract before anything is paid",
MaxIterations = 4,
HandoffTo = ["ContractsAgent"])]
public class RefundReviewTeam
{
[TeamMember(
Role = "Analyst",
Instructions = "Check the contract history and propose a decision with reasons.",
Tools = [typeof(ContractReports)])]
public object? Analyst { get; set; }
[TeamMember(
Role = "Approver",
Instructions = "Approve or reject the analyst's proposal, with reasons.")]
public object? Approver { get; set; }
[TeamTerminationCondition]
public bool ShouldTerminate(string lastMessage, string lastSpeaker)
=> lastSpeaker == "Approver"
&& (lastMessage.Contains("[TEAM:APPROVED]", StringComparison.Ordinal)
|| lastMessage.Contains("[TEAM:REJECTED]", StringComparison.Ordinal));
}Each [TeamMember] property becomes a ChatClientAgent with its role and its instructions. MentorAgent puts the members in an Agent Framework group chat workflow, where they speak in turn:
- Tools. A member can call the tools of the classes listed in its
Tools. Register those classes in dependency injection. A listed class is the toolbox of that member only. Its methods are not actions of the coordinator, and they are not published over MCP or on the A2A agent card. A class marked with[MentorSkill]is the exception: it keeps its tools for the coordinator. The tools follow the rules of actions: public instance methods of the class itself, marked with[MentorAction]or a non-empty[Description]. - End of the discussion. The discussion stops when the
[TeamTerminationCondition]method returnstrue, or afterMaxIterationsturns,10by default. Each time the coordinator calls the team, the count starts again.
The coordinator sees the team as one tool named after it: RefundReview becomes refund_review, and the discussion is the tool's result. With TriggerOn, the coordinator's prompt tells it to always use the team for requests that mention those words. The model still decides whether to do it.
A team is not part of the handoff workflow. Its HandoffTo does not hand anything off by itself:
- MentorAgent tells the members to mark their final decision with
[TEAM:APPROVED]or[TEAM:REJECTED]. It removes both markers before the text reaches the user. - When the discussion contains
[TEAM:APPROVED]and the team hasHandoffTo, the team's result asks the coordinator to carry out the approved actions. - The coordinator's model then decides whether to call
route_to_specialistto do it.
Keep the members' tools read-only and declare them with ReadOnly = true. Leave the writes to specialists: their tools keep their role and confirmation checks.
Which level to use
Section titled “Which level to use”- Start with actions. Most applications need nothing else: the coordinator reads the request, calls the actions it needs and answers. It is the fastest and cheapest level.
- Add a specialist when an area has its own set of actions and its own rules for using them, and the coordinator's list of tools grows long. The coordinator then carries one tool for all the specialists, and each specialist works with a short list and focused instructions.
- Add a team when a request needs a second opinion before anything happens: an analysis and an approval, a proposal and a review. Leave the writes to the specialists named in
HandoffTo. - Define a specialist in YAML when its instructions and its description change more often than its tools. The tools stay in C#.
Cost and latency
Section titled “Cost and latency”Each level adds model calls to the requests that use it.
- Actions. No extra agent. The name, description and parameters of every tool travel with each call of the coordinator, so a long list makes every call larger.
EnableToolFilteringsends only the actions relevant to the message. When none is relevant, it still sends the three best-ranked actions that need no confirmation and no role, which adds about 80 input tokens.route_to_specialistand the team tools are always sent, also on the turns where an opt-in option requires the first call to be an action: a request meant for a specialist or a team can still be handed over. - Specialists. A delegated request adds at least one call to the router and one to the specialist, more if the specialist uses its tools, before the coordinator writes the reply. The specialist's tools are sent only to the specialist, so they do not weigh on the requests the coordinator handles alone.
- Teams. Each member's turn is at least one model call, up to
MaxIterationsturns, plus the members' tool calls. A team is the slowest and most expensive level: keepMaxIterationslow, end the discussion with a termination condition, and chooseTriggerOnwords that ordinary requests do not use.
The router, the specialists and the team members run on your ChatClient and are counted by MentorAgent's token meter, so a delegated turn appears in full on the cost dashboard.
Specialists in YAML
Section titled “Specialists in YAML”The optional Novior.MentorAgent.Declarative package lets you declare a Level 2 specialist in a file instead of a class. The file sets the name, the description the router uses to choose the agent, the instructions, the model options and the tools the agent may use:
kind: Prompt
name: CaseStatusAgent
description: Answers questions about the status of support cases
instructions: |
You answer questions about support cases only. Use the tools to look up real cases.
State only facts a tool returned; if nothing provides what is asked, say so.
model:
options:
temperature: 0.2
tools:
- kind: function
name: get_case_statusYou register the folder with AddMentorAgentDeclarative, and the agent joins the same handoff workflow as the C# specialists: route_to_specialist reaches both in the same way. The loader's options are in MentorDeclarativeOptions.
toolsnames tools; it does not create them. Each name must match one of your Level 1 actions exactly, such asget_case_statusabove. The methods of a C# specialist, MCP tools, team tools and the built-in tools cannot be named. A name that matches nothing raises no error: the model sees a tool with no code behind it.- Every entry needs
kind: function. Entries of the other kinds (webSearch,codeInterpreter,fileSearch,mcp) are removed with a warning: those capabilities belong to the host's configuration. - The checks hold. The named tools arrive already gated, so
RequiredRolesand confirmation apply inside the YAML agent too. instructionsare used exactly as written. MentorAgent adds nothing, not even the grounding rule, which tells the model to state only facts a tool returned. Write your own rule, as in the example. Use|for text on several lines: folded scalars (>) are not supported.- The agent returns to the router when it is done. It cannot hand off to other agents.
- Definitions are read for each new session, so an edited file reaches new sessions without a restart. Like the other specialists, YAML agents need a
ChatClient, and are skipped with a warning without one.
A definition file writes the instructions and names the tools, so treat it as code. Load it only from a folder that ships with the application, never from one users can write to, and review it like a .cs file. A good rule is behavior in C#, phrasing in YAML. To load definitions from a database or another store, implement IMentorAgentSource from the Novior.MentorAgent package: AddMentorAgentDeclarative is one implementation of it.
See also
Section titled “See also”- Attributes — The parameters of MentorAction, MentorAgent, MentorTeam, TeamMember, MentorPage and MentorSkill, with their defaults and rules.
- Roles, confirmation and governance — How MentorAgent checks roles and asks for confirmation before an action runs, on every channel, and what it leaves to your code.
- 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.
- 4 · Put agents in a team — Tutorial step 4: let two agents review a case in a group chat before anything changes, while roles and confirmation still apply.
