MentorAgent Docs
Menu

ConceptPackages: Novior.MentorAgent, Novior.MentorAgent.Declarative

The three agent levels

Actions, specialists and teams: how the three levels of MentorAgent map to Agent Framework, and when to use each one.

View as Markdown
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.

LevelYou writeThe coordinator seesWhat Agent Framework runs
1 · ActionA method with [Description] or [MentorAction]One tool per method, named after itA function tool (AIFunction)
2 · SpecialistA class with [MentorAgent]One route_to_specialist tool for all specialistsA ChatClientAgent per specialist, in a handoff workflow
3 · TeamA class with [MentorTeam] and one [TeamMember] property per memberOne tool per team, named after itA 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.

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.

CaseService.cs
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.

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.

ContractsAgent.cs
[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.

A class marked with [MentorTeam] is a group chat: several agents discuss a request before the coordinator answers.

RefundReviewTeam.cs
[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 returns true, or after MaxIterations turns, 10 by 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:

  1. 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.
  2. When the discussion contains [TEAM:APPROVED] and the team has HandoffTo, the team's result asks the coordinator to carry out the approved actions.
  3. The coordinator's model then decides whether to call route_to_specialist to 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.

  • 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#.

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. EnableToolFiltering sends 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_specialist and 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 MaxIterations turns, plus the members' tool calls. A team is the slowest and most expensive level: keep MaxIterations low, end the discussion with a termination condition, and choose TriggerOn words 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.

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:

Agents/case-status.agent.yaml
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_status

You 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.

  • tools names tools; it does not create them. Each name must match one of your Level 1 actions exactly, such as get_case_status above. 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 RequiredRoles and confirmation apply inside the YAML agent too.
  • instructions are 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.

  • 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.