Questa pagina non è ancora tradotta in italiano: qui la trovi in inglese.
Attributes
The parameters of MentorAction, MentorAgent, MentorTeam, TeamMember, MentorPage and MentorSkill, with their defaults and rules.
On this page
Attributes on your own classes and methods tell MentorAgent what the agent may do. MentorAgent finds them in the assemblies listed in ScanAssemblies. Register every class whose methods become tools in dependency injection (DI). If the container cannot create a class, MentorAgent leaves its methods out and logs a warning.
At a glance
Section titled “At a glance”| Attribute | Applies to | Namespace | What it declares |
|---|---|---|---|
[MentorAction] | Method | Novior.MentorAgent.Attributes | An action the agent can call, shown to the model as a tool |
[MentorAgent] | Class | Novior.MentorAgent.Attributes | A specialist agent the coordinator hands requests to |
[MentorTeam] | Class | Novior.MentorAgent.Attributes | A team of agents that discuss a request in a group chat |
[TeamMember] | Property | global namespace | A member of a team |
[TeamTerminationCondition] | Method | global namespace | The rule that ends a team's discussion |
[MentorPage] | Class | Novior.MentorAgent.Attributes | A page the agent can open |
[MentorSkill] | Class | Novior.MentorAgent.Attributes | A skill whose instructions the model loads when it needs them |
[TeamMember] and [TeamTerminationCondition] are in the global namespace, so they need no using. Which level to use explains when to use actions, specialists or teams.
The API reference has a page for each attribute class, generated from the package, with every property: MentorActionAttribute, MentorAgentAttribute, MentorTeamAttribute, TeamMemberAttribute, TeamTerminationConditionAttribute, MentorPageAttribute and MentorSkillAttribute.
[MentorAction]
Section titled “[MentorAction]”Put it on a public method of a service you register in DI: the method becomes a tool the agent can call. A method with only [Description] from System.ComponentModel is an action too, with no rules: no roles and no confirmation. The default confirmation of RequireConfirmationForWrites covers [MentorAction] methods only. What each declaration does on each channel is in Every declaration, on every channel.
public class CaseService(ICaseStore store)
{
// Only reads: it never asks for confirmation
[MentorAction(
Description = "Gets the status of a support case",
ReadOnly = true)]
public Task<string> GetStatusAsync(string caseNumber)
=> store.GetStatusAsync(caseNumber);
[MentorAction(
Description = "Closes a support case with a resolution",
Category = "Cases",
RequiresConfirmation = true,
RequiredRoles = ["Supervisors", "Admins"],
ProactiveHint = "Suggest checking the case status first",
NavigateTo = "/cases")]
public Task CloseCaseAsync(string caseNumber, string resolution)
=> store.CloseAsync(caseNumber, resolution);
}| Parameter | Type | Default | Effect |
|---|---|---|---|
Description | string | Required | The action's description: what the model reads to decide when to call it |
Category | string? | null | Used only on the Agent-to-Agent (A2A) agent card, where an action is listed as an A2A skill: Category becomes that skill's tag. MentorAgent does not group actions by it |
RequiresConfirmation | bool | false | Shows the confirmation box before the method runs. Write true on an action that changes data or cannot be undone, and false on a write that must run without asking. Left out, the action follows RequireConfirmationForWrites: it asks unless it is ReadOnly. Leaving it out is not the same as writing false. Tool filtering reads this parameter. An action without confirmation can reach the model on any message, and an action with confirmation never travels alone |
RequiresConfirmationDeclared | bool | false | Read-only: you do not write it. The attribute sets it to true when you write RequiresConfirmation, as true or as false. Read it to tell an action that declares nothing from one that declares RequiresConfirmation = false: RequiresConfirmation reads false for both. It says only what the attribute declares: whether the action asks also depends on ReadOnly, IsReadOnlyAction and RequiresApproval |
ReadOnly | bool | false | Declares that the action only reads: it changes no data. MentorAgent trusts your declaration and does not check it. The Model Context Protocol (MCP) server publishes the action with readOnlyHint: some MCP clients skip their own confirmation for such a tool, so declare it only on an action that only reads. RequireLookupForRecordQuestions can require the agent to call it. Ignored when the action also needs confirmation. For a method that has only [Description], use IsReadOnlyAction |
RequiredRoles | string[] | empty | The roles allowed to run the action: any one of them is enough. The user must be signed in. On the hub, on /mentor/chat and over A2A, the user is the caller of the request. In a Blazor circuit, it is the user that the AuthenticationStateProvider of the host reports. Empty means no role check, for anonymous users too |
ProactiveHint | string? | null | Guidance for the model, appended to the tool's description as Guidance: <hint> for the coordinator, the specialists and the team members. It is not added on the MCP server, and tool filtering ignores it |
NavigateTo | string? | null | A URL the agent opens after the method succeeds |
- Tool names. The tool name comes from the method name: PascalCase becomes snake case (lowercase words joined by underscores), and a trailing
Asyncis dropped first.CloseCaseAsyncisclose_case. Options that name tools, such asRequiresApproval, use this name. - Roles on a server. How MentorAgent finds the user on each channel is explained in Two checks, in this order. A claim that only your own
AuthenticationStateProvideradds is not seen on the hub, on/mentor/chator over A2A: add it with anIClaimsTransformation. - Which methods become tools. MentorAgent finds the public instance methods declared on the class itself that have
[MentorAction]or a[Description]with a text. It ignores inherited methods, static methods and an empty[Description]. The same rules apply to a specialist and to theToolsof a team member. - Every level. The same checks apply when a specialist or a team member calls the method. See Roles, confirmation and governance.
[MentorAgent]
Section titled “[MentorAgent]”Put it on a class to make it a specialist: its [Description] and [MentorAction] methods become the specialist's tools, and the coordinator hands it the requests it can handle. Register the class in DI. Specialists need ChatClient. Implementing IMentorAgent is optional: MentorAgent never reads it.
[MentorAgent(
Name = "CaseAgent",
Description = "Handles support cases: status, history and closing",
HandoffTo = ["BillingAgent"])]
public class CaseAgent(ICaseStore store) : IMentorAgent
{
[Description("Gets the status of a support case")]
public Task<string> GetCaseStatusAsync(string caseNumber)
=> store.GetStatusAsync(caseNumber);
}| Parameter | Type | Default | Effect |
|---|---|---|---|
Name | string | Required | The agent's name: the key in the handoff workflow and in the coordinator's prompt |
Description | string | Required | What the agent can do. The coordinator reads it to decide when to hand off |
HandoffTo | string[] | empty | The names of other [MentorAgent] agents it can hand off to, matched regardless of case. A name that matches no agent logs a warning, and that handoff is skipped |
Instructions | string? | null | The agent's system prompt, used exactly as written. With null, MentorAgent writes one from Name and Description, including the grounding rule: state only facts a tool returned. A custom prompt replaces that rule, so include your own |
[MentorTeam]
Section titled “[MentorTeam]”Put it on a class to define a group chat: the members discuss the request, then the coordinator answers with the result. The team is a tool of the coordinator, named after the team: EscalationReview becomes escalation_review. Teams need ChatClient. Implementing IMentorTeam is optional.
[MentorTeam(
Name = "EscalationReview",
Description = "Reviews a request to escalate a support case",
TriggerOn = ["escalation review"],
MaxIterations = 6,
HandoffTo = ["CaseAgent"])]
public class EscalationReviewTeam : IMentorTeam
{
[TeamMember(
Role = "Analyst",
Instructions = "Summarize the case history and propose a decision.",
Tools = [typeof(CaseReports)])]
public object? Analyst { get; set; }
[TeamMember(
Role = "Approver",
Instructions = "Approve or reject the 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));
}| Parameter | Type | Default | Effect |
|---|---|---|---|
Name | string | Required | The team's name, and the name of its tool |
Description | string | Required | What the team is for. The coordinator reads it to decide when to use the team |
TriggerOn | string[] | empty | Keywords added to the coordinator's prompt as an "always delegate to this team" directive. The model still decides: choose specific words, because a broad one sends ordinary requests through the group chat |
MaxIterations | int | 10 | The maximum number of times the members speak before the discussion is stopped |
HandoffTo | string[] | empty | Specialists that carry out the plan once the team approves it. The team's tool description names them. When the final message carries [TEAM:APPROVED] and no member wrote [TEAM:REJECTED], the result asks the coordinator to carry out the approved actions. The coordinator's model then decides whether to do it |
[TeamMember]
Section titled “[TeamMember]”Put it on a property of a [MentorTeam] class, one property per member, as in the example above.
| Parameter | Type | Default | Effect |
|---|---|---|---|
Role | string | Required | The member's role in the team, for example "Analyst" |
Instructions | string | Required | The member's instructions. MentorAgent appends the language, the role, the grounding rule and the decision protocol described below |
Tools | Type[] | empty | Classes whose tools the member can call during the discussion. They are the toolbox of this member only: see Level 3: teams. Keep the tools read-only and declare them with ReadOnly = true. Register the classes in DI. A class that the container cannot resolve is skipped with a warning, and the member runs without it |
Writes belong to the specialists named in the team's HandoffTo, which keep their own role and confirmation checks.
[TeamTerminationCondition]
Section titled “[TeamTerminationCondition]”Put it on one method of the team class, with the signature bool ShouldTerminate(string lastMessage, string lastSpeaker): the discussion ends when it returns true, and your method alone decides. Without it, the members speak in turn, and the discussion stops at the first member message that carries [TEAM:APPROVED] or [TEAM:REJECTED], or after MaxIterations. The attribute has no parameters.
MentorAgent instructs the members to mark their decision with [TEAM:APPROVED] or [TEAM:REJECTED], and removes both before the text reaches the user. Your condition can end the discussion on any text. The result asks the coordinator to hand off only when the final message carries [TEAM:APPROVED] and no member wrote [TEAM:REJECTED]. A discussion that ends without a verdict in its final message is not approved.
[MentorPage]
Section titled “[MentorPage]”Put it on a routable Blazor page to let the agent open it with navigate_to. The agent knows the page from the start, without visiting it first. In a .razor file, write it with @attribute. With a front end of your own, such as React, put it on an empty class, one per page, in an assembly that is listed in ScanAssemblies. Your client then receives the URL, in the NavigationRequested event or in the navigation frame over Server-Sent Events (SSE), and routes to it.
@page "/cases"
@using Novior.MentorAgent.Attributes
@attribute [MentorPage(Url = "/cases", Name = "Cases",
Description = "The list of support cases, with filters by status")]| Parameter | Type | Default | Effect |
|---|---|---|---|
Url | string | Required | The page's URL, for example "/cases" |
Name | string | Required | The page's name in the system prompt |
Description | string? | null | What the page offers, shown to the model. The classifiers of RefuseOutOfScope and RequireActionForChangeRequests see it too |
HasUIActions | bool | false | After opening the page, navigate_to waits until the new page calls PageContext.SignalReady(). A signal from the page being left does not count. It waits only when the turn runs inside the Blazor app that shows the page, as on Blazor Server or in a MAUI Blazor Hybrid app. To the page already shown (the same path, whatever the query), navigate_to does not wait. Turns that run on the hub, over SSE or over A2A never wait |
ReadyTimeout | int | 2000 | How long navigate_to waits for SignalReady(), in milliseconds. A component with two routes, such as /cases and /cases/{id}, is not initialized again when navigate_to goes from one route to the other: the wait reaches ReadyTimeout and logs a warning. In such a component, call SignalReady() at the end of OnParametersSet too |
[MentorSkill]
Section titled “[MentorSkill]”Put it on a class to register a skill: instructions the model loads with load_skill only when it needs them, while the prompt carries only the name and the description. The class's [Description] and [MentorAction] methods become tools, so register it in DI when it has any. Skills need EnableSkills.
[MentorSkill(
Name = "case-escalation",
Description = "Rules for escalating a support case to a supervisor",
InstructionsFile = "Skills/case-escalation/SKILL.md")]
public class CaseEscalationSkill
{
}| Parameter | Type | Default | Effect |
|---|---|---|---|
Name | string | Required | The skill's unique name in kebab-case: the key for load_skill |
Description | string | Required | One sentence shown in the skill catalog, the only text the model sees before it loads the skill |
InstructionsFile | string? | null | A Markdown file with the instructions, absolute or relative to the content root. With null, MentorAgent reads SKILL.md in the folder named after the skill, inside SkillsFolder |
Instructions | string? | null | The instructions inline, in Markdown. They win over InstructionsFile |
When no file is found, the instructions are only the skill's name and description. If you gave a path that does not exist, MentorAgent also logs a warning.
Vedi anche
Sezione “Vedi anche”- Configuration options — Every MentorOptions property with its type, default and effect, grouped by area, plus the options of the WebAssembly client and the YAML loader.
- The three agent levels — Actions, specialists and teams: how the three levels of MentorAgent map to Agent Framework, and when to use each one.
- Ruoli, conferma e governance — Ruoli e approvazione human-in-the-loop per i tool di un agente AI in .NET: controlli nel codice, non nel prompt, su ogni canale, e cosa resta a te.
