MentorAgent.Declarative 1.0.0-rc.10

This is a prerelease version of MentorAgent.Declarative.
dotnet add package MentorAgent.Declarative --version 1.0.0-rc.10
                    
NuGet\Install-Package MentorAgent.Declarative -Version 1.0.0-rc.10
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="MentorAgent.Declarative" Version="1.0.0-rc.10" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="MentorAgent.Declarative" Version="1.0.0-rc.10" />
                    
Directory.Packages.props
<PackageReference Include="MentorAgent.Declarative" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add MentorAgent.Declarative --version 1.0.0-rc.10
                    
#r "nuget: MentorAgent.Declarative, 1.0.0-rc.10"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package MentorAgent.Declarative@1.0.0-rc.10
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=MentorAgent.Declarative&version=1.0.0-rc.10&prerelease
                    
Install as a Cake Addin
#tool nuget:?package=MentorAgent.Declarative&version=1.0.0-rc.10&prerelease
                    
Install as a Cake Tool

MentorAgent.Declarative

Preview Release — MentorAgent is currently in public preview. APIs may change before the stable release.

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
MentorAgent Blazor Server app
MentorAgent.Server Web API / headless backend, or Blazor Auto server-side project
MentorAgent.Blazor Blazor WASM / Blazor Auto client project
MentorAgent.Abstractions Never directly — it arrives with any of the above
MentorAgent.Declarative ← you are here You want YAML-defined agents. Add it alongside MentorAgent or MentorAgent.Server

Table of Contents


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("ShippingAgent", "Answers questions about deliveries")]
public class ShippingAgent
{
    [MentorAction("get_tracking", "Looks up a tracking number")]
    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 MentorAgent.Declarative

Registration

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 the coordinator, which happens on the first message.

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 at startup you get a warning naming the resolved path — 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. Use a \| block for multiple lines
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. Accepted values are codeInterpreter, fileSearch, function, webSearch and mcp; function is the one that binds to a tool of your application. Omit it and loading fails with NotSupportedException — not a validation message naming the line.

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 a tool your application already exposes — a [MentorAction] method, or any other Level-1 tool.

tools:
  - kind: function              # required — see below
    name: get_order_status      # must exist; a typo simply means the agent never gets that tool

The tool name is the snake_case of the C# member, with a trailing Async dropped: GetOrderStatusAsync()get_order_status. Get it wrong and nothing tells you — the agent simply starts without that tool and then improvises an answer it has no data for.

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.


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 .cs file.
  • The gate still holds. A file cannot invent a tool or bypass a role check — that is enforced, not advisory. 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.
        """);
});

For a fully custom source — one that hits your own store, or refreshes on a schedule — implement IMentorAgentSource from the MentorAgent package directly and register it. AddMentorAgentDeclarative is one implementation of that interface, not a privileged path:

public sealed class DatabaseAgentSource : IMentorAgentSource
{
    public async Task<IReadOnlyList<AIAgent>> GetAgentsAsync(
        MentorAgentSourceContext context, CancellationToken ct = default)
    {
        // context.ChatClient — the coordinator's client, 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
});

Files are loaded 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 named after the keys — SupportEmail, MaxRefund. 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. Every key must therefore be a valid Power Fx identifier, and a single one that is not takes the entire factory down before any definition is read. This is not hypothetical: a key literally named ${name}, 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

  1. Put shipping.agent.yaml in an Agents folder, with CopyToOutputDirectory.
  2. Start the app. The log should show, at Information level:
    [MentorAgent:Declarative] Agent 'ShippingAgent' loaded from shipping.agent.yaml (12 tool(s) available to it).
    [MentorAgent] Handoff: declarative agent 'ShippingAgent' added to workflow.
    
  3. Ask something in that agent's area — "where is order 1001?". The answer should come back through it.
  4. 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.
  5. Negative check — the gate holds. Name a tool carrying RequiredRoles in 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 MentorAgent core package. Installing this one is an explicit decision to pay it.

The reference is pinned to the 1.6.1 line, matching Microsoft.Agents.AI in the core package, so adding it does not move the rest of the library onto a different Agent Framework version.


Requirements

  • .NET 10
  • MentorAgent (or MentorAgent.Server) configured with a ChatClient — declarative agents need one, and are skipped with a warning without it
  • A provider supporting the model options you use in the definitions

Package Purpose
MentorAgent Blazor Server — full AI assistant
MentorAgent.Server Any ASP.NET Core app — headless AI backend
MentorAgent.Blazor Blazor WASM — SignalR client
MentorAgent.Abstractions Shared contracts and UI components

License

MIT — the full text ships in the repository's LICENSE file.

Product 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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.0.0-rc.10 34 9/19/2026
1.0.0-rc.9 34 9/19/2026
1.0.0-rc.8 41 9/18/2026
1.0.0-rc.7 39 9/16/2026
1.0.0-rc.6 56 9/14/2026
1.0.0-rc.5 55 9/13/2026
1.0.0-rc.4 62 9/9/2026
1.0.0-rc.3 71 9/4/2026
1.0.0-rc.2 76 8/24/2026
1.0.0-rc.1 76 8/19/2026
1.0.0-preview.5 71 8/12/2026

1.0.0-rc.10

Found by re-driving the release matrix's open cells on the published rc.9. BUG-080 holds: in the topology that looped (two hosts, each the other's remote agent) a delegated request is one task on the peer and none bounced back, and A2A context is per scope on every caller. With remote answers finally arriving, they could be compared with the data - and several were not true. No API change.

=== FIXED
- BUG-081 (S2): a turn SERVED over A2A stated figures no tool had returned. "Quanti clienti Premium ha?" asked through a caller came back as 18; put straight to the peer's /a2a endpoint, as 3, 4, 11, 16, 6, 6, 8 - there are 2. Tools were offered every time and none was called, while the same server on the same question over SSE called search_customers and said 2. The only difference was the line rc.8 added to an A2A turn's context - "carry the request out with your tools and reply with the result itself" - obeyed in the wrong order: a result at once. (BUG-079's "87 customers" was very likely this.) The notice is now a procedure: FIRST call the tool; every number, name, date or status comes from a tool result of THIS turn; if no tool has it, reply only that the application cannot provide it. A rule in a prompt lowers a rate and does not remove a behaviour - measured live, the notice alone gave 3 grounded answers in 5 - so there is a structural backstop, each step of it added because the one before was measured and was not enough. When an A2A-served turn ends without the coordinator reaching for ANY tool, the handler discards the reply and puts the same request once more, with the turn's context saying why and with a tool call REQUIRED on that attempt's first model call. If the second attempt too is tool-less, the classifier model is asked whether the reply states a value of the application's live records (a model, not a pattern: "9 clienti Premium" and "reso entro 30 giorni" both contain a number); on anything but a clear no the caller receives "NOT GROUNDED: ... do not present a figure" instead of the value. Two attempts, never three; the extra turn is paid only by tool-less A2A requests; a tool-less reply that states no data is returned as it is. Measured on the package-built ApiServer, the hardest host: 15 of 15 correct, none invented, none withheld (rc.9: 18, 3, 4 for a true 2).
- BUG-081, second half: the caller's own name for the peer travels inside the request ("quanti prodotti ha ShopFlowRemote?") and nothing told the peer that name means ITSELF - it answered "I have no access to ShopFlowRemote" 3 times in 6, without calling a tool. The notice now says so, and a MentorAgent caller sends its name for the peer as A2A message metadata (mentoragent.addressedAs), which the peer reads into the notice. The value arrives from another machine and goes into a prompt: only one token of letters, digits and - _ . (64 characters at most) is accepted; anything else is dropped whole.

=== VERIFIED ON THE PUBLISHED rc.9 (no change)
- BUG-080: API and React up together, remote turns driven from every column - one "Task received" per turn on the peer, each followed by "This turn arrived over A2A: remote agent(s) ... are not offered to it", zero tasks bounced. A2A context per scope closed on the React client and the WebAssembly client (one context across a connection's turns, a different one for a second connection).
- BUG-073, residual: the cold first turn of a fresh Chat Completions process delegated this time (route_to_specialist -> OrderAgent, real data).

=== STILL OPEN, UNCHANGED
- BUG-074 (S3): MentorshipLevel.Proactive offers actions the application does not have. Measured, recorded, a product-voice decision.

1,667 tests green, build 0 warnings / 0 errors.

1.0.0-rc.9

Found by re-driving the release matrix's open cells on the published rc.8 - the first published build on which an A2A round trip completes (BUG-078 had kept every one from finishing). T6/T7 closed on every host: route_to_specialist reaches OrderAgent and the declarative ShippingAgent, with real data, and the router's call is metered. The first remote delegation ever driven between the PUBLISHED samples found what was behind it. No API change.

=== FIXED
- BUG-080 (S2): a request delegated over A2A was delegated ONWARD by the peer, and two hosts that peer each other never stopped. Live: "Delega a ShopFlowRemote: quanti prodotti a catalogo?" on one sample produced five tasks on its peer and four on the peer's peer (each ~5,500 input tokens), 141 seconds, no answer - until a server was stopped by hand. Two causes. (1) rc.8 made route_to_specialist prefix the workflow's input with "[Specialist requested: NAME]" for the LOCAL router (BUG-076), and the A2A client sent the last user message verbatim: the marker crossed the wire and the peer's coordinator read it as its own order - every sample calls its remote agent "ShopFlowRemote". The marker now has one writer and one remover (SpecialistMarker), and the A2A client strips it on both the streaming and the non-streaming path. (2) Structural: a turn that ARRIVED over A2A was offered the host's remote agents like any other. It no longer is - they are not built for that scope and are absent from the coordinator's prompt, the router's prompt and the description of route_to_specialist; one Debug line names what was withheld. The host's own specialists, declarative agents, teams and tools still serve the request. Consequence, documented in the README: chaining A -> B -> C through a MentorAgent host is not supported in 1.0 (before rc.8 no chain could complete a single hop). Verified live on the fixed source in the mirror topology that looped (two hosts, each the other's "ShopFlowRemote"): one task per turn on the peer, zero bounced back, "8 prodotti a catalogo" in 32 s.

=== VERIFIED ON THE PUBLISHED rc.8 (no change)
- BUG-076/077: three hosts' logs show route_to_specialist returned: main_coordinator -> OrderAgent[FunctionCall] -> OrderAgent[FunctionResult] -> OrderAgent[Text]; the router's call costs ~400 input tokens and is metered.
- BUG-078: both peers' cards advertise JSONRPC at an absolute URL; tasks are received and logged with their context ids. A2A context per scope holds: one context across a circuit's turns, a different one for a second circuit (Blazor Server and MAUI callers).
- BUG-075, second pass: "Ricorda che il mio codice privato e' ..." and asking for it back are both SAFE / IN scope; no UNSAFE verdict anywhere in the process log.
- BUG-079: with the peer unreachable or looping, the user read "il sistema remoto non ha fornito il numero" - no invented figure.
- BUG-073, residual rate: on the Chat Completions host the cold first turn of a process narrated a handoff without calling the tool, once in five. The fix lowers a rate; it does not remove a behaviour.

=== SAMPLES (not part of the packages)
- MentorAgentServer's remote agent can be renamed and re-pointed from the command line (--A2ARemote:Name / --A2ARemote:Url), so the source pair can mirror the published topology (two hosts, each the other's remote, same name). The source pair that verified rc.8 could not show BUG-080 because it did not.

=== STILL OPEN, UNCHANGED
- BUG-074 (S3): MentorshipLevel.Proactive offers actions the application does not have. Measured, recorded, a product-voice decision.

1,654 tests green, build 0 warnings / 0 errors.

1.0.0-rc.8

Found by re-driving the release matrix's open cells on the published rc.7 - the first published build in which route_to_specialist was really called (BUG-073 had kept everything behind that call out of sight). The first delegated turns ever driven live found what was behind it. One additive API change: route_to_specialist takes an optional second argument.

=== FIXED
- BUG-078 (S2): the A2A SERVER had never completed a round trip with any client - three independent faults, each hidden behind the one before, found the first time a delegation reached a live peer. (1) The agent card advertised protocolBinding "HTTP+JSON" while MapMentorAgentA2A maps the SDK's JSON-RPC endpoint, so a client that believed the card got HTTP 404 - every client, MentorAgent's own included. It now says JSONRPC. (2) The handler changed a task's status without submitting the task first: "Agent handler did not produce any response events". (3) It disposed a plain DI scope that had resolved MentorOrchestrator, which is IAsyncDisposable only, so the container threw AFTER the turn had run and been billed: "An internal error occurred". And on the CLIENT side (4): inside the handoff workflow an agent runs streaming, and A2AChatClient read only Message events - a MentorAgent server answers with a task whose answer rides on the final status update, so MentorAgent's client could not hear MentorAgent's server. It now reads a final Task/StatusUpdate too (a Working status is a progress note and is skipped). Pinned by the SDK's own A2AClientFactory + A2ACardResolver completing a round trip against the in-memory server, given nothing but the card.
- A turn that arrives over A2A now tells the model so, in the per-request context: no human is present, carry the request out and reply with the result - no follow-up questions, no offers, no announcing instead of doing. Found on that first round trip: the peer answered "Sto per estrarre il numero dei clienti... Vuoi anche un elenco?" to a caller that is a program. The hub and SSE are unchanged - they carry people.
- BUG-076 (S2): the agent at the entry of the handoff workflow - the router route_to_specialist talks to first - was built from the coordinator's ENTIRE prompt. It has no application tools, only the workflow's handoff functions, so about 5,000 input tokens were paid a second time on every delegated turn by an agent that could use none of it. And since rc.7 that prompt says "to delegate, call route_to_specialist": the router is INSIDE that tool. Told to call a function it was not given, it narrated ("Sto chiedendo allo specialista spedizioni...", "Sto incaricando l'agente remoto... appena risponde ti informero'"), the workflow ended on the narration, and the tool handed it to the coordinator - inside a serialised AgentResponse envelope - as the specialist's answer. Nothing reached the remote peer; a request addressed to OrderAgent was answered by ShippingAgent. Now: the router has a router's prompt (who the specialists are, call exactly one handoff function, never answer, never say you are forwarding; about 400 tokens); the tool returns what the specialists SAID, as text; when nobody but the router spoke the result is "NOT DELEGATED ... do not say it was forwarded" and the operator gets a warning; a specialist that RAISED is reported as "DELEGATION FAILED" with the error in the warning, not as a refusal (that line is how BUG-078 was found); and route_to_specialist has an optional `specialist` argument, because a model asked for "the user's request" tidies it and the name is the first thing to go ("Delega a ShopFlowRemote: quanti prodotti ha?" arrived as "Quanti prodotti ci sono nel catalogo ShopFlow?").
- BUG-077 (S3): every model call made UNDER the coordinator - the router, the Level 2 specialists, the Level 3 team members, and the agents an IMentorAgentSource builds - ran on the raw host client and was never metered. Live, the session counter went from 5 calls to 6 across a delegated turn that made at least three more in between; a 30-second team deliberation showed up as the coordinator's two calls. BUG-033's shape, on the turns that cost the most. They now run on the host's ChatClient inside the metering wrapper and nothing else. The new lines showed one more thing at once: "model: , in: 404" - Azure's first streamed chunk carries an EMPTY model id, not a null one, and the wrapper kept it. It now takes the first non-empty id and falls back to the client's own deployment, which also gives a ClassifierChatClient on its own deployment its own dashboard row.

- BUG-079 (S3): a specialist had no rule against inventing data. On that same first round trip the peer's customer specialist - whose tools can look one customer up but cannot count them - answered "87 registered customers", in a table; the instance has 5, and the figure reached the user through two coordinators with no reason to doubt it. The GENERATED specialist prompt and every team member's now carry a grounding rule (state only facts a tool returned; if nothing provides what is asked, say so - never estimate or invent a number, a name, a date or a status). Custom [MentorAgent(Instructions = ...)] stay exactly as written, by design and by test: both READMEs now say a custom prompt replaces the rule too and should carry its own.
- BUG-075, second pass: the rc.7 fix did not hold. On the published rc.7 "Ricorda che il mio codice privato e' ZULU-2200" was still classified UNSAFE, and so was asking for it back. Naming memory as a capability does not touch the rule the model was applying - "extract credentials ... or other infrastructure secrets" - with nothing saying WHOSE secrets. On hosts with memory on, the classifier is now told: the secrets that rule protects are the system's; what users tell the assistant about themselves is theirs to give and to ask back. Verified live with the exact two messages (both SAFE), injection control still refused. What is NOT changed: past the classifier, the model itself may decline to keep something its user calls private, and says why - a defensible answer from a plain-text memory, recorded rather than overridden.

=== SAMPLES (not part of the packages)
- The remote A2A agent's description now says what it answers about. "Headless instance, for delegating A2A operations" gave a router nothing to route on.
- The sample specialists' custom instructions carry the grounding rule.
- The source-referenced sample points its A2A peer at the source-referenced headless server (5169): source with source, packages with packages.

=== STILL OPEN, UNCHANGED
- BUG-074 (S3): MentorshipLevel.Proactive offers actions the application does not have. Measured, recorded, a product-voice decision.

1,645 tests green, build 0 warnings / 0 errors.

1.0.0-rc.7

Found by the release matrix: the same ~70 checks driven on all eight sample applications against the published rc.6 packages, signed in and anonymous, on Blazor Server (cookie), WebAssembly and React (JWT), Blazor Auto, .NET MAUI (over CDP) and the two source-referenced apps. 584 cells, every option of MentorOptions and MentorAgentBlazorOptions with a verdict. No API change.

=== FIXED
- BUG-073 (S2): the coordinator was told it COULD delegate to its specialists and never told how - the tool name route_to_specialist appeared nowhere in the prompt and the tool's own description named no agent. Told a specialist existed (BUG-069), the model narrated the handoff ("inoltro subito la domanda allo ShippingAgent, attendo la sua risposta") and ended the turn without calling anything, three times, once under an explicit order. The capabilities block now says how to delegate and forbids announcing an unperformed handoff; the tool's description names every specialist it reaches, attribute-declared, source-supplied and remote.
- BUG-075 (S3): "Ricorda che il mio codice privato e' ZULU-2200" was classified UNSAFE on a host with memory on. Remembering a fact the user asks to keep is a configured capability and is now named to the classifier, the way MCP servers and image input already were (BUG-067).
- BUG-072 (S3): a signed-in user whose AuthenticationStateProvider threw was silently keyed as anonymous - and since rc.5, as a different anonymous in every circuit, so the same user would have had a different memory in every tab with nothing in the log. The fallback stays (fail closed); it is now said once per session, without a stack trace per visitor. The live check on a real cookie principal shows the catch does not fire.
- BUG-068 addendum: one collaborating builder was still announcing per circuit at Information ("Remote agent 'X' connected from ..."). Routed through the same quiet logger; the general test now includes a remote agent.

=== SAMPLES (not part of the packages, recorded for whoever reads them)
- The three samples with authentication expose GET /account/dev-login?as=admin|manager|user in Development only (404 in Production, verified), so the authenticated rows of the matrix can be driven by a script; the WebAssembly and React clients accept ?as= on their login route for the same reason.
- The Blazor Server sample binds RefuseOutOfScope, WarmUpAtStartup, CompactionMaxTurns and RateLimitPerUser from configuration, so command-line overrides actually reach them.
- The samples point their A2A peer at a live server (the headless sample) instead of a port nobody listened on.

=== MEASURED, NOT CHANGED
- BUG-074 (S3, open): MentorshipLevel.Proactive still offers actions the application does not have - 3 in 5 turns ("esportare l'elenco", "cercare con il nome"). The rule is in the prompt; a structural fix (offers must name a listed tool or page) is a product-voice decision, recorded rather than taken.
- A compaction pass writes no log line; it is pinned in-process (CompactionTests). A Debug line when a pass runs would make it observable live.

1,623 tests green, build 0 warnings / 0 errors.

1.0.0-rc.6

Four defects found by running the published rc.5 packages against the sample applications - the pass rc.5's own notes implied but had not yet been done. No API change: every fix restores behaviour rc.5 already claimed.

=== FIXED
- BUG-068 (S3): the configuration summary was NOT written once per process, as rc.5's notes said it was. A second browser circuit still reprinted seven Information lines - the skills catalogue, the external agents, the hosted image model, the Azure image-header note, the hosted MCP server, the hosted tool list and the declarative handoff. The sentinel was claimed halfway through the build, after everything above it had already announced itself, and three collaborating builders never consulted it at all. It is now claimed first, and a later build demotes Information to Debug instead of discarding it, so an operator who turns Debug on to investigate one circuit can still see what it was built with. Degradation warnings stay per session, unchanged.
- BUG-069 (S2): an agent supplied by an IMentorAgentSource - a declarative YAML specialist, or a host's own - joined the handoff graph and was never named in the coordinator's instructions. route_to_specialist names no agent either, so nothing the model could see said the specialist existed: asked about it, the assistant answered that there is no such agent, while the log recorded it being added to the workflow. Source-supplied agents are now listed with their descriptions beside the attribute-declared ones.
- BUG-070 (S4): every skill on the published A2A card carried a flattened name ("Getallordersforanalysis"). The friendly-name helper splits on underscores and was being handed the PascalCase method name. That card is the one artefact whose entire audience is another machine's directory.
- BUG-071 (S4): a turn whose input safety check met the first failure printed two stack traces instead of one - the turn's fault log was reset after that check, so its cause was recorded and immediately discarded. The reset now happens before every early return.

1,616 tests green, build 0 warnings / 0 errors.

1.0.0-rc.5

Latency, cost per visitor, and the answers themselves. Measured on a live Blazor Server host before and after: time to the first character on the same question fell from 2.6-3.0 s to 1.6-2.0 s, and a shared MCP server now runs one child process for the whole application instead of one per browser circuit.

=== NEW - options
- ClassifierChatClient: the client MentorAgent uses for its own one-word decisions (input safety, hosted-tool scope, post-turn fact extraction). Point it at a small deployment: those calls sit in front of the reply and were measured at 707-2091 ms on gpt-4.1 for two tokens of answer. Defaults to ChatClient, and is metered under its own model id.
- AnonymousIdentity (PerSession by default): who a signed-out visitor is, for memory and for the rate-limit counter. Every unauthenticated session used to share the literal key "anonymous", so one visitor's facts were recalled for the next and one visitor could spend everybody's allowance. BREAKING for single-user hosts that want the old behaviour: set AnonymousIdentity = Shared (MAUI, desktop, kiosk - it is what makes memory survive a restart).
- RefuseOutOfScope (off by default): refuse a message that is not about this application, using the scope verdict the classifier already produces. Free when EnableSafetyCheck is on, because both questions now travel in one call.
- WarmUpAtStartup (off by default): build one coordinator when the application starts, so the first visitor does not pay for the catalogue embeddings, the shared MCP sessions and the remote agent cards.
- MentorMcpServer.Shared (null by default): one connection per process, or one per session. Unset decides by transport - a server reached over ServerUrl is shared, a server started from Command is not, because a local child process can hold state for the person who started it.
- MapMentorAgentMcp / MapMentorAgentA2A take a configure callback, so the host can finally apply RequireAuthorization() to them. Both endpoints discarded their convention builders before this.
- IMentorQueryEmbedding: the current message's embedding, computed once per turn. Inject it in your IMentorRagSource instead of embedding the query a second time.

=== FASTER
- One embedding per text instead of one per consumer. Routing, tool filtering and memory relevance were each embedding the same sentence - two to four network round trips per turn - and the tool catalogue plus the routing exemplars were re-embedded once per DI scope, i.e. once per browser circuit.
- One classifier call instead of two: the input safety check and the hosted-tool scope check read the same message and now ask both questions in the same call.
- The composer is handed back when the answer is complete, not after the post-turn fact extraction (a second model call, 544-767 ms measured).
- Shared MCP sessions and a process-wide agent-card cache with a failure backoff. Two browser tabs used to mean eight node processes and 580 MB.

=== FIXED
- BUG-062 (S3): text written before a tool call was glued to the text after it ("...i dati.Il catalogo"), and a Markdown table opening the second response stopped being a table.
- BUG-063 (S3): in HitlMode.Native a user without the required role was shown the confirmation banner and refused only after approving it. The role gate now runs first, as it always did in Blocking mode.
- BUG-064 (S1): two anonymous visitors of the same site shared one memory. Now isolated per session by default, with a warning when a host opts back into the shared bucket and a second session reaches it.
- BUG-065 (S4): the RAG "wrong scale" warning fired for thresholds on the source's own scale, once per visitor.
- BUG-066 (S3): the hosted-tool scope classifier was built on the raw ChatClient, so its billed call appeared in no dashboard.
- BUG-067 (S3): the safety classifier was never told what the application is equipped with, and refused three legitimate messages out of forty on a host with an MCP filesystem server and image input. It is now told, and a refused message is logged by fingerprint rather than by its text.

=== CHANGED
- RAG citation chips show the documents the answer actually cited. Documents are numbered in the prompt and the model is asked to cite them; if it cites none, no chips are shown.
- MentorshipLevel.Proactive asks for one sentence before acting instead of a plan, and may only offer next steps the application can actually perform.
- The A2A agent card publishes the application's ungated actions as skills, its input/output modes and its streaming capability. It carried a name and a description and nothing else.
- The coordinator's configuration summary is logged once per process instead of once per session, and one provider failure produces one stack trace instead of one per component that met it.

1,611 tests green, build 0 warnings / 0 errors.
1.0.0-rc.4

No change in this package. Version aligned with MentorAgent 1.0.0-rc.4, which fixes one S3 in the provider error classifier - see that package's notes. The five packages ship as a set and are meant to be upgraded together.

1.0.0-rc.3

No changes to this package's own surface: the YAML dialect, the file scanning rules and the tool-resolution guarantees are exactly as in 1.0.0-rc.2.

=== Why the version moved ==================================================
- All five packages ship together and share one version. rc.3 closes six findings in the core, Blazor and Abstractions packages, including two S1s - see those packages' notes.
- If you reference Microsoft.Extensions.AI.OpenAI or Microsoft.Agents.AI.Foundry directly, note that the core package now FAILS THE BUILD (error MENTOR001) when OpenAI resolves to 2.11.0 or later, rather than letting the application die at startup.

=== Unchanged, and worth restating ========================================
- A tool named in YAML still resolves to a tool the application already registered, already wrapped in MentorAgent's gate, so RequiredRoles and human approval keep working inside a declarative agent's own function-calling loop.
- ConfigurationSection still exposes one named section and nothing else. Null means expose nothing, never expose everything.
- A definition file is code. Load it only from deploy-time locations.