MentorAgent Docs
Menu

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

ReferencePackages: Novior.MentorAgent, Novior.MentorAgent.Abstractions, Novior.MentorAgent.Server, Novior.MentorAgent.Blazor, Novior.MentorAgent.Declarative

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.

On this page

MentorOptions holds every server-side setting of MentorAgent. You set it once, in the callback that you pass to AddMentorAgent. Each option below shows its type, its default and its effect. An option marked Required has no default. The last sections cover the classes that these options use, the options of the Blazor WebAssembly client and the options of the YAML loader.

Some options mention the coordinator and its specialists. The coordinator is the main agent that answers the user: see The three agent levels. The glossary defines the other terms of this page, such as classifier and hosted tool.

This page groups the options by area and explains how they work together. The API reference lists the same properties as the package declares them, generated from it: see MentorOptions.

Program.cs
builder.Services.AddScoped<CaseService>();
builder.Services.AddMentorAgent(options =>
{
    // Required: without them the options fail validation
    options.AppName = "Cases";
    options.ScanAssemblies = [typeof(CaseService).Assembly];
    options.ChatClient = chatClient; // or options.Agent, never both

    // Everything else has a default: set only what you change
    options.AppDescription = "Support case management for the service desk";
    options.RateLimitPerUser = 20;
});
ClassPackageWhere you set it
MentorOptionsNovior.MentorAgentAddMentorAgent(options => …)
MentorMcpServer, MentorHostedMcpServer, MentorRemoteAgent, AgentCardInfoNovior.MentorAgentThe McpServers, HostedMcpServers, RemoteAgents and AgentCard options
ModelPriceNovior.MentorAgent.AbstractionsThe values of ModelPricing
MentorConfirmationContext, MentorConfirmationArgumentNovior.MentorAgentYou do not set them: the ConfirmationMessage function receives them
MentorAgentBlazorOptionsNovior.MentorAgent.BlazorAddMentorAgentBlazor(options => …), in a WebAssembly client
AgentDisplayInfoNovior.MentorAgent.AbstractionsThe values of MentorAgentBlazorOptions.RemoteAgentDisplays
MentorDeclarativeOptionsNovior.MentorAgent.DeclarativeAddMentorAgentDeclarative(o => …)

Novior.MentorAgent.Server has no options class. The SignalR hub, the Server-Sent Events (SSE) endpoint, the Model Context Protocol (MCP) server and the Agent-to-Agent (A2A) agent all read MentorOptions. The paths of the hub and of the SSE endpoint are parameters of MapMentorAgentServer.

Novior.MentorAgent.Server also registers the MCP server and the A2A agent. AddMentorAgentServer() does it when their options are on. A host without the hub, such as a Blazor Server app, calls AddMentorAgentMcp() or AddMentorAgentA2A() after AddMentorAgent().

The widget reads a copy of its settings as MentorWidgetOptions, which MentorAgent fills from MentorOptions or from MentorAgentBlazorOptions. Set them there, not on MentorWidgetOptions.

MentorAgent checks the options when the host starts, not when you call AddMentorAgent. In an ASP.NET Core app or in a worker service, an invalid configuration stops the app with the reason, before the first request. An app without such a host, such as a .NET MAUI app, has no start to check. In that app, the error appears the first time MentorAgent reads the options. The check throws InvalidOperationException when:

  • AppName is empty or blank;
  • ScanAssemblies is empty;
  • neither ChatClient nor Agent is set, or both are;
  • both ChatHistoryProvider and ChatHistoryProviderFactory are set.
  • The callback runs twice. AddMentorAgent runs your callback once on a copy of the options, to decide what to register, and once more when it builds the real options. Keep the callback free of side effects that must happen once.
  • Some options come from the copy. MentorAgent reads EnableObservability, ObservabilitySourceName, McpServerEnabled and A2AServerEnabled at registration. It also reads AppName at registration, as the name of the MCP server. Set these options in the callback: a Configure<MentorOptions> registered after AddMentorAgent does not reach them. MentorAgent reads every other option from the real options.
  • A callback that throws on the copy leaves those options at their defaults. A warning with the exception is logged when the host starts.

string · Required

The name of your application. MentorAgent puts it in the system prompt, in the topic that the safety check and the off-topic check use to judge messages, and in the A2A agent card.

string · Default: ""

One or two sentences on what your application does. The model reads it as context. The input safety check also uses it to judge messages: without a description, the check must guess what your application is about.

MentorLanguage · Default: English

The language of the replies and of the widget's texts: English, Italian, French, German, Spanish, Portuguese, Dutch, Polish, Japanese or Chinese. The confirmation box shows the Description of the action and the names of its parameters. MentorAgent does not translate them, so write them in your users' language, or write the box's text yourself with ConfirmationMessage. The instruction that tells the model how to cite the retrieval-augmented generation (RAG) documents is in this language too. So are the instructions that MentorAgent writes for the model, such as its example sentences and the instructions in tool results, so that the model does not copy English sentences into its replies.

MentorAgent reads the option again on every turn: a change made while the application runs applies from the next turn.

A language per page. On a site in more than one language, give the widget the language of the page:

MainLayout.razor
<ChatWidget Language="@_language" />

@code {
    // From the route (/it/…) or from a language switch in your layout
    [Parameter] public MentorLanguage? Lang { get; set; }

    private MentorLanguage _language => Lang ?? MentorLanguage.English;
}

The widget then uses that language for its own texts, its voice and its onboarding tour, and sends it with every turn: the assistant answers in it. A front end of your own sends it with the hub method SendUserMessage, or as language on /mentor/chat: the name ("Italian") or the code ("it") of a language. A2A tasks and MCP calls come from no page, so they stay on this option. The texts that you set yourself, such as WelcomeMessage and AiDisclosureMessage, are shown as you wrote them: on a site in more than one language, leave them null, and the widget shows its own translated texts.

MentorshipLevel · Default: Standard

How much the agent explains and suggests:

  • Minimal: it carries out requests without explanations or suggestions. For expert users.
  • Standard: balanced answers, with suggestions when they are relevant.
  • Proactive: it explains what it did and why, warns of risks and says in one sentence what it is about to do. It does not close its answers with suggested next steps: the user asks for what comes next. To bring them back, turn on ProactiveNextSteps.

bool · Default: false

With MentorshipLevel.Proactive, closes each answer with suggested next steps. The prompt tells the model to offer a next step only when the application can carry it out, with a tool of the current request, a page, a skill, a specialist or a team. The model does not always follow this rule: on a sample application, 4 of 15 closing offers suggested something the application does not have. That is why the option is off by default: see BUG-074. The other levels ignore it.

IChatClient? · Default: null

The model, as a Microsoft.Extensions.AI IChatClient: Azure OpenAI, OpenAI, Ollama or any provider that has one. This is the recommended path, because specialists, teams, remote agents, hosted tools, tool filtering and model routing need it. Set this or Agent, not both.

MentorAgent does not include the SDK of any model provider. Add the package that your provider needs, for example Azure.AI.OpenAI or Microsoft.Extensions.AI.OpenAI. See Choose a path in Model providers.

AIAgent? · Default: null

A ready-made Agent Framework AIAgent. Use it for providers that do not expose an IChatClient, such as Foundry's AIProjectClient. For Foundry, add its package yourself. The features that need ChatClient are not available with Agent: see the list under ChatClient. Set this or ChatClient, not both. See Foundry agents (Path B).

IEmbeddingGenerator<string, Embedding<float>>? · Default: null

An embedding model, which turns a text into a list of numbers that represents its meaning, so that MentorAgent can compare texts. EnableToolFiltering, MemoryRelevanceFiltering and the Semantic RoutingStrategy need it. It does not turn them on: each has its own option. If you turn one on without an embedding model, MentorAgent logs a warning and the feature stays off.

Assembly[] · Required

The assemblies MentorAgent scans for actions, agents, teams, pages and skills. Usually [typeof(Program).Assembly]. See Attributes.

MentorTheme · Default: Default

The widget's theme:

  • Default: dark translucent glass with a blue accent. On a light page it shows as dark grey, with the page behind it: there, use Minimal.
  • Dark: dark and nearly opaque, for applications that are already dark.
  • Minimal: light, with a white panel, thin borders and a soft shadow. The theme for a light page.
  • Auto: follows the visitor's system setting (prefers-color-scheme): Minimal when it asks for light, or states no preference, and Default when it asks for dark. It follows the system, not your page.
  • Custom: injects no theme overrides and renders like Default: your stylesheet decides what changes.

The themes are stylesheets of the package, so they work under a Content-Security-Policy without style-src 'unsafe-inline', such as default-src 'self', and so does PrimaryColor.

ChatPosition · Default: BottomRight

Where the widget sits: BottomRight, BottomLeft, TopRight, TopLeft, or a full-height side panel with SideRight and SideLeft.

string? · Default: null

An accent color in hex, for example #2563EB. null keeps the theme's color.

string · Default: "Mentor AI"

The name shown in the widget header.

string? · Default: null

The URL of the avatar shown in the widget. null uses the built-in one.

string? · Default: null

The message shown while the chat is empty. The widget renders it as HTML, so never build it from user input. null uses the built-in message.

bool · Default: true

Shows a notice under the widget's header that tells people they are talking to an AI assistant. It stays for the whole conversation: it does not scroll with the messages, and it cannot be closed. The text is AiDisclosureMessage.

Turning the notice off does not remove the machine-readable mark that every reply of the model carries: data-ai-generated="true" in the widget, aiGenerated on the hub, on /mentor/chat and on A2A. The notice and the mark are two tools for article 50 of the EU AI Act. Whether the article applies to your application, and which words to show, is yours to decide.

string? · Default: null

The text of the notice that ShowAiDisclosure shows, as you write it, in every language. null shows the default text in the language of Language: in English, "You are talking to an AI assistant: its replies are generated automatically."

string? · Default: null

The placeholder of the input box. With null, the widget uses the text in the language of Language: "Write a message..." in English.

bool · Default: true

Shows starter suggestion chips in the widget's welcome panel. No hub event or SSE frame carries suggestions, so it has no effect on a React, Angular or Vue client, or on any other client of the hub.

bool · Default: true

Shows a feedback line while one of your tools runs. Over the SignalR hub, MentorAgent sends the events ActionExecuting and ActionCompleted. Hosted tools have their own switch, ShowHostedToolActivity.

bool · Default: false

Shows a short guided tour the first time a user opens the widget. MentorAgent generates it from your [MentorPage] pages and the agent's tools. Novior.MentorAgent.Server answers GET /mentor/tour even when this option is false: a client of the server decides for itself whether to show the tour.

bool · Default: true

Renders a MentorCard returned by a tool as a card. MentorAgent reads a result for cards only when the method is declared to return them: MentorCard or IEnumerable<MentorCard>, directly or through a Task. A card returned as object, or as a type of your own, is shown as text. A MentorCard that an OnToolResult hook returns is a card too. The result of an MCP tool never is. With false the data reaches the model as text, and the model describes it.

bool · Default: true

Asks the coordinator to format answers as Markdown, with tables and lists. The widget always renders Markdown safely. With false, the coordinator is asked for short answers in plain text.

bool · Default: false

Shows a microphone button that uses the browser's Speech Recognition API. The browser does the recognition: in Chrome, for example, it sends the audio to its vendor's service, and only the transcript reaches MentorAgent. Another client does its own capture and sends the transcript as an ordinary message.

bool · Default: false

Reads replies aloud with the browser's Speech Synthesis API. Synthesis always happens on the client.

bool · Default: true

Speaks each sentence as it streams, instead of reading the finished answer back.

bool · Default: true

Stops the spoken answer when the user acts: presses the microphone, sends a message, presses Stop or starts a new conversation.

bool · Default: false

Lets the user talk without touching the widget: the message is sent when the user stops speaking, and the microphone opens again when the spoken answer ends. Ignored unless both EnableVoiceInput and EnableVoiceOutput are on.

double · Default: 1.0

The speech rate, passed to SpeechSynthesisUtterance.rate. The useful range is about 0.5 to 2.0.

bool · Default: false

Lets users send images with a message. In the widget, they can upload, paste or drag and drop an image, or give its URL. In Novior.MentorAgent.Server, images arrive as attachments of SendMessage and POST /mentor/chat. The chat model must support images.

int · Default: 4194304

The maximum size of one image, 4 MB. The server checks it.

int · Default: 4

The maximum number of images in one message. The server checks it.

IReadOnlyList<string> · Default: ["image/png", "image/jpeg", "image/gif", "image/webp"]

The only MIME types that MentorAgent accepts. MentorAgent drops any other image and logs a warning.

Before an action runs, MentorAgent can ask the user to confirm it, in the confirmation box. Agent Framework calls this step an approval: on this page, confirmation and approval mean the same thing. See Roles, confirmation and governance.

bool · Default: true

Turns confirmation on or off for the whole application. With true, the default, a tool asks for confirmation in four cases:

Do not confuse this option, RequireConfirmation, with the attribute parameter RequiresConfirmation.

With false:

  • no action or tool asks for confirmation;
  • RequiredRoles still refuses a user without the role;
  • the only approvals that still appear are those for the MCP servers that the model provider calls directly (HostedMcpServers);
  • tool filtering still reads what you declared: see EnableToolFiltering;
  • RequireActionForChangeRequests does not force any action, because no action shows a confirmation box.

See Roles, confirmation and governance.

bool · Default: true

Makes a [MentorAction] ask for confirmation when it declares neither ReadOnly = true nor RequiresConfirmation. MentorAgent treats such an action as one that changes data. To stop an action from asking, declare it in one of these ways:

  • ReadOnly = true on an action that only reads, or IsReadOnlyAction by tool name;
  • RequiresConfirmation = false on a write that must run without asking.

What each declaration does on each channel is in Every declaration, on every channel.

CaseReview.cs
public class CaseReview(ICaseReviewStore store)
{
    // Declares nothing: a write for MentorAgent, so it asks for confirmation
    [MentorAction(Description = "Reopens a closed support case")]
    public Task ReopenCaseAsync(string caseNumber, string reason)
        => store.ReopenAsync(caseNumber, reason);

    // Only reads: it never asks
    [MentorAction(
        Description = "Lists the notes of a support case",
        ReadOnly = true)]
    public Task<string> ListCaseNotesAsync(string caseNumber)
        => store.ListNotesAsync(caseNumber);

    // A write that is easy to undo and must run without asking
    [MentorAction(
        Description = "Flags a support case for review",
        RequiresConfirmation = false)]
    public Task FlagCaseAsync(string caseNumber)
        => store.FlagAsync(caseNumber);
}

An action that asks for confirmation is not offered to MCP clients and is not on the A2A agent card, like every action that asks, because nobody there can answer. When a message matches no action, tool filtering does not send it either.

The option covers [MentorAction] methods only. A method with only [Description] asks for nothing and stays on /mcp: mark it [MentorAction] to have the default.

The first time MentorAgent reads your actions, a warning lists the writes that run without confirmation, in two lists: the [MentorAction] methods that opted out, and the methods with only [Description] that are not declared read-only. MentorAgent reads your actions:

  • at startup, when the host maps the A2A agent or the MCP endpoint, or turns on WarmUpAtStartup;
  • at the first request for the onboarding tour (GET /mentor/tour);
  • otherwise, at the first message.

With false, an action asks only when it declares RequiresConfirmation = true. Setting RequireConfirmation to false turns off every confirmation of your actions, including this default.

MentorHitlMode · Default: Blocking

How the server handles a confirmation. The name comes from human in the loop (HITL): a person confirms an action before it runs. Agent Framework calls a confirmation an approval.

  • Blocking: MentorAgent's own flow. The run waits for the user's answer.
  • Native: Agent Framework's approval protocol, for interoperability. After the answer, the turn resumes in a new run.

The confirmation box, the client protocol and the model calls are the same in both modes. In both modes, when the user declines, the model is told three things: the user declined, nothing was checked or changed, and it must not give another reason.

With Native, you can press Stop while a confirmation box waits. The conversation keeps working: the next message answers the open request as not run. A snapshot saved in that state carries the open requests and their answers, and the first turn after the restore answers them. For the two modes, see Blocking and Native modes.

Func<string, bool>? · Default: null

A predicate on the tool name: return true to ask the user to confirm before that tool runs. It works in addition to RequiresConfirmation. Use it for tools that have no attribute you can set, such as MCP tools and skills. The name is the tool name, not the method name: CloseCaseAsync becomes close_case.

MentorAgent asks the predicate more than once: when it builds the coordinator, on every call to a tool in Blocking mode, and on every turn in which tool filtering matches a tool. Keep it a cheap, pure function of the tool name. If it needs a file or a database, read them once, outside the predicate.

Func<string, bool>? · Default: null

A predicate on the tool name: return true to declare that the action only reads. It does the same as ReadOnly on [MentorAction]. Use it for methods that have only [Description], or to declare many actions in one place. MentorAgent trusts your declaration and does not check the action.

  • The MCP server publishes the action with readOnlyHint.
  • RequireLookupForRecordQuestions can require the agent to call it.
  • A tool that needs confirmation is never read-only, whatever the predicate returns.
  • It does not apply to the tools of the MCP servers you connect to: their own readOnlyHint decides.

The name is the tool name, not the method name: GetCaseStatusAsync becomes get_case_status.

Func<MentorConfirmationContext, string?>? · Default: null

Writes the text of the confirmation box, for example in your own words or in your users' language. Before each confirmation box, MentorAgent calls the function with a MentorConfirmationContext: the tool, its description, the arguments, the language, the default text and the services of the conversation.

  • Return a text to show it in the box. The widget and the clients of the SignalR hub receive it as it is. For a tool of a hosted MCP server, the server's name can follow it.
  • Return null to show the default text: the action's description, then its arguments.
  • If the function returns only white space or throws an exception, the box shows the default text and MentorAgent logs a warning. The box always appears.

With the option at null, every box shows the default text. The title of the box stays "Confirmation required", in the language of Language.

Program.cs
options.ConfirmationMessage = context =>
{
    // Any other tool keeps the default text: the description, then the arguments
    if (context.ToolName != "reopen_case")
        return null;

    var caseNumber = context.Arguments
        .FirstOrDefault(a => a.Name == "caseNumber")?.Value;
    var reason = context.Arguments.FirstOrDefault(a => a.Name == "reason")?.Value;
    return context.Language == MentorLanguage.Italian
        ? $"Riaprire la pratica {caseNumber}? Motivo: {reason}"
        : $"Reopen case {caseNumber}? Reason: {reason}";
};

IList<Func<ToolAutoApprovalRuleContext, ValueTask<bool>>> · Default: empty

Works only with HitlMode = Native. MentorAgent evaluates the rules in order. The first rule that returns true approves the call, and no confirmation box appears.

  • Setting a rule, or MaxAutoApprovalIterations, also turns on "don't ask again": after the user approves a call, matching calls stop asking for the rest of the session.
  • A rule skips the question, not the role check: RequiredRoles still applies.
  • Rules do not cover the tools of specialists and teams.
  • A rule never approves a call that RequireActionForChangeRequests requires: the confirmation box always appears.

int? · Default: null

Native mode only. A cap on how many times one turn may run again because its approvals were granted by AutoApprovalRules. Each run is a billable model call. null uses Agent Framework's default.

TimeSpan · Default: 00:00:00

How long a confirmation box waits for an answer, from when it appears. When the time passes with no answer, the confirmation counts as rejected:

  • the action does not run;
  • the model is told that nobody confirmed it in time, not that the user declined;
  • an answer that comes later is ignored, and POST /mentor/approve answers 404.

It applies in Blocking and in Native mode, to the actions of specialists and team members, and to a confirmation that an action policy asks for. 00:00:00 or a negative value, the default, means no limit: the box waits until the user answers, presses Stop or closes the session.

MentorAgent does not read this option from configuration. Set it in code, for example options.ConfirmationTimeout = TimeSpan.FromMinutes(5).

TimeSpan · Default: 00:00:05

How long each call to an action policy may take. An action policy is a class of yours, registered with AddMentorActionPolicy<T>(), that decides whether a user may use an action and whether a call may run: for example a permission per company or per record. A policy that does not answer in time is no longer awaited, and its token is cancelled:

  • the call it was asked about does not run;
  • when MentorAgent decides which actions to offer, the action is left out.

00:00:00 or a negative value means no limit. Stop still cancels the wait. MentorAgent does not read this option from configuration: set it in code.

Several options in this group use the classifier: the model that makes MentorAgent's own short decisions about a message, for example whether it is safe. It defaults to ChatClient. You choose another model with ClassifierChatClient.

bool · Default: false

Before each turn, asks a model whether the message is a prompt injection, a jailbreak or an attempt to extract the system prompt or credentials. It adds one call per message, on ClassifierChatClient. With Agent and no ClassifierChatClient, the check is skipped, and a warning says so when the coordinator is built.

TimeSpan · Default: 00:00:15

How long the input and output checks may take before they count as failed. TimeSpan.Zero or a negative value means no limit, and Stop still cancels a check. Raise it for a slow local model.

MentorSafetyCheckFailure · Default: Allow

What happens when a safety check gives no verdict, after a timeout or an error. Allow lets the message or the reply through, and logs a warning. Block refuses the message, or withholds the reply. With both values, a check that finds the message or the reply unsafe blocks it.

bool · Default: false

Politely refuses a message that plainly has nothing to do with your application, without calling the coordinator. When the classifier is unsure, MentorAgent answers the message. A name that the classifier does not recognize may be one of your records. MentorAgent answers greetings, follow-up questions and questions about the agent too.

MentorAgent judges the message against HostedToolDomainScope or, when that is not set, against the name and the description of your app and the names and descriptions of your pages. With EnableSafetyCheck on, one call answers both questions, with a longer prompt. Without it, the option adds one short call per turn, on ClassifierChatClient or, when that is not set, on ChatClient. When HostedToolDomainCheck is on, a turn that is about to use a hosted tool also goes through that check, which asks a stricter question. Each refusal counts in GuardrailBlockCount and in the mentoragent.guardrail_blocks metric.

bool · Default: false

Builds one coordinator when the application starts, so the first user does not wait for work that all users share: the tool embeddings, the shared MCP sessions and the cards of remote agents. The warm-up runs once the host has started. On a web host, it runs after the host starts listening. A failure of the warm-up itself never stops the application, but an invalid configuration stops it. The option needs a generic host, such as ASP.NET Core or a worker service. A .NET MAUI app runs no hosted services, so there the option does nothing.

int · Default: 4000

The maximum length of a message, in characters. A longer message is refused before the model is called. 0 means no limit.

int · Default: 0

The maximum number of messages per user in each RateLimitWindowSecs window. 0 turns the limit off. MentorAgent counts a signed-in user by the same key as memory (see UseMemoryContext). It counts an anonymous user by AnonymousIdentity. With the default, PerSession, a page reload starts a new count. So only signed-in users get a real limit per user.

The tool calls of the MCP server have the same limit and window, but their own allowance, separate from the chat messages of the same user. MentorAgent counts the caller in this order:

  • by the NameIdentifier claim or, if that claim is missing, by sub, oid or the name;
  • by remote address, when the host registers IHttpContextAccessor, as AddMentorAgentServer() and AddMentorAgentA2A() do.

Without either, all such callers share one allowance, and one busy client can use it up for the others. A call over the limit gets an isError result.

int · Default: 60

The length of the rate-limit window, in seconds.

bool · Default: false

Puts the exception message of a failing specialist or team into the reply the user reads. Development only.

These options add checks on messages and replies, your own or built in, and let you change tool results, error messages and the pipeline around ChatClient. A guardrail that you write returns true when the text is safe.

Func<string, CancellationToken, Task<bool>>? · Default: null

Your own check on each incoming message; return true when it is safe. When set, it runs on every message and replaces the built-in check of EnableSafetyCheck.

Func<string, CancellationToken, Task<bool>>? · Default: null

Your own check on the completed reply; return true when it is safe. While it is set, each reply is held back and shown after the check, without live streaming.

bool · Default: false

Checks each reply before the user sees it. The check blocks a reply that leaks instructions or secrets, discloses someone's personal data or contains harmful content. The reply is shown after the check. The check runs on ClassifierChatClient, or on ChatClient when that is not set. With Agent and no ClassifierChatClient, the check is skipped, and a warning says so when the coordinator is built. A custom OutputGuardrail takes precedence.

Func<string, object?, object?>? · Default: null

Changes a tool result before it returns to the model, for example to remove sensitive data. The hook receives the tool name and the result. It also runs on the result of each call to the MCP server, before the caller sees it. If the hook throws, an MCP caller gets an isError result and no data. In the chat, the model is told that the action ran, that its result was withheld and that it must not run the action again.

Func<Exception, string?>? · Default: null

Maps an exception to the message the user sees. Return null to keep the built-in message.

When a tool throws, the message goes to the chat and to the model:

  • With the hook set, the chat never shows the text of the exception. It shows your message. When the hook returns null or an empty string, or throws, it shows "The action could not be completed." in the language of the widget.
  • The model gets your message or, without one, the message of the exception, because it needs a reason to answer. Return a message for any exception whose text must not reach the model.
  • Without the hook, the chat and the model both get the message of the exception.

Only an exception that the tool itself throws counts as a failure. An exception in OnToolResult, in the cards or in a screen update does not. The log has the type of the exception, and the whole exception at Debug level.

Func<ChatClientBuilder, ChatClientBuilder>? · Default: null

Adds your own middleware, such as logging or caching, to the pipeline that MentorAgent builds around ChatClient. Your middleware wraps MentorAgent's own middleware: it is the outermost layer.

MentorAgent builds this pipeline once for each coordinator, so once per circuit on Blazor Server and once per connection on the hub. A limit that you keep there, such as a semaphore or a counter, does not limit the whole process: keep that state in a singleton that you inject.

Only the coordinator's own model calls go through the pipeline:

  • MentorAgent's auxiliary calls use ClassifierChatClient, or ChatClient when it is not set. They are the classifier, the safety checks, the domain check of hosted tools, the off-topic check, the memory extraction, and the lookup and action checks.
  • The router of the handoff, the specialists and the team members use ChatClient.

These options control the history: the messages of the current conversation, which the model receives again at each turn. By default MentorAgent keeps the history in memory. A history provider can store it elsewhere, for example in a database.

int · Default: 50

The maximum number of messages that the default in-memory history keeps. Every message counts, tool calls and tool results included. MentorAgent removes whole turns, oldest first, so the model still sees the data that a tool read in the previous turn. 0 or a negative value turns trimming off, with a warning. MentorAgent ignores this option in three cases: when ChatReducer is set, when you supply a history provider, and when UseServiceManagedHistory is on.

IChatReducer? · Default: null

Replaces the reducer that keeps the default in-memory history short. A reducer decides which messages the history keeps. With null, MentorAgent uses a MentorChatReducer of MaxSessionMessages messages. It cuts whole turns, oldest first, and keeps every tool call together with its result.

When you set it, MentorAgent uses it as it is and does not read MaxSessionMessages. One instance serves every session, so it must not keep the state of one conversation. To keep only the text of the conversation, set a MessageCountingChatReducer: it is experimental in Microsoft.Extensions.AI (diagnostic MEAI001).

This option applies to the default history of ChatClient only. MentorAgent uses a ChatHistoryProvider or a ChatHistoryProviderFactory of yours without changes, so give it a reducer yourself, for example new MentorChatReducer(50) from the namespace Novior.MentorAgent.Core. With UseServiceManagedHistory, or when you supply a provider, a set ChatReducer has no effect and MentorAgent logs a warning.

ChatHistoryProvider? · Default: null

A persistent history provider. One instance serves every user in the process: use it only in a single-user host, or with a provider that keeps a separate store for each user. With Agent, set the provider on the agent instead.

Func<IServiceProvider, ChatHistoryProvider>? · Default: null

Builds a history provider once for each coordinator. A coordinator exists for one circuit, one hub connection or one app run. Prefer it whenever the application has more than one user. Setting it together with ChatHistoryProvider fails validation. The provider is kept across new conversations, so it must keep the transcript in the session, not in a field: see Conversation history and memory.

bool · Default: false

Tells MentorAgent that the chat client keeps the conversation on the service, as the OpenAI and Azure OpenAI Responses client does. MentorAgent then installs no history provider and no compaction. MaxSessionMessages, ChatReducer, EnableCompaction and your history provider no longer apply. A session snapshot records how the history is kept. A host that keeps the history in the other way refuses the snapshot.

MentorSessionContextPlacement · Default: Instructions

Where MentorAgent puts the context of each request: the page that is open, the data that it shows, its address, the date and the time.

  • Instructions: at the end of the instructions. The context changes with every minute and every page, so the instructions change too.
  • LastUserMessage: at the start of the user's last message, sent to the model only. The instructions stay the same for the whole session, so a model that caches the prompt by its beginning, such as a local model, does not read the whole conversation again on every new minute or page. The stored conversation keeps the message as the user wrote it.

The user's name and roles, the memory and the RAG documents stay in the instructions. LastUserMessage works with a ChatClient and a history that your application keeps, the built-in one or yours. With UseServiceManagedHistory or a prebuilt Agent, it has no effect, and a warning at startup says so.

Memory keeps facts about each user across conversations, such as a name, a role or a preference, and adds them to the prompt.

bool · Default: false

Turns on memory across conversations. MentorAgent identifies a signed-in user by the NameIdentifier claim. If that claim is missing, it uses sub, oid or the name of the identity. A key made from sub, oid or the name starts with the claim name (sub:…, name:…), so a name never matches the identifier of another user. MentorAgent identifies an anonymous user by AnonymousIdentity. A signed-in user with none of these claims is keyed as an anonymous user. Memories live in RAM unless you register your own IMentorMemoryStore before AddMentorAgent.

MentorAnonymousIdentity · Default: PerSession

Who an anonymous user is, for memory and for the rate limit. PerSession gives each circuit or connection its own key. Shared gives every anonymous user the same key, anonymous. Use Shared only in a single-user app, such as a .NET MAUI app: on a website, users would share memories and the rate limit. Ignored for a signed-in user.

int · Default: 10

How many memories go into the prompt: the most recent, or the most relevant with MemoryRelevanceFiltering.

bool · Default: false

Adds to the prompt only the memories that are related to the message, plus the identity and preference facts. MentorAgent finds the related memories with an embedding. The option needs EmbeddingGenerator. Without it, MentorAgent uses the most recent memories.

bool · Default: true

Saves lasting facts about the user with a small extra model call after each message. It needs UseMemoryContext. With ChatClient, this call is the only way memories are saved, and the remember tool is removed. With Agent and a ClassifierChatClient, the extraction runs through the classifier and is the only writer: the remember tool is removed too. With Agent and no ClassifierChatClient, the remember tool saves the facts. Set false to save the extra call.

Retrieval-augmented generation (RAG) lets the model answer from your own documents. For each message, before the coordinator answers, MentorAgent searches them and adds the relevant ones to the prompt.

You write the search yourself, as a class that implements IMentorRagSource, and you register it in dependency injection. MentorAgent has no ready-made source for ITextSearch. If you use one, call it from your own source.

MemberWhat it does
SearchAsync(string query, int maxResults, CancellationToken)Searches with the text of the message. Every source implements it.
SearchAsync(MentorRagRequest request, CancellationToken)The method that MentorAgent calls. Its default implementation calls the first one, so implement it only when the search needs more than the text.
DefaultMinScoreThe relevance threshold for the scores of your source. It applies when you do not set RagMinScore.

Do not make each SearchAsync method call the other. They would call each other forever.

A MentorRagRequest holds:

PropertyHolds
Query, MaxResultsThe message of the user, and RagResultCount.
RecentMessagesThe messages before this one, oldest first: the user's messages and the replies of the last three turns of this conversation, as text. It is empty on the first turn, after a reset and after a restore. Over Server-Sent Events it is always empty, because each request is a conversation of its own.
UserThe user who sends the message: the caller of the hub, of /mentor/chat or of an A2A request. In a Blazor circuit, it is the user that the AuthenticationStateProvider of the host reports. It is null when the host has no authentication. For an anonymous visitor, it is a user who is not authenticated. Use it to leave out the documents that this user may not read.
PageName, PageDataThe name of the current page and the data that the page put in context with IMentorPageContext.

A result is a MentorRagResult: the content, the source URL, the title and the score. It can also carry Metadata: facts about the document, such as a date or a version.

  • The model sees them under the title of the document, one key: value per entry, so it can tell the 2025 price list from the 2026 one.
  • They also travel to the client with the citation chips.
  • The model can repeat them in its reply.

Put nothing in them that the user must not see.

CaseKnowledgeSource.cs
public class CaseKnowledgeSource(ICaseKnowledge knowledge) : IMentorRagSource
{
    // The scores of this source are cosine similarities, between 0 and 1:
    // say which threshold suits them
    public float? DefaultMinScore => 0.6f;

    // The method that MentorAgent calls: it sees who is asking
    public Task<IReadOnlyList<MentorRagResult>> SearchAsync(
        MentorRagRequest request, CancellationToken ct = default)
        => FindAsync(request.Query, request.MaxResults, request.User, ct);

    // Required by the interface. Share the search through a third method:
    // never call one SearchAsync from the other
    public Task<IReadOnlyList<MentorRagResult>> SearchAsync(
        string query, int maxResults, CancellationToken ct = default)
        => FindAsync(query, maxResults, user: null, ct);

    async Task<IReadOnlyList<MentorRagResult>> FindAsync(
        string text, int max, ClaimsPrincipal? user, CancellationToken ct)
    {
        // The knowledge base returns only the articles that this user may read
        var articles = await knowledge.FindAsync(text, max, user, ct);
        return [.. articles.Select(a =>
            new MentorRagResult(a.Text, a.Url, a.Title, a.Score)
            {
                // Facts the model weighs with the text:
                // it can tell the 2025 price list from the 2026 one
                Metadata = new Dictionary<string, string>
                {
                    ["valid from"] = a.ValidFrom,
                },
            })];
    }
}

bool · Default: false

Searches your knowledge base before each coordinator call and adds the relevant documents to the prompt. Needs a registered IMentorRagSource.

int · Default: 5

How many documents are retrieved for each message.

float · Default: 2

The minimum relevance score of a document. The scale depends on your source. With keyword search, 2 means about two matches. With cosine similarity (a score from 0 to 1), use 0.5 to 0.75. 0 turns filtering off.

MentorAgent uses the first of these thresholds that applies:

  1. this option, when you set it in code or in configuration;
  2. the DefaultMinScore of the source, when it declares one;
  3. the default of this option, 2.

A source with cosine scores should declare a DefaultMinScore. Without one, the default 2 discards every document, and a warning says which threshold discarded them.

string · Default: "Use the following documents to answer:\n{documents}"

The text that introduces the documents in the prompt; {documents} is replaced with them.

bool · Default: false

Shows citation chips under the agent's replies. The pages of a hosted web search and the documents of a file search use the same chips. The Metadata of a document travels with its chip. Over the hub, the client receives it in the metadata field of RagSourcesReady.

The model cites a document with its number, such as [1]. A list such as [1, 2] or a range such as [1-3] shows a chip for each document it names. A range with an end that names no document, such as [0-9] in a code sample, cites nothing.

A skill is a set of instructions, with optional resources, that the model loads only when it needs them. Until then, the prompt carries only the skill's name and description.

bool · Default: false

Turns on skill discovery and the load_skill and read_skill_resource tools. Skills are declared with [MentorSkill] or as files.

string · Default: "Skills"

The folder of file-based skills, with one sub-folder and one SKILL.md per skill. A relative path starts from the content root of the application. An absolute path works too.

IList<AgentSkillsSource> · Default: empty

More skill sources, through Agent Framework's own abstraction: a database, a remote catalog or AgentFileSkillsSource. If a skill from a source has the same name as one of your own skills, MentorAgent uses yours.

Func<AgentSkill, bool>? · Default: null

Keeps or drops a skill that comes from SkillSources. It never applies to the skills MentorAgent discovered itself.

TimeSpan? · Default: null

How long a list of skills from SkillSources stays fresh. While it is fresh, every session in the process shares it. The first session that starts after the interval reads the sources again. With null, there is no shared list: every new session reads the sources. Set it for a remote or slow source.

The Model Context Protocol (MCP) lets an AI application use tools from other programs. MentorAgent can be an MCP client: it uses the tools of external MCP servers. It can also be an MCP server: it publishes your actions to MCP clients.

MentorMcpServer[]? · Default: null

External MCP servers your application connects to as a client. Their tools become tools of the coordinator, like your actions. See MentorMcpServer.

bool · Default: false

Exposes your actions as an MCP server. The server is in the Novior.MentorAgent.Server package. To use it, call AddMentorAgentMcp() after AddMentorAgent, and call app.MapMentorAgentMcp(). AddMentorAgentServer() calls AddMentorAgentMcp() for you. MentorAgent reads this option when you call AddMentorAgent, so set it in that callback.

Actions that need confirmation are never published, and that includes a [MentorAction] that asks for confirmation by default: see RequireConfirmationForWrites and where nobody can answer. An action declared read-only is published with readOnlyHint: some MCP clients skip their own confirmation for such a tool, so declare it only on an action that only reads. Every tool call passes through OnToolResult and RateLimitPerUser.

The endpoint has no Cross-Origin Resource Sharing (CORS) policy. That suits desktop and IDE clients. A client that runs in a browser needs a CORS policy that you turn on. See MCP server.

string · Default: "/mcp"

The path MapMentorAgentMcp() maps when you pass none. A path passed to the method wins.

Func<ClaimsPrincipal?>? · Default: null

Tells MentorAgent who is calling the MCP server. An action with RequiredRoles is then published to an authenticated caller who has one of its roles. With null, MentorAgent withholds every action with RequiredRoles. It also withholds them when the function returns null or an unauthenticated user, or when it throws.

MentorAgent tests the roles with ClaimsPrincipal.IsInRole. A roles claim works once the identity names it as its RoleClaimType. With JwtBearer and MapInboundClaims = false, set TokenValidationParameters.RoleClaimType = "roles". MentorAgent calls the function once for each MCP request and never keeps the answer for another caller, so the function should be fast.

bool · Default: false

Shows a badge in the widget header with the connection status of McpServers.

The Agent-to-Agent (A2A) protocol lets AI agents call each other. MentorAgent can hand requests to remote A2A agents, and it can answer as an A2A agent itself. An A2A agent describes itself in an agent card, a JSON document at /.well-known/agent-card.json.

MentorRemoteAgent[]? · Default: null

Remote A2A agents the coordinator can hand requests to, like local specialists. They need ChatClient. Any user of your application can reach them: see MentorRemoteAgent.

bool · Default: false

Exposes your application as an A2A agent. The agent is in the Novior.MentorAgent.Server package. To use it, call AddMentorAgentA2A() after AddMentorAgent, and call app.MapMentorAgentA2A(). AddMentorAgentServer() calls AddMentorAgentA2A() for you. MentorAgent reads this option when you call AddMentorAgent. See A2A server.

string · Default: "/a2a"

The path of the A2A task handler when you pass none to MapMentorAgentA2A(). The agent card stays at /.well-known/agent-card.json.

string? · Default: null

The full public URL of your A2A endpoint, path included. MentorAgent publishes it as it is in the agent card. With null, the card carries only the path: a MentorAgent caller resolves it against the address of the card, and another A2A client may not. Set it whenever other agents call this one.

AgentCardInfo? · Default: null

Extra metadata for the agent card; the name and description come from AppName and AppDescription. See AgentCardInfo.

bool · Default: false

Shows a badge in the widget header that lists the configured remote agents.

Hosted tools run on the model provider's infrastructure, not in your application: web search, code interpreter, file search, image generation and remote MCP servers. MentorAgent's roles and confirmation do not apply to them: see HostedTools.

MentorHostedTools · Default: None

The provider-hosted tools that the model may use. You can combine these values: WebSearch, CodeInterpreter, FileSearch, ImageGeneration and HostedMcp. The tools run on the provider's infrastructure and need ChatClient. Which ones work depends on the client: the Responses client supports them all, and the Chat Completions client supports at most web search. Roles, confirmation and OnToolResult do not apply to them.

IReadOnlyList<string>? · Default: null

The provider's vector stores that FileSearch searches. Required when FileSearch is on: without an id the tool is skipped and a warning is logged.

int? · Default: null

An upper bound on file-search matches. null lets the provider decide.

string? · Default: null

The model that ImageGeneration uses, for example gpt-image-1-mini. On Azure OpenAI, this option alone is not enough. The request must also carry the image deployment in the x-ms-oai-image-generation-deployment header. You add that header where you build the Azure client.

string? · Default: null

The size of generated images as WIDTHxHEIGHT, for example 1024x1024: the setting that controls the cost of image generation. A value that cannot be read is ignored with a warning.

IReadOnlyList<MentorHostedMcpServer>? · Default: null

The remote MCP servers the provider connects to for HostedMcp. Required when HostedMcp is on. See MentorHostedMcpServer.

bool · Default: true

Shows hosted-tool work while it happens: "Searching the web…", "Running code…", "Generating the image…". Citations also need ShowRagSources.

bool · Default: false

Shows a badge in the widget header that lists the active hosted tools. A development aid.

bool · Default: true

Offers each hosted tool to the model only when tool filtering finds it relevant to the message. The option needs EnableToolFiltering and EmbeddingGenerator. Without them, MentorAgent filters nothing and logs a warning.

float? · Default: null

The similarity threshold for hosted tools; null means 0.15. Keep it low: this filter is only a first, rough selection, and HostedToolDomainCheck makes the final decision.

bool · Default: false

Before a hosted tool runs, asks ClassifierChatClient, or ChatClient when that is not set, whether the message concerns your application. The check runs once per turn, and only when a hosted tool passed the filter. If the check itself fails, because of an error or an unexpected answer, the turn goes on. If the check says that the message is not about your application, the agent is told only that the hosted tools are not for this request. The check is strict on purpose, so it can say this about a request that your own actions can answer. In that case the agent still answers the request with your own actions.

string? · Default: null

The topic that the off-topic check uses to decide whether a message is about your application. null derives it from AppName, AppDescription and the page names. When hosted tools are on, the derived text also lists the enabled capabilities, and says that they do not widen the topic. RefuseOutOfScope uses the text you set, as written.

Func<string, CancellationToken, Task<bool>>? · Default: null

Replaces the model call of the off-topic check with your own decision: return true when the message is about your application.

int · Default: 0

A hard cap on hosted-tool calls per session. Beyond it, MentorAgent no longer offers hosted tools to the model. 0 means no cap. Over SignalR, the session is the connection. Each request to /mentor/chat, with GET or with POST, is a session of its own.

These options reduce the tokens sent to the model on each call, and so the cost.

bool · Default: false

Sends the model only the tools relevant to the message, by embedding similarity. The core tools are always sent: navigation, memory, handing off to specialists and teams, skills and UI actions. Needs EmbeddingGenerator and ChatClient: without them, every tool is sent and a warning is logged.

The embedding ranks the actions well, but its score is not reliable as a threshold: a question that needs data can score below a greeting. So the filter does not rely on the threshold alone:

  • When no action scores above ToolFilterMinScore, the three best-ranked actions that need no confirmation and no role are sent anyway, within ToolFilterMaxTools. An MCP tool qualifies only when its server marks it readOnlyHint. This adds about 80 input tokens to each model call on such turns, greetings included.
  • When every selected action needs confirmation, up to three of the best-ranked actions that do not need it are sent with them, within ToolFilterMaxTools.
  • When a single selected action needs no confirmation, it is sent alone, unless ToolFilterMinTools asks for more.

An action needs confirmation when it has RequiresConfirmation, when RequiresApproval matches it, when it comes from a server with MentorMcpServer.RequiresConfirmation, or when it is a [MentorAction] that RequireConfirmationForWrites makes ask. The filter reads these declarations even when RequireConfirmation is false. An action without confirmation can therefore reach the model on any message. A [MentorAction] that is not read-only asks for confirmation by default, but a method with only [Description] does not. Use [MentorAction] for every method that changes data.

When a message matches no action, MentorAgent sends the actions that the previous message selected. So the answer to the agent's own question, such as "Yes, go ahead", still arrives with the action that the question was about. MentorAgent reuses the actions for one message only, never twice. It never sends an action that needs confirmation without other actions. A new or restored conversation reuses nothing.

Two options, both off by default, use the selection of the filter: RequireLookupForRecordQuestions and RequireActionForChangeRequests.

int · Default: 12

The maximum number of matched tools sent, in addition to the core tools.

int · Default: 0

Once one action scores above ToolFilterMinScore, MentorAgent sends at least this many of the best-ranked actions, up to ToolFilterMaxTools. With 0, the threshold alone decides. A higher value is a trade-off. It can add the read action that the model needed, but it can also add a wrong action that the model picks instead.

You do not need this option to keep an action that needs confirmation from reaching the model alone, because EnableToolFiltering already sends other actions with it.

The option does not apply to a message where no action scores above ToolFilterMinScore. That message gets the three best-ranked actions that need no confirmation and no role.

float · Default: 0.35

The minimum cosine similarity, from 0 to 1, for a tool to count as relevant. Higher is stricter. The threshold decides which actions match. It does not decide whether a message needs an action: see EnableToolFiltering.

bool · Default: false

Makes the agent read a value before it answers a question about your records. A classifier model reads each message. When it says that the message asks for a value of your records (a price, a stock level, the status of an order), the first model call must call a read-only action. With tool filtering, it must be one that the filter selected for that message; without it, any declared read that the user may run. The agent can also hand the request to a specialist or a team. The agent then writes the reply from what the action returned.

  • Which actions. Those you declared read-only, with ReadOnly or IsReadOnlyAction, and the tools that a connected MCP server marks readOnlyHint. An action that needs confirmation or a role is never required.
  • What it needs. A ChatClient and at least one declared read that needs no role. Tool filtering is not needed. Otherwise the option has no effect, and a startup warning says so.
  • What it costs. One small classifier call on each turn whose selection holds a declared read. With tool filtering, which also sends reads when nothing matches, and without it, that is nearly every turn, greetings included. The call runs on ClassifierChatClient when you set one, within SafetyCheckTimeout. The reply starts after the result of the action, so a Proactive sentence such as "I am looking it up" no longer comes first.
  • When it does not force. A message addressed by name to one of your specialists, teams or remote agents is not forced. If the classifier fails, times out or gives an answer it cannot read, the turn runs as it would without the option.

bool · Default: false

Makes the agent start a change with the action itself. The confirmation box then comes before any sentence. Without it, the model can answer "Stock updated" without calling anything: no box appears, and nothing changes.

A classifier model reads each message. It checks two things: whether the message asks to carry out one of your actions that need confirmation and that the user may run, and whether the message gives every input that the action needs. If both are true, the first model call must call one of those actions, chosen from the ones that the tool filter selected for that message. The agent can also hand the request to a specialist or a team. When the message asks for one of your [MentorPage] pages, the first call must be navigate_to.

  • A request that leaves something out is not forced. The classifier sees the inputs that each action requires. A request is not complete when:

    • an input is missing;
    • the record is only described ("cancel the last order");
    • a name is given where the input is an ID;
    • the new value depends on the current one ("add 5 units").

    In these cases the agent may ask, as it would without the option. An input counts as needed only when the action's schema marks it required.

  • Which actions. Only actions that show a confirmation box, with RequireConfirmation on. An action shows the box when:

    MentorAgent never requires an action whose input schema it cannot show faithfully: a schema with $ref or a combination of schemas, or an MCP tool that marks no parameter as required.

  • With HitlMode.Native. No rule of AutoApprovalRules is consulted for a required call, so the box always appears.

  • Pages. A page that your application does not have, or something within the page that is already open, is not forced. A request from another agent never has to navigate.

  • What it needs and costs. EnableToolFiltering and an EmbeddingGenerator. One small classifier call on each turn while the application has a page, or the message selected such an action. With RequireLookupForRecordQuestions on as well, one call answers both. A startup warning says when the option has no effect, or covers page requests only.

The turn that answers a confirmation left open by Stop is never forced.

bool · Default: false

Shortens a long conversation history before each model call, without calling a model. It collapses old tool results and keeps the last CompactionMaxTurns turns. It changes what is sent to the model, never the conversation that is stored: the history provider and the session snapshots keep every message. It works with ChatClient and with a history that your application keeps: the built-in provider, ChatHistoryProviderFactory or ChatHistoryProvider. With UseServiceManagedHistory it is not installed.

int · Default: 4000

The size of the history, in tokens, at which compaction starts. If the history is still larger after compaction, it is truncated to this size.

int · Default: 8

How many of the most recent user turns compaction keeps intact.

Model routing sends the turns that need it to a stronger, more expensive model, StrongChatClient, and keeps the others on ChatClient. ClassifierChatClient is the model for MentorAgent's own short decisions: a small, fast model is enough.

IChatClient? · Default: null

A stronger, more expensive model for the turns that need it; ChatClient stays the default. Routing also needs what RoutingStrategy requires: otherwise a warning is logged and every turn stays on ChatClient.

MentorRoutingStrategy · Default: Custom

How MentorAgent chooses between ChatClient and StrongChatClient:

  • Custom: calls your UseStrongModelAsync or UseStrongModel.
  • Semantic: compares the message with RoutingComplexExemplars. Needs EmbeddingGenerator.
  • Classifier: asks a model whether the turn needs the strong model, with one extra call per turn. The call runs on RoutingClassifierClient or, when that is not set, on ChatClient.
  • Cascade: answers with the cheap model first, and runs the turn again on the strong model when the answer is not good enough. This works only on calls that do not stream. Chat turns stream, so there Cascade decides like Classifier.

Func<IReadOnlyList<ChatMessage>, CancellationToken, Task<bool>>? · Default: null

Custom only: your router. It sees the whole conversation; return true to use StrongChatClient. MentorAgent uses it instead of UseStrongModel when both are set.

Func<string, bool>? · Default: null

Custom only: a synchronous predicate on the latest user message. Prefer UseStrongModelAsync.

IReadOnlyList<string>? · Default: null

Semantic only: example turns that deserve the strong model. null uses a built-in multilingual set.

float · Default: 0.35

Semantic only: the similarity at which MentorAgent sends a turn to StrongChatClient. Higher is stricter.

IChatClient? · Default: null

Classifier and Cascade only: the client that judges the turn. null uses ChatClient.

IChatClient? · Default: null

The client for MentorAgent's own small decisions:

  • the input and output safety checks;
  • the off-topic checks of hosted tools and of RefuseOutOfScope;
  • the fact extraction of MemoryAutoCapture;
  • the grounding check of A2A replies;
  • the question behind RequireLookupForRecordQuestions and RequireActionForChangeRequests, asked with temperature 0 (the most predictable setting).

Most of these calls run before the user sees the reply, so point it at a small, fast deployment. null uses ChatClient. With Agent and no ChatClient, the safety checks run only when this is set. Without it, a warning says so when the coordinator is built.

MentorAgent can send traces and metrics to OpenTelemetry. It also keeps token and cost figures for a dashboard: the MentorDashboard component, or GET /mentor/admin/metrics for other clients.

bool · Default: false

Emits OpenTelemetry traces and metrics that follow the OpenTelemetry conventions for generative AI, plus MentorAgent's own spans and counters.

The traces of those conventions cover only the coordinator's model calls, on the pipeline of ChatClient. MentorAgent does not trace the other model calls: those of ClassifierChatClient, of EmbeddingGenerator, of a prebuilt Agent and of the agents under the coordinator, such as the specialists. To trace a client, wrap it with UseOpenTelemetry() before you pass it to MentorAgent.

bool · Default: false

Includes prompt and response content in the telemetry. Development only.

string · Default: "MentorAgent"

The name of the ActivitySource and of the Meter: add it with .AddSource(name).AddMeter(name). An exporter that listens on another name receives nothing.

IReadOnlyDictionary<string, ModelPrice>? · Default: null

Token prices per model, for the dashboard's cost estimate. MentorAgent ships no prices. Enter them under the model id that the dashboard shows; on Azure OpenAI, that id is the deployment name. See ModelPrice.

string · Default: "Admin"

The role that a caller needs for GET /mentor/admin/metrics of Novior.MentorAgent.Server. An empty string ("") leaves the endpoint open: use it for development only. The MentorDashboard component does not check the role, so protect the page that hosts it.

TimeSpan · Default: 00:00:30

How often the metrics are saved to a registered IMentorMetricsStore; they are also saved on shutdown. Without a store the metrics live in RAM and restart from zero.

TimeSpan · Default: 7.00:00:00

How far back the dashboard keeps its hourly time series: seven days.

An entry of McpServers: an MCP server your application connects to, over HTTP or by starting a local process.

string · Required

The server's name, shown in the MCP status badge.

string? · Default: null

The endpoint of an HTTP MCP server. Set this or Command.

string? · Default: null

The executable of a local server that talks over stdio, for example npx. On Windows, script launchers such as npx run through cmd.exe /c for you.

IList<string>? · Default: null

The command-line arguments of the local process.

IDictionary<string, string>? · Default: null

Environment variables for the local process.

string[]? · Default: null

The only tools to take from the server. null takes them all.

bool · Default: false

Asks the user to confirm every call to a tool of this server.

bool? · Default: null

Whether one connection serves the whole process. null decides by transport: a ServerUrl server is shared, a Command server gets one process per session. Set true only for a local server you know is stateless.

An entry of HostedMcpServers: a remote MCP server the model provider connects to. Your process never talks to it.

string · Required

The identifier the provider uses for the server. The model sees it.

string · Required

The address of the server. The provider connects to it, so it must be reachable from the provider's network: localhost does not work.

string? · Default: null

A description passed to the provider, for more context.

IList<string>? · Default: null

The only tools the provider may call. null allows them all; prefer a list for a server you do not control.

bool · Default: true

Asks for the user's approval of every call, in the usual confirmation box. Set false only for a read-only server that you trust.

MentorHostedMcpServer.AlwaysRequireApprovalTools

Section titled “MentorHostedMcpServer.AlwaysRequireApprovalTools”

IList<string>? · Default: null

Tools that always need approval. For these tools, this list overrides RequireApproval.

MentorHostedMcpServer.NeverRequireApprovalTools

Section titled “MentorHostedMcpServer.NeverRequireApprovalTools”

IList<string>? · Default: null

Tools that never need approval. For these tools, this list overrides RequireApproval.

IDictionary<string, string>? · Default: null

HTTP headers that the provider sends to the server, for example an authorization token. They leave your process: use a token with few permissions that expires soon.

An entry of RemoteAgents: an A2A agent the coordinator can hand requests to.

A remote agent has no role check of its own: any user of your application can reach it through route_to_specialist. To restrict it, do one of these:

  • protect the /a2a endpoint on the remote host and give roles to the actions there;
  • do not configure the agent on hosts whose users may not use it.

The remote host receives the credentials in Headers, and they are the same for every user of your application. So the roles on the remote host limit what your application may do there. They do not limit which of your users may do it.

string · Required

The agent's name in the handoff graph and in the coordinator's prompt. It must be unique.

string · Required

What the agent does. The coordinator reads it to decide when to hand a request to it.

string · Required

The base URL of the agent, without /.well-known/agent-card.json: the SDK adds it.

Dictionary<string, string>? · Default: null

HTTP headers sent with the card request and with every task, for example Authorization.

The value of AgentCard: extra fields of the card served at /.well-known/agent-card.json.

string · Default: "1.0.0"

The agent version published on the card.

string? · Default: null

The URL of the agent's icon.

string? · Default: null

The provider or organization name.

string? · Default: null

The URL of the agent's documentation.

string[]? · Default: null

Reserved: it is not published on the card.

A value of ModelPricing, created with new ModelPrice(InputPer1M, OutputPer1M). Prices are in your billing currency, per million tokens.

decimal · Required

The price of one million input tokens.

decimal · Required

The price of one million output tokens.

What the ConfirmationMessage function receives to write the text of one confirmation box. MentorAgent creates it, and you only read it. It is in the Novior.MentorAgent.Options namespace.

string · Required

The tool about to run, as the model calls it: reopen_case.

string · Required

The action's description, from its [MentorAction] or its [Description]. Without one, it is the tool name made readable.

IReadOnlyList<MentorConfirmationArgument> · Required

The arguments the model is about to pass, as the default text shows them:

  • in the order of the method's parameters, then the arguments that the method does not declare;
  • for a tool of an MCP server, in the order the model wrote them;
  • each value with its control and bidirectional formatting characters turned into spaces, and cut at 200 characters;
  • without the empty ones.

Each MentorConfirmationArgument has three properties:

  • Name: the parameter's name, as the model sent it (caseNumber);
  • Label: the name made readable, as the default text shows it ("Case Number");
  • Value: the value, cleaned and cut.

MentorLanguage · Required

The value of Language: the language of the widget and of the agent.

string · Required

The text that MentorAgent shows when the function writes none: the description, then the arguments.

IServiceProvider · Required

The services of the conversation's scope, for what the text needs, such as an IStringLocalizer or the data of your application. It is empty when there is no scope.

In a Blazor WebAssembly or Auto client, AddMentorAgentBlazor(options => …) configures the widget and the connection to the server's hub. The model, the actions and every rule stay in MentorOptions, on the server. See option B of Blazor WebAssembly and Auto.

string · Default: "/mentor-hub"

The URL of the Novior.MentorAgent.Server SignalR hub. A browser resolves a relative value against the base address of the page. The widget does the same: /mentor-hub points to the root of the origin, and mentor-hub points under the base path of the app. Use a relative value when the server also serves the client. For a server on another origin, set its absolute URL.

Stop (/mentor/cancel) and the answers to confirmations (/mentor/approve) are ordinary HTTP requests, not hub calls. The widget sends them through an HttpClient that you register. If the hub URL is absolute, the widget sends them to the same origin. If it is relative, the widget sends them to the BaseAddress of that HttpClient. The client project of a Blazor Web App has no such HttpClient.

MentorAgentBlazorOptions.AccessTokenProvider

Section titled “MentorAgentBlazorOptions.AccessTokenProvider”

Func<Task<string?>>? · Default: null

Returns the bearer token for the hub. It is read on connect and on every reconnect, and sent with every Stop and confirmation request. Without it every user is anonymous to the server: RequiredRoles actions are refused, and memory and rate limit follow the server's AnonymousIdentity.

MentorAgentBlazorOptions.ConfigureConnection

Section titled “MentorAgentBlazorOptions.ConfigureConnection”

Action<HttpConnectionOptions>? · Default: null

Changes the hub's connection options: headers, transports, cookies. It runs after AccessTokenProvider.

string · Default: "Mentor AI"

The name shown in the widget header, as BotName on the server.

string? · Default: null

The message shown while the chat is empty, as WelcomeMessage on the server. It is rendered as HTML, so never build it from user input.

bool · Default: true

Shows the notice that tells people they are talking to an AI assistant, as ShowAiDisclosure on the server. The mark on each reply, data-ai-generated, stays either way.

MentorAgentBlazorOptions.AiDisclosureMessage

Section titled “MentorAgentBlazorOptions.AiDisclosureMessage”

string? · Default: null

The text of the notice, as AiDisclosureMessage on the server. null shows the default text in the language of Language.

string? · Default: null

The URL of the avatar shown in the widget, as AvatarUrl on the server.

string? · Default: null

The placeholder of the input box, as InputPlaceholder on the server. null uses the text in the language of Language.

MentorTheme · Default: Default

The widget's theme, as Theme on the server.

ChatPosition · Default: BottomRight

Where the widget sits, as Position on the server.

string? · Default: null

An accent color in hex, as PrimaryColor on the server.

MentorLanguage · Default: English

The language of the widget's texts. The language of the replies is Language on the server.

bool · Default: false

Shows the microphone button, as EnableVoiceInput on the server.

bool · Default: false

Reads replies aloud, as EnableVoiceOutput on the server.

bool · Default: true

Speaks each sentence as it streams, as VoiceStreaming on the server.

bool · Default: true

Stops the spoken answer when the user acts, as VoiceBargeIn on the server.

bool · Default: false

Lets the user talk without touching the widget, as VoiceHandsFree on the server. Ignored unless both voice options are on.

double · Default: 1.0

The speech rate, as VoiceRate on the server. The useful range is about 0.5 to 2.0.

bool · Default: false

Shows suggestion chips in the welcome panel. Off by default here, unlike the server's option.

MentorAgentBlazorOptions.EnableOnboardingTour

Section titled “MentorAgentBlazorOptions.EnableOnboardingTour”

bool · Default: false

Shows the guided tour on first open, with the steps fetched from TourUrl. The server answers GET /mentor/tour even when its own EnableOnboardingTour is false.

string · Default: "/mentor/tour"

Where the tour steps come from. A relative value is resolved against the origin of HubUrl when that is absolute, otherwise against the app's base address.

bool · Default: false

Shows the image attachment controls. The server's EnableImageInput must be on too: that is where images are checked.

int · Default: 4194304

The size cap per image in the client, 4 MB, as MaxImageBytes on the server. Keep it equal to the server's option.

MentorAgentBlazorOptions.MaxImagesPerMessage

Section titled “MentorAgentBlazorOptions.MaxImagesPerMessage”

int · Default: 4

The cap on images per message in the client, as MaxImagesPerMessage on the server. Keep it equal to the server's option.

List<string> · Default: ["image/png", "image/jpeg", "image/gif", "image/webp"]

The MIME types that the client accepts, as AllowedImageTypes on the server. Keep the list equal to the server's option.

bool · Default: false

Shows citation chips under the agent's replies, as ShowRagSources on the server.

MentorAgentBlazorOptions.ShowHostedToolsStatus

Section titled “MentorAgentBlazorOptions.ShowHostedToolsStatus”

bool · Default: false

Shows the badge of the active hosted tools, as ShowHostedToolsStatus on the server.

MentorHostedTools · Default: None

Leave it unset. The widget uses it only until the connection is up; then the server sends the hosted tools it really has, and the widget uses those.

bool · Default: false

Shows the MCP status badge, as ShowMcpStatus on the server.

bool · Default: false

Tells the widget that the server has MCP servers, which enables the MCP badge.

List<string> · Default: empty

The names of the MCP servers that the server connects to, as in McpServers. The widget shows them as connecting until their status arrives.

bool · Default: false

Shows the badge of the remote A2A agents, as ShowA2AStatus on the server.

bool · Default: false

Tells the widget that the server has remote agents, which enables the A2A badge.

MentorAgentBlazorOptions.RemoteAgentDisplays

Section titled “MentorAgentBlazorOptions.RemoteAgentDisplays”

List<AgentDisplayInfo> · Default: empty

The remote agents listed in the A2A badge, one AgentDisplayInfo each.

A value of RemoteAgentDisplays: a remote agent as the WebAssembly widget shows it in the A2A detail bar. It only drives the display; the remote agents themselves are configured on the server, in RemoteAgents.

string · Default: ""

The agent's name in the detail bar.

string? · Default: null

The address shown next to the name, usually the agent's base URL, as in MentorRemoteAgent.AgentCardUrl.

AddMentorAgentDeclarative(o => …) tells Novior.MentorAgent.Declarative where to find agents written in YAML. A definition chooses the model, writes the instructions and names the tools, so treat it as code. See Specialists in YAML.

string? · Default: null

The folder to scan, absolute or relative to the application's base directory. Use a path that ships with the application, never an upload folder or a path built from a request.

string · Default: "*.agent.yaml"

The file pattern inside Directory.

bool · Default: false

Scans the sub-folders too.

IList<string> · Default: empty

YAML definitions passed as text, for example from a database. They load before the files of Directory. Add to the list: it cannot be replaced.

MentorDeclarativeOptions.ConfigurationSection

Section titled “MentorDeclarativeOptions.ConfigurationSection”

string? · Default: null

The configuration section whose values a definition may reference. null exposes no configuration. Name one section, never the whole configuration: a single key that is not a valid Power Fx name stops every definition from loading.

  • Attributes — The parameters of MentorAction, MentorAgent, MentorTeam, TeamMember, MentorPage and MentorSkill, with their defaults and rules.
  • 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.
  • Production checklist — Settings to review before production: governance, safety check, rate limits, cost and routing, history, several instances, observability and outgoing data.