Novior.MentorAgent.Declarative
1.1.0
Prefix Reserved
dotnet add package Novior.MentorAgent.Declarative --version 1.1.0
NuGet\Install-Package Novior.MentorAgent.Declarative -Version 1.1.0
<PackageReference Include="Novior.MentorAgent.Declarative" Version="1.1.0" />
<PackageVersion Include="Novior.MentorAgent.Declarative" Version="1.1.0" />
<PackageReference Include="Novior.MentorAgent.Declarative" />
paket add Novior.MentorAgent.Declarative --version 1.1.0
#r "nuget: Novior.MentorAgent.Declarative, 1.1.0"
#:package Novior.MentorAgent.Declarative@1.1.0
#addin nuget:?package=Novior.MentorAgent.Declarative&version=1.1.0
#tool nuget:?package=Novior.MentorAgent.Declarative&version=1.1.0
Novior.MentorAgent.Declarative
New package IDs. From 1.0.0 the packages, their namespaces and their assemblies are
Novior.MentorAgent.*; up to 1.0.0-rc.15 they wereMentorAgent.*. See Migrating from the MentorAgent.* packages in the Novior.MentorAgent README.
Optional package. Install it only if you want to define specialist agents in YAML files instead of C# classes. Everything MentorAgent does works without it.
Define a Level-2 specialist in a text file, drop it next to your application, and the assistant can hand off to it — no new class, no [MentorAgent] attribute, no recompile of the agent's behaviour.
Built on the Agent Framework's declarative agent factory (Microsoft.Agents.AI.Declarative).
Package Family
| Package | Install when |
|---|---|
| Novior.MentorAgent | Blazor Server app |
| Novior.MentorAgent.Server | Web API / headless backend, or Blazor Auto server-side project |
| Novior.MentorAgent.Blazor | Blazor WASM / Blazor Auto client project |
| Novior.MentorAgent.Abstractions | Never directly — it arrives with any of the above |
| Novior.MentorAgent.Declarative ← you are here | You want YAML-defined agents. Add it alongside Novior.MentorAgent or Novior.MentorAgent.Server |
Table of Contents
- What it does
- Getting started
- The YAML format
- Tools: named, not defined
- Security — a definition file is code
- When to use YAML and when to use C#
- Loading definitions from somewhere else
- Configuration options
- How to test it
- Why a separate package
- Requirements
- Related Packages
- License
What it does
MentorAgent's three-level agent model has a coordinator (L1) that can hand off to specialists (L2). Normally a specialist is a C# class:
[MentorAgent(Name = "ShippingAgent", Description = "Answers questions about deliveries")]
public class ShippingAgent // register it: builder.Services.AddScoped<ShippingAgent>();
{
[MentorAction(Description = "Looks up a tracking number")] // tool name: get_tracking
public string GetTracking(int orderId) => /* … */;
}
This package adds a second way to declare the agent — its name, its instructions, its model settings and which tools it may use — as a file:
kind: Prompt
name: ShippingAgent
description: Answers questions about deliveries and shipping costs
instructions: |
You handle shipping questions only. Use the available tools to look up real orders;
never invent a tracking number. If the question is not about shipping, say so and stop.
model:
options:
temperature: 0.2
tools:
- kind: function
name: get_order_status
- kind: function
name: get_all_orders
Both kinds end up in the same handoff graph, so route_to_specialist reaches them identically and the user cannot tell which is which.
Getting started
Installation
dotnet add package Novior.MentorAgent.Declarative
Registration
using Novior.MentorAgent.Declarative; // AddMentorAgentDeclarative
using Novior.MentorAgent.Extensions; // AddMentorAgent
builder.Services.AddMentorAgentDeclarative(o => o.Directory = "Agents");
builder.Services.AddMentorAgent(o =>
{
o.ChatClient = chatClient;
o.AppName = "ShopFlow";
o.ScanAssemblies = [typeof(Program).Assembly];
});
Order does not matter — MentorAgent asks every registered agent source while it builds a coordinator. A coordinator is built lazily for each session scope (each Blazor circuit, each SignalR connection to Novior.MentorAgent.Server, each SSE request), on its first message, or once at startup when WarmUpAtStartup is on. So the definitions are read again for every new scope. An edited file reaches new sessions without a restart, existing sessions keep the agents they were built with, and the log lines shown under How to test it appear then, not when the application starts.
Make sure the files reach the output folder
A definition that is not copied is the most common way this feature appears not to work. In your .csproj:
<ItemGroup>
<Content Include="Agents\**\*.agent.yaml" CopyToOutputDirectory="PreserveNewest" />
</ItemGroup>
If the directory is missing you get a warning naming the resolved path, [MentorAgent:Declarative] Directory '…' does not exist., when a coordinator is built: on a session's first message, or at startup with WarmUpAtStartup. That message exists because the failure is otherwise silent.
The YAML format
The schema comes from the Agent Framework, not from MentorAgent. The fields that matter in practice:
| Field | Meaning |
|---|---|
kind |
Prompt for a prompt-based agent. Required |
name |
The specialist's name. This is what appears in handoff logs — keep it stable |
description |
What this specialist is for. The coordinator reads it to decide when to route here |
instructions |
The agent's system prompt, used exactly as written. Use a \| block for multiple lines. MentorAgent adds nothing to it, including the grounding rule its generated specialist prompts carry, so write your own: state only facts a tool returned; if nothing provides what is asked, say so, and never estimate or invent a number, a name, a date or a status. Without it, a specialist asked for a figure its tools cannot return may make one up |
model.options |
temperature, topP, and other per-agent model settings |
tools |
Names of tools this agent may call — each entry needs kind and name, see below |
outputSchema |
Optional JSON-schema-style shape for a typed answer |
description is worth care: it is the coordinator's only basis for choosing this agent over another. "Handles orders" competes badly with "Handles orders, shipping and returns for existing customers".
Two format details that cost an afternoon
Neither is in the Agent Framework guide, and both fail in a way that points somewhere else.
Every tools entry needs kind. The reader accepts codeInterpreter, fileSearch,
function, webSearch and mcp, but function is the only one MentorAgent keeps: it binds to a
tool of your application. Omit kind and loading fails with NotSupportedException — not a
validation message naming the line.
The other four are not names of your tools, and they do not add anything. The Agent Framework would turn them into provider-hosted tools (web search, code interpreter, file search, or a remote MCP server at whatever endpoint the file gives), configured by the file alone. MentorAgent removes them from the agent when it loads and logs a warning naming them:
[MentorAgent:Declarative] 'shipping.agent.yaml': removed web_search. A definition file can only use the application's own tools (`kind: function`); web search, code interpreter, file search and MCP servers are configured by the host, through MentorOptions.HostedTools and McpServers.
The agent still loads, with its function tools only. If those tools cannot be taken out, the
whole agent is skipped with an error instead. To give the assistant these capabilities, configure
them in the host (MentorOptions.HostedTools, McpServers), where the host's own controls apply.
Folded scalars (>) are not supported by this reader. Use |, or a single line. With > the
parse error reports the end of the file, so you will look everywhere except at the block that
caused it:
description: > # ✗ parse error, blamed on the last line of the file
Handles shipping and delivery.
description: | # ✓
Handles shipping and delivery.
description: Handles shipping. # ✓
Tools: named, not defined
A tools: entry names a tool; it does not create one. The name must match one of your application's Level-1 methods: a public instance method with [MentorAction] or [Description] on a class in ScanAssemblies that is registered in DI, including a [MentorSkill] class. That is the whole list the factory is given. The methods of a C# [MentorAgent] specialist, MCP client tools, [MentorTeam] tools and the built-in tools (navigation, memory, UI actions) are not in it, so naming one of them binds nothing real (see below).
tools:
- kind: function # required — see below
name: get_order_status # must match exactly; a typo gives the agent a declaration with nothing behind it
The tool name is the snake_case of the C# member, with a trailing Async dropped:
GetOrderStatusAsync() → get_order_status, matched exactly and case-sensitively. Get it wrong and
nothing tells you at load time. The agent does not simply go without the tool: for a name that matches
nothing, the Agent Framework creates a declaration-only function of that name and gives it to the
agent. The model sees it and may call it, but there is no code behind it, so the call never runs and
the specialist has no data to answer with. Check every name against your [MentorAction] /
[Description] methods.
This is the design point of the whole package. MentorAgent hands the factory the application's real tool list, already wrapped in its gate, so a YAML agent calling create_order still hits:
RequiredRoles— the role check, exactly as a C# specialist does- human approval — the confirmation banner, if the tool requires one
- action feedback, per-tool metrics and tracing
An agent defined in a file therefore has no capability your application did not already have, and no shortcut around the controls on it. That holds for the other kind values too: webSearch, codeInterpreter, fileSearch and mcp entries would add provider-hosted tools outside your list, so MentorAgent removes them with a warning (see Two format details that cost an afternoon).
Security — a definition file is code
Read this before pointing Directory anywhere.
A definition chooses the model, writes the system instructions, and names the tools the agent may call. Anyone who can write that file can rewrite the assistant's persona and widen which tools it reaches for. That makes it code, whatever its file extension says.
- Load only from deploy-time locations. An application directory or an embedded resource. Never an upload folder, never a user-writable path, never a path built from request input.
- Review definitions like source. Put them in version control and through the same review as a
.csfile. - The gate still holds. A file cannot invent a tool or bypass a role check — that is enforced, not advisory. A
kind: functionentry resolves only against your own, gated tools, and awebSearch,codeInterpreter,fileSearchormcpentry is removed with a warning, so a file cannot add web search, a code interpreter or an MCP server of its own choosing. But it can instruct the agent to try things, so the controls on your tools remain the thing that actually stops it.
The second point is the one people skip: YAML feels like configuration, and configuration feels safe to let more people edit.
When to use YAML and when to use C#
| YAML | C# [MentorAgent] |
|
|---|---|---|
| Change an agent's instructions | Edit a file | Recompile |
| New tool / new logic | Not possible — tools stay in C# | Where it belongs |
| Compile-time checking | None; a bad tool name is silent | Full |
| Who can author it | Anyone who can edit a reviewed file | Developers |
| Fits when | Wording and routing get tuned often | The agent has real behaviour |
A good rule: behaviour in C#, phrasing in YAML. If you find yourself wanting a loop or a branch in a definition file, that agent wants to be a class.
You can mix freely — both kinds coexist in the same handoff graph.
Loading definitions from somewhere else
Definitions do not have to be files. Pass them as strings for agents stored in a database, a configuration service, or a test:
builder.Services.AddMentorAgentDeclarative(o =>
{
o.Definitions.Add("""
kind: Prompt
name: FaqAgent
description: Answers frequently asked questions about the shop
instructions: Answer briefly, in the user's language. Say so when you do not know.
""");
});
To ship definitions inside the assembly, the safest deploy-time location, embed them and pass their text as inline definitions:
<ItemGroup>
<EmbeddedResource Include="Agents\**\*.agent.yaml" />
</ItemGroup>
builder.Services.AddMentorAgentDeclarative(o =>
{
var asm = typeof(Program).Assembly;
foreach (var name in asm.GetManifestResourceNames()
.Where(n => n.EndsWith(".agent.yaml", StringComparison.Ordinal))
.Order(StringComparer.Ordinal))
{
using var reader = new StreamReader(asm.GetManifestResourceStream(name)!);
o.Definitions.Add(reader.ReadToEnd());
}
});
For a fully custom source — one that hits your own store, or refreshes on a schedule — implement IMentorAgentSource from the Novior.MentorAgent package directly and register it. AddMentorAgentDeclarative is one implementation of that interface, not a privileged path:
using Novior.MentorAgent.Core; // IMentorAgentSource, MentorAgentSourceContext
using Microsoft.Agents.AI; // AIAgent
public sealed class DatabaseAgentSource : IMentorAgentSource
{
// Called once per coordinator build, i.e. once per session, not once per process.
// Cache here if reading your store is expensive.
public async Task<IReadOnlyList<AIAgent>> GetAgentsAsync(
MentorAgentSourceContext context, CancellationToken ct = default)
{
// context.ChatClient — your MentorOptions.ChatClient wrapped only in the token meter
// (not the coordinator's pipeline), so usage lands in the metrics
// context.Tools — the Level-1 tools, already gated
…
}
}
builder.Services.AddSingleton<IMentorAgentSource, DatabaseAgentSource>();
Configuration options
| Option | Type | Default | Description |
|---|---|---|---|
Directory |
string? |
null |
Folder to scan, absolute or relative to the app base directory. Deploy-time paths only |
SearchPattern |
string |
"*.agent.yaml" |
File pattern inside Directory |
Recursive |
bool |
false |
Scan subdirectories too |
Definitions |
IList<string> |
empty | YAML supplied inline, loaded in addition to Directory |
ConfigurationSection |
string? |
null |
Name of the configuration section the YAML may reference. null exposes nothing |
SearchPattern and Recursive — what the scan is allowed to reach
Both defaults are deliberately narrow, and widening them is a decision worth making on purpose rather than by accident:
builder.Services.AddMentorAgentDeclarative(o =>
{
o.Directory = "Agents";
// Default "*.agent.yaml", not "*.yaml". A deployment folder holds other YAML — a CI file,
// a Helm values file — and reading one of those as an agent definition would at best fail
// loudly. Widen it only if your definitions genuinely do not carry the suffix.
o.SearchPattern = "*.agent.yaml";
// Default false. A nested folder is exactly where a definition gets added without review,
// and a definition file is code: it picks the model, writes the instructions and names the
// callable tools. Turn it on when your layout needs it, not "just in case".
o.Recursive = true; // now Agents/support/*.agent.yaml is loaded too
});
Inline Definitions are loaded first (named inline[0], inline[1], … in the log), then files in a stable order (sorted by path), so two definitions declaring the same agent
name resolve identically on every machine rather than depending on the file system's enumeration
order. A missing Directory is warned about — almost always a CopyToOutputDirectory miss, where
the assistant otherwise starts fine and is simply missing a specialist nobody thinks to look for.
ConfigurationSection — exposing configuration to a definition
Default null: no configuration reaches the YAML at all, and the definitions are self-contained.
Set it to expose one section, so a definition can reference values instead of hardcoding them:
// appsettings.json
{
"AgentSettings": {
"SupportEmail": "help@contoso.com",
"MaxRefund": "250"
}
}
builder.Services.AddMentorAgentDeclarative(o =>
{
o.Directory = "Agents";
o.ConfigurationSection = "AgentSettings"; // only AgentSettings:* reaches the YAML
});
The values arrive as Power Fx variables, one per key, named by the key's full configuration path: AgentSettings:SupportEmail, AgentSettings:MaxRefund, plus one for the section AgentSettings itself. The factory enumerates the section with AsEnumerable(), which does not make paths relative.
How a definition references them is part of the Agent Framework's declarative schema, not something
MentorAgent defines, so check the framework's documentation for the expression syntax before
relying on it.
Name a section. Never hand over the whole IConfiguration. The factory loads whatever
configuration it is given into the Power Fx engine as variables — one per key, in its
constructor. A single key that Power Fx rejects as a name (in the version this package uses, an
empty or whitespace-only key) takes the entire factory down before any definition is read. The
exception does not say which key it was: Power Fx's message is the fixed text Invalid name: ${name}.
The placeholder is never filled in, so do not look for a key literally named ${name}. This is not
hypothetical: a key contributed by an unrelated configuration provider produced
ArgumentException: Invalid name: ${name}
and no agents loaded at all. Naming one section bounds the blast radius to keys you control.
MentorAgent catches that failure and logs the cause rather than letting it surface as a generic startup error, then returns no agents:
[MentorAgent:Declarative] The agent factory rejected the configuration exposed to YAML
(AgentSettings). Every key in it becomes a Power Fx variable and must be a valid identifier.
Narrow MentorDeclarativeOptions.ConfigurationSection, or leave it null.
If you see it, the fix is a narrower section — or null, which is the right setting unless you
actually need substitution.
How to test it
- Put
shipping.agent.yamlin anAgentsfolder, withCopyToOutputDirectory. - Start the app and send a first message (or set
WarmUpAtStartup = true, which builds a coordinator at startup). The log should then show, at Information level (the tool count is every Level-1 tool handed to the factory, not the number the file names):[MentorAgent:Declarative] Agent 'ShippingAgent' loaded from shipping.agent.yaml (12 tool(s) available to it). [MentorAgent] Handoff: declarative agent 'ShippingAgent' added to workflow. - Ask something in that agent's area — "where is order 1001?". The answer should come back through it.
- Negative check — a bad file does not take the app down. Break the YAML deliberately: you get
'shipping.agent.yaml' could not be loaded — skipped, and everything else still starts. - Negative check — the gate holds. Name a tool carrying
RequiredRolesin the YAML and ask the agent to use it while unauthenticated. It must be refused, the same way a C# specialist is, and no action taken.
Why a separate package
Microsoft.Agents.AI.Declarative brings the Power Fx interpreter (YAML expressions are Power Fx), the Agents object model in three assemblies, Microsoft.ML.Tokenizers and several more.
That is a fair price for file-based authoring and pure overhead for everyone else, so it stays out of the Novior.MentorAgent core package. Installing this one is an explicit decision to pay it.
The reference is Microsoft.Agents.AI.Declarative 1.18.0-rc1, on the same 1.18 line as Microsoft.Agents.AI 1.18.0 in the core package, so adding it does not move the rest of the library onto a different Agent Framework version. The declarative package is still a prerelease upstream.
Requirements
- .NET 10
Novior.MentorAgent(orNovior.MentorAgent.Server) configured with aChatClient— declarative agents need one, and are skipped with a warning without it- A provider supporting the model options you use in the definitions
Related Packages
| Package | Purpose |
|---|---|
| Novior.MentorAgent | Blazor Server — full AI assistant |
| Novior.MentorAgent.Server | Any ASP.NET Core app — headless AI backend |
| Novior.MentorAgent.Blazor | Blazor WASM — SignalR client |
| Novior.MentorAgent.Abstractions | Shared contracts and UI components |
License
Apache License 2.0 — the full text is in the LICENSE file inside this package, with the copyright notice in NOTICE. Versions up to and including 1.0.0-rc.14 were released under the MIT License, which still applies to them.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- Microsoft.Agents.AI.Declarative (>= 1.18.0-rc1)
- Novior.MentorAgent (= 1.1.0)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
1.1.0 - 2026-10-21
### Added
- **Novior.MentorAgent:** `MentorOptions.ConfirmationMessage` writes the text of the confirmation banner, in the
application's own words or language. It gets the tool, its description, the arguments as the banner shows them,
`Language` and the services of the conversation's scope. When it returns `null` the default text is shown; when it
throws or returns only white space, the default text and a warning (BUG-186).
- Every package carries its software bill of materials: SPDX 2.2, at `_manifest/spdx_2.2/manifest.spdx.json`
inside the `.nupkg`, with its SHA-256 beside it. It lists the package's files and the packages restored to build
it, build-time tools included.
- **Novior.MentorAgent:** `MentorActionAttribute.RequiresConfirmationDeclared` is public and read-only: whether the
attribute writes `RequiresConfirmation`, true or false. With `RequireConfirmationForWrites` on, an action that
declares nothing asks for confirmation and one that declares `RequiresConfirmation = false` does not, while
`RequiresConfirmation` reads false for both; a component outside the library can now tell them apart.
### Security
- **Novior.MentorAgent.Blazor:** BUG-173 (S2): the package depended on `Microsoft.AspNetCore.SignalR.Client`
10.0.3, whose `Microsoft.AspNetCore.SignalR.Common` is covered by advisory GHSA-334f-vvp4-7g6p
(CVE-2026-70159, high): a client using stateful reconnect can block a server's broadcasts. It now depends on
10.0.13, the patched release. With 1.0.0, add
`<PackageReference Include="Microsoft.AspNetCore.SignalR.Client" Version="10.0.13" />` to the client project.
MentorAgent turns stateful reconnect on neither in its hub nor in its client.
- **Novior.MentorAgent.Server:** the hub runs on the SignalR of the ASP.NET Core shared framework, not on a
package: a host is patched by the ASP.NET Core runtime 10.0.13 or later.
- A package with a high or critical advisory (NuGet audit NU1903, NU1904), direct or transitive, now fails the
Release build of the packages.
### Changed
- Every package is now checked at pack time against the public API of 1.0.0, so a 1.x release cannot break
code built against 1.0.0 by accident. An intended break is listed in this file as Breaking.
- **Novior.MentorAgent, .Server, .Blazor, .Abstractions, .Declarative:** NOTICE says that "MentorAgent" and its
logo are trademarks of Antonio Macrì and Emanuele Longo, to be assigned to Novior, not licensed under the Apache
License 2.0, and points to the trademark policy, TRADEMARKS.md, in the GitHub repository.
### Fixed
- **Novior.MentorAgent:** the README's *Migrating from the MentorAgent.\* packages* listed the build diagnostic
`MENTOR001` among what stays the same. That check, and its `MentorAgentSkipOpenAIVersionCheck` switch, were
removed in 1.0.0-rc.15: the core package has no build target, and there is no switch to keep (BUG-159, S3).
- **Novior.MentorAgent.Server:** three sentences of the README did not match the code. `IncludeWorkflowExceptionDetails`
puts the exception message of a failing handoff or group chat agent into the reply, not a stack trace.
`EnableSuggestions` and the widget options do take effect in a MAUI Blazor Hybrid app, which runs the Blazor
widget; only a MAUI app with its own native UI ignores them. And the `Authorization` header is missing from a
WebSocket or Server-Sent Events connection because a browser cannot send one, not because the protocol forbids
it; the Blazor README said the same and is corrected too (BUG-160, S4).
- **Novior.MentorAgent.Server:** the README's React example for image input did not compile: a `<textarea>` outside
any component and an `await` at the top level. It is now a component that keeps the attachments in its state and
sends them from an `async` handler, passing `null` when there are none (BUG-161, S3).
- **Novior.MentorAgent.Server:** the README said that `POST /mentor/cancel` answers `404` when Stop is pressed just as
the turn ends, and that it is not an error; its React, Angular and Vue components ignored it. The endpoint answers
`200` whenever the connection exists, with or without a turn running, and `404` only when the connection is closed
or unknown, which is the case a client must not hide. The README and the three components now say so (BUG-162, S3).
- **Novior.MentorAgent:** the option reference tables of the README and the Server README left out options that only
their own sections described: `SkillSources`, `SkillFilter`, `SkillsRefreshInterval`, `AutoApprovalRules` and
`MaxAutoApprovalIterations` in the README; `EnableRichResponses`, `IsReadOnlyAction`, `EnableOutputSafetyCheck`,
`RequireLookupForRecordQuestions`, `RequireActionForChangeRequests` and `McpCallerPrincipal` in the Server README.
Every public option of `MentorOptions` is now in a reference table of both, or among the Server README's options
not listed on purpose. The `[MentorAction]` tables mark `Description` as required, and the Server README's lists
`ReadOnly` (BUG-163, S4).
- **Novior.MentorAgent:** with a pre-built `Agent` (Path B), `MemoryAutoCapture` and a `ClassifierChatClient`, memory
had two writers: the post-turn extraction ran through the classifier, one call per message, and the `remember`
tool was sent too. The log and the documentation said the extraction does not run on Path B. The tool is now left
out whenever the extraction runs, and the build log names the writer: on Path B the extraction with a
`ClassifierChatClient`, the `remember` tool without one (BUG-174, S3).
- **Novior.MentorAgent:** a `[MentorTeam]` with `HandoffTo` was approved when `[TEAM:APPROVED]` appeared anywhere
in the discussion, so a member's "yes" followed by another's "no" still told the coordinator to execute the
actions. The team is now approved only if its final message carries `[TEAM:APPROVED]` and no member wrote
`[TEAM:REJECTED]`; a discussion that ends without a verdict is not approved. Without a `[TeamTerminationCondition]`
the discussion now ends at the first verdict, instead of running all `MaxIterations` rounds; a host's condition
still decides alone when to stop. The members' instructions keep `[TEAM:REJECTED]` for a definitive no and ask for
changes without a token (BUG-175, S3).
- **Novior.MentorAgent, .Server, .Blazor, .Abstractions, .Declarative:** the XML documentation that IntelliSense
shows no longer contradicts the code: anonymous callers share the memory key `"anonymous"` only with
`AnonymousIdentity = Shared`; an empty `AllowedTools` allows every tool, like `null` (the README says so too);
`MentorTheme.Custom` renders like `Default`; `IMentorAgent` is optional and validates nothing; the hub's events list
`Cards`, and `MapMentorAgentServer` lists `/mentor/session` (BUG-164, S4).
- **Novior.MentorAgent:** the README did not say that with `UseRag` the search runs on every message, before the
model is called, so its time (an embedding call, the search, a reranker) is added to every reply; nor that the
absence of a keyword pre-gate relies on `RagMinScore` being on the source's scale. It says both now, and so does
the comment in `RagContextProvider` (BUG-165, S4).
- **Novior.MentorAgent:** the XML documentation of `RequiresApproval` said the predicate is evaluated once per
tool while the coordinator is built. It is asked again on every call to a tool in Blocking mode and on every turn
the tool filter matches a tool, so a predicate that reads a file or a database pays for it each time. The
documentation now says when it is asked and that it must stay a cheap, pure function of the tool name
(BUG-176, S4).
- **Novior.MentorAgent:** the documentation of `ConfigureChatClientPipeline` said only that the pipeline goes
outermost. It is built once per coordinator, so once per circuit on Blazor Server and once per SignalR connection
on the hub: a limit placed there does not limit the process. And only the coordinator's own calls go through it:
the auxiliary calls — classifier, safety checks, out-of-scope refusal, memory extraction, the lookup and action
checks — use `ClassifierChatClient ?? ChatClient`, and specialists, team members and the handoff router use
`ChatClient`. The READMEs and the XML documentation now say both (BUG-166, S4).
- **Novior.MentorAgent:** the READMEs said that `navigate_to` waits for `SignalReady()` on a `HasUIActions` page
only in a Blazor Server circuit. It waits wherever the page runs in the same process as the turn, so in a MAUI
Blazor Hybrid app too; turns over the hub, SSE or A2A still do not wait. The READMEs now say so (BUG-171, S4).
- **Novior.MentorAgent:** on Path B (a pre-built `Agent`), `EnableSafetyCheck` and `EnableOutputSafetyCheck`
without a `ClassifierChatClient` now log a warning when the coordinator is built: neither check can run, so every
message and every reply goes through unchecked. Both checks run on `ClassifierChatClient`, or on `ChatClient` when
none is set, so on Path B a `ClassifierChatClient` turns them on; the README and the XML documentation said they
required a `ChatClient` (BUG-167, S3).
- **Novior.MentorAgent:** the README and the XML documentation of `HitlMode` said that `Native` costs one
model call more per approved action, and that `Blocking` is cheaper. Both modes make the same model calls:
`Native` adds one agent run per round of confirmations, and its reply arrives in two segments (BUG-168, S3).
- **Novior.MentorAgent.Blazor:** the README said twice that the server must enable `EnableOnboardingTour` too,
and the widget's log said so when the tour had no steps. `MapMentorAgentServer()` maps `GET /mentor/tour`
whatever the server's flag says: the client's flag alone decides whether the tour is shown. The README and the
log now say so (BUG-169, S3).
- **Novior.MentorAgent:** the README has an example of `RequiredRoles` with Windows groups (Negotiate, `DOMAIN\Group`)
and with Microsoft Entra ID (app roles, or the `groups` claim's ids). The XML documentation of `EnableObservability`
said the embedding generator is traced too, and the README "the chat client calls": only the coordinator's model
calls are traced, and both now say how to trace the rest (BUG-170, S4).
- **Novior.MentorAgent.Server:** the A2A agent card listed the actions of classes not registered in the DI container,
which no turn can run, and repeated a skill id when two actions share a tool name. It now lists only the actions a
turn can call, once per name, and leaves a name out when any action carrying it is gated (BUG-177, S3).
- **Novior.MentorAgent.Server:** the warning that lists the actions withheld from the MCP server was written on the
first MCP request, not at startup as the README says. `MapMentorAgentMcp()` now writes it when it maps the
endpoint; the tools are still built by the first request, and which of them a caller sees is still decided per
request (BUG-178, S3).
- **Novior.MentorAgent:** on Path B (a pre-built `Agent`) the `[MentorAgent]` specialists, the `[MentorTeam]` teams
and the `RemoteAgents` are not built, because they run on `options.ChatClient`, and nothing said so. A warning now
names each of them when the coordinator is built, and the remote agents' cards are no longer fetched on Path B,
where no turn could reach them (BUG-179, S3).
- **Novior.MentorAgent:** the two log lines that open and close the discovery were in Italian ("Discovery avviata",
"Discovery completata"). They now read "Discovery started on …" and "Discovery completed — …" (BUG-180, S4).
- **Novior.MentorAgent:** a team's discussion reached the coordinator as one run-on text ("… is solved.I agree"), so
it could not tell who said what. Each message is now a paragraph of its own, headed by its author. The team tool's
description gets a full stop before the handoff note when the team's `Description` has none (BUG-181, S4).
- **Novior.MentorAgent:** two methods carrying the same tool name were reported only with tool filtering on. A model
calls a tool by name, so an agent offered both reaches only one of them, and up to 1.0.0 one method's attributes
decided the other's call (BUG-188). A warning now names every shared tool name and its methods when the actions
are first read, with tool filtering on or off (BUG-182, S4).
- **Novior.MentorAgent:** the signed-in user's roles told to the model came from `ClaimTypes.Role` claims only, while
the gate decides with `IsInRole`, which reads each identity's own `RoleClaimType`. With roles in a `roles` claim —
Microsoft Entra ID, or JwtBearer with `MapInboundClaims = false` — an action passed the gate while the model was
told no role. The model is now told the roles `IsInRole` answers to, from every identity of the user; a Windows
identity's group SIDs are left out (BUG-183, S3).
- **Novior.MentorAgent:** the RAG citation chips knew one number per marker, "[1] [2]". An answer that cited "[1, 2]"
or "[1-2]", as models also write, showed no chip at all. A list or a range now raises 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 (BUG-184, S3).
- **Novior.MentorAgent:** the instruction that tells the model how to cite the RAG documents was in English whatever
`Language` said. It is now written in the configured language, in each of the ten, with the same `[1], [2]`
markers the citation chips read (BUG-185, S3).
- **Novior.MentorAgent:** the confirmation banner showed each argument whole, control and bidirectional characters
included, in the order of the model's JSON. Each value is now cleaned and cut at 200 characters, and the arguments
follow the order of the method's parameters (BUG-186, S2).
- **Novior.MentorAgent:** with `HitlMode.Native`, a confirmation that a Stop or a failure left open was answered
from memory only. A snapshot saved in that state and restored in another scope, or after a reset, still held the
open request, and every later turn failed as in BUG-116 (a known issue since 1.0.0-rc.15). The open requests and
their answers now travel with the snapshot, and the first turn after the restore answers them (BUG-187, S3).
- **Novior.MentorAgent:** two methods carrying one tool name shared one set of rules. The roles, the confirmation,
the banner's text and the page to open came from the first method found with the name, level 1 first, so a
level-1 method without roles opened a specialist's role-gated action of the same name to every user. The gate now
applies the rules of the method the call runs, at every level and in both HITL modes (BUG-188, S2).
- **Novior.MentorAgent, .Abstractions, .Blazor:** the README taught `PageContext.Clear()` in a page's `Dispose()`.
Blazor disposes the previous page after it has initialized the next one, so from one page with UI actions to
another, the previous page's `Clear()` wiped the actions the next page had just registered, and the assistant
answered that the action did not exist. `IMentorPageContext.Clear(pageName)` clears only what that page
registered, or everything when it is still the current page: call it in `Dispose()` with the page's name.
`Clear()` still clears everything (BUG-192, S2).
- **Novior.MentorAgent.Abstractions:** in a table inside an assistant bubble, a number stood one digit per line. The
bubble lets a word break anywhere, so the table's columns could shrink to one character. The cells now break
between words only, a cell holding only a number, an amount, a date or a time stays on one line, and a table wider
than the bubble scrolls inside it instead of widening the conversation (BUG-193, S4).
- **Novior.MentorAgent:** `navigate_to` to a page with `HasUIActions` could return before the new page had registered
its actions. The page being left had called `SignalReady()` when it opened, and that signal ended the wait at once.
`navigate_to` now takes it back before asking for the navigation, so it waits for the new page's `SignalReady()`;
to the page already shown it does not wait, since Blazor does not initialize that page again (BUG-194, S3).
The full history is in CHANGELOG.md, inside this package.