# MentorAgent 1.2.0 release notes

> What MentorAgent 1.2.0 adds and fixes: only permitted actions reach the model, your own policies, who confirmed, the AI notice, and how to upgrade.

- URL: https://docs.mentoragent.net/release-notes/1-2-0
- Verified with: MentorAgent 1.2.0
- Last updated: 2026-11-07
- Packages: Novior.MentorAgent, Novior.MentorAgent.Abstractions, Novior.MentorAgent.Blazor, Novior.MentorAgent.Declarative, Novior.MentorAgent.Server

MentorAgent 1.2.0 was published on NuGet on 27 October 2026 (UTC). It is a minor version: it adds features and fixes defects. It has one intended break, in how a tool result becomes a generative card: see [Breaking changes](https://docs.mentoragent.net/release-notes/1-2-0#breaking-changes).

| Package | Version |
|---|---|
| [Novior.MentorAgent](https://www.nuget.org/packages/Novior.MentorAgent/1.2.0) | 1.2.0 |
| [Novior.MentorAgent.Abstractions](https://www.nuget.org/packages/Novior.MentorAgent.Abstractions/1.2.0) | 1.2.0 |
| [Novior.MentorAgent.Blazor](https://www.nuget.org/packages/Novior.MentorAgent.Blazor/1.2.0) | 1.2.0 |
| [Novior.MentorAgent.Declarative](https://www.nuget.org/packages/Novior.MentorAgent.Declarative/1.2.0) | 1.2.0 |
| [Novior.MentorAgent.Server](https://www.nuget.org/packages/Novior.MentorAgent.Server/1.2.0) | 1.2.0 |

The five packages require each other at exactly this version: upgrade them together. MentorAgent 1.2.0 runs on .NET 10. To upgrade, see [Upgrade from 1.1.0](https://docs.mentoragent.net/release-notes/1-2-0#upgrade-from-110).

## Highlights

- **[The model sees only the actions the user may run](https://docs.mentoragent.net/release-notes/1-2-0#the-model-is-offered-only-what-the-user-may-run).** An action that needs a role the user lacks is no longer offered, so the agent no longer announces it and then refuses it.
- **[Your code can decide too: action policies](https://docs.mentoragent.net/release-notes/1-2-0#action-policies).** A permission per company or per record, or a rule on the arguments, applied on every surface.
- **[Who confirmed, and every decision of the gate](https://docs.mentoragent.net/release-notes/1-2-0#who-confirmed-and-every-decision-of-the-gate).** A ledger of your writes can record who approved each one, and why a call was refused.
- **[A notice and a mark for AI-generated replies](https://docs.mentoragent.net/release-notes/1-2-0#a-notice-and-a-mark-for-ai-generated-replies).** For article 50 of the EU AI Act, shown by default in the widget.
- **[Generative cards need a declared type](https://docs.mentoragent.net/release-notes/1-2-0#generative-cards-need-a-declared-type).** A record of yours is no longer drawn as a card by its shape alone.

## New features

### Action policies

The roles say who may use an action. Many rules depend on the data instead: a permission per company or per record, or a limit on an amount. An `IMentorActionPolicy`, registered with `AddMentorActionPolicy<T>()`, decides whether a user may use an action and whether a call may run. It applies on every surface: the widget, the hub, the MCP server and A2A tasks, the specialists, the teams and the MCP servers that you consume.

```csharp title="CaseTeamPolicy.cs"
public class CaseTeamPolicy : IMentorActionPolicy
{
    // Whether the user may use the action: only the support team closes cases
    public ValueTask<bool> IsAllowedAsync(
        MentorActionRequest request, CancellationToken cancellationToken)
        => ValueTask.FromResult(
            request.ToolName != "close_case"
            || request.User?.HasClaim("team", "support") == true);

    // Before each call: a case that is already closed is not closed again
    public async ValueTask<MentorActionVerdict> BeforeActionAsync(
        MentorActionRequest request, CancellationToken cancellationToken)
    {
        if (request.ToolName != "close_case"
            || request.Arguments?.GetValueOrDefault("caseNumber")?.ToString()
                is not { } caseNumber)
            return MentorActionVerdict.Proceed;

        var store = request.Services.GetRequiredService<ICaseStore>();
        var status = await store.GetStatusAsync(caseNumber);
        return status.Contains("closed", StringComparison.OrdinalIgnoreCase)
            ? MentorActionVerdict.Stop($"Case {caseNumber} is already closed.")
            : MentorActionVerdict.Proceed;
    }
}
```

A policy can let a call proceed, ask for a confirmation or stop it, and the strictest answer wins. A policy that throws, or that does not answer within [`ActionPolicyTimeout`](https://docs.mentoragent.net/reference/options#actionpolicytimeout), stops the call. See [Action policies and observers](https://docs.mentoragent.net/reference/action-policies).

### Who confirmed, and every decision of the gate

A record of who approved a change belongs in your ledger, next to the change. Inside an action that asked for confirmation, `MentorActionConfirmation.Current` says who confirmed it and when, with the ID of the confirmation and the tool. It works in `Blocking` and in `Native` mode, and it is `null` for an action that nobody confirmed:

```csharp title="CaseEscalation.cs"
public class CaseEscalation(ILogger<CaseEscalation> logger)
{
    [MentorAction(
        Description = "Escalates a support case to the second line",
        RequiresConfirmation = true)]
    public string EscalateCase(string caseNumber)
    {
        // Who confirmed this call, and when: null when nobody confirmed it
        var confirmation = MentorActionConfirmation.Current;
        logger.LogInformation(
            "Case {Case} escalated, confirmed by {User} at {Time}",
            caseNumber,
            confirmation?.ConfirmedBy?.Identity?.Name,
            confirmation?.ConfirmedAt);
        return $"Case {caseNumber} escalated.";
    }
}
```

An `IMentorActionObserver`, registered with `AddMentorActionObserver<T>()`, receives every decision about a tool call, on every surface and for every agent: the outcome, a reason code that stays the same across versions, the tool, the agent, the session, the user and who confirmed. It never receives the arguments, and a decision never waits for it:

```csharp title="ActionAuditLog.cs"
public class ActionAuditLog(ILogger<ActionAuditLog> logger) : IMentorActionObserver
{
    public ValueTask OnDecisionAsync(
        MentorActionDecision decision, CancellationToken cancellationToken)
    {
        logger.LogInformation(
            "{Tool}: {Outcome} ({Reason}) for {User}, confirmed by {ConfirmedBy}",
            decision.ToolName, decision.Outcome, decision.ReasonCode,
            decision.User?.Identity?.Name ?? "anonymous",
            decision.ConfirmedBy?.Identity?.Name ?? "nobody");
        return ValueTask.CompletedTask;
    }
}
```

`IMentorStateService.OnActionStarted` also gives the technical name of the tool that starts, next to the text shown.

### The list of your actions

A component that shows or checks your actions needs to see them as MentorAgent does. `IMentorActionCatalog.GetActions()` lists the actions of the coordinator, of the specialists and of the team members, with their roles, whether they ask for a confirmation, whether they only read, and whether the MCP server and the A2A card publish them.

```csharp title="ActionChecks.cs"
// The writes that the MCP server publishes to every caller, for example
// to fail a test when one appears
public static IEnumerable<string> WritesPublishedToEveryone(
    IServiceProvider services) =>
    services.GetRequiredService<IMentorActionCatalog>().GetActions()
        .Where(action => !action.ReadOnly
            && action.McpExposure == MentorMcpExposure.Published)
        .Select(action => action.ToolName);
```

### A time limit on confirmations

A confirmation box that nobody answers kept the action waiting. With [`ConfirmationTimeout`](https://docs.mentoragent.net/reference/options#confirmationtimeout), a box that nobody answers in time counts as rejected: the action does not run, and the model is told that nobody confirmed it in time. By default there is no limit, as before.

### A notice and a mark for AI-generated replies

Article 50 of the EU AI Act asks that people know when they talk to an AI system, and that generated content can be told apart. The widget shows a notice under its header, [`ShowAiDisclosure`](https://docs.mentoragent.net/reference/options#showaidisclosure), with a default text in the ten languages, or yours in [`AiDisclosureMessage`](https://docs.mentoragent.net/reference/options#aidisclosuremessage). Every reply of the model also carries a machine-readable mark: `data-ai-generated="true"` in the widget, `"aiGenerated": true` on `/mentor/chat`, the hub event `ReplyStarted` and `aiGenerated` on A2A. Whether the article applies to your application, and which words to show, is yours to decide.

### A language per page

A site in more than one language needs the widget in the language of the page. `<ChatWidget Language="…" />` gives one page a language of its own: the widget's texts, its voice, its tour and the assistant's answers follow it.

```razor title="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;
}
```

Clients send it as `language` on `/mentor/chat` or with the hub method `SendUserMessage`. A2A and MCP stay on [`Language`](https://docs.mentoragent.net/reference/options#language).

### Other features

- **A theme that follows the system.** A site with a light and a dark mode had to pick one theme for the widget. [`MentorTheme.Auto`](https://docs.mentoragent.net/reference/options#theme) shows the light theme, `Minimal`, or the dark one, `Default`, as the visitor's system asks.
- **The context of a request at the end of the conversation.** With [`SessionContextPlacement`](https://docs.mentoragent.net/reference/options#sessioncontextplacement) set to `LastUserMessage`, the page, its data and the time go with the user's last message, and the instructions stay the same through the session. A model that caches the prompt by its beginning, such as a local model, then answers sooner.

## Improvements

- **Roles: the model sees only the actions the user may run.** At the start of every turn, MentorAgent decides which actions the user may run, and offers the model only those: on the coordinator, the specialists, the team members and the agents of an `IMentorAgentSource`. The model is told that some actions are not available, without naming them, in the language of the turn. A call to an action left out is still refused, as before. Under [Fixes](https://docs.mentoragent.net/release-notes/1-2-0#the-model-is-offered-only-what-the-user-may-run) you find what this fixes.
- **`Proactive` no longer closes with next steps.** With [`MentorshipLevel.Proactive`](https://docs.mentoragent.net/reference/options#mentorshiplevel), the answers no longer end with suggested next steps: the model offered steps that the application does not have (BUG-074). The explanations and the warnings stay. To bring the suggestions back, set [`ProactiveNextSteps`](https://docs.mentoragent.net/reference/options#proactivenextsteps) to `true`.
- **The widget shows the AI notice by default.** Under its header, the widget now says "You are talking to an AI assistant: its replies are generated automatically.", in the language of `Language`. Write your own text with `AiDisclosureMessage`, or set `ShowAiDisclosure` to `false`.
- **The hub sends `ReplyStarted` before each reply.** A client that does not register it keeps working, but SignalR logs a warning for every reply. Register it, even with a handler that does nothing, as the components of [Any front end](https://docs.mentoragent.net/configure/any-front-end) do. The WebAssembly client of `Novior.MentorAgent.Blazor` registers it.
- **Traces and logs.** The `mentor.tool.*` spans carry the outcome, the reason code, the confirmation and the surface, and a failed call sets the status of the span to error. A call refused for its roles is logged once, at Warning, in both modes. The MCP server tells apart, in its log, a call refused for its roles, an action that it never publishes and an unknown name.
- **A check of the public API.** At pack time, every package is checked against the public API of 1.1.0. An intended break is listed under [Breaking changes](https://docs.mentoragent.net/release-notes/1-2-0#breaking-changes).
- **Security and support policy.** `SECURITY.md` says that a vulnerability report is answered within 5 working days, on a best-effort basis, and how long each version gets security fixes: see [Versioning and support](https://docs.mentoragent.net/release-notes#versioning-and-support).

## Fixes

Each defect has a severity from S1, the most severe, to S4: see [Versioning and support](https://docs.mentoragent.net/release-notes#versioning-and-support).

### Generative cards need a declared type

**Breaking.** Severity S3 (BUG-212). A tool result became a [generative card](https://docs.mentoragent.net/reference/options#enablegenerativecards) by its shape alone: any object with a non-empty string `kind`. So a record of yours with such a property, for example a support ticket with a link that a customer wrote, was drawn as a card, the link became a button, and the other fields never reached the model.

Now a result is read for cards only when its method is declared to return them: `MentorCard`, or a sequence of `MentorCard`, directly or through a `Task`.

**What to do.** If a method returns cards as `object`, as a `JsonElement` built by hand or as a type of your own, declare its return type as `MentorCard` or `IEnumerable<MentorCard>`. Otherwise its cards are shown as text. A `MentorCard` that an [`OnToolResult`](https://docs.mentoragent.net/reference/options#ontoolresult) hook returns is still a card. The result of an MCP tool never is.

### The model is offered only what the user may run

Severity S3 (BUG-200, BUG-201, BUG-205) and S2 (BUG-224). The coordinator's model was offered every action, including those the user had no role for. Asked for one, it announced it, and the check of the roles then refused it. Specialists, team members and the agents of an `IMentorAgentSource` had the same defect (BUG-205). Now each of them is offered only the actions that the user may run. With [`RequireActionForChangeRequests`](https://docs.mentoragent.net/reference/options#requireactionforchangerequests), such an action is no longer one that the first call can be required to make (BUG-201).

The notice that tells the model that some actions are not available was in English in every language, and in Italian the model still offered the action. The notice now follows the language of the turn, and tells the model not to offer such an action and not to use another tool in its place (BUG-224). A residue remains: see [BUG-224](https://docs.mentoragent.net/reference/known-issues#bug-224-an-action-the-user-may-not-run-offered-in-another-language).

### Stop and confirmations

- **BUG-204 (S2):** a Stop pressed after the user confirmed an action, but before the action started, did not stop it. Now the action does not run, and the model is told that the user stopped the request.
- **BUG-203 (S2):** in `Native` mode, a tool that the [`RequiresApproval`](https://docs.mentoragent.net/reference/options#requiresapproval) predicate gated after the coordinator was built ran with no confirmation. Now the confirmation box appears, as in `Blocking` mode.
- **BUG-208 (S3):** in `Native` mode, the confirmation box of an action could take its text from another method with the same tool name, so the user was asked to confirm what the other method does. The box now reads as the method that the call runs, as the [1.1.0 notes](https://docs.mentoragent.net/release-notes/1-1-0#each-call-follows-the-rules-of-its-own-method) said for both modes (BUG-188).
- **BUG-225 (S2):** in `Native` mode, a model response with two calls of one ID broke the conversation until it was reset. Now the second call gets an ID of its own, except with [`UseServiceManagedHistory`](https://docs.mentoragent.net/reference/options#useservicemanagedhistory).

### MCP

- **BUG-202 (S2):** the [MCP server](https://docs.mentoragent.net/configure/mcp-server) read the `RequiresApproval` predicate once, at the first request. An action that the predicate gated after that stayed in `tools/list` and ran for a caller that nobody could ask. Now the predicate is read for every list and every call.
- **BUG-198 (S3):** a shared MCP server that connected but did not answer `ping` was opened and dropped again in a loop, and the turn never reached the model. Now it is left out for a while, like a server that is down, and a warning says so.

### A2A

- **BUG-197 (S2):** on A2A 1.0.0-preview3, a turn delegated to a [remote agent](https://docs.mentoragent.net/reference/options#remoteagents) came back empty, with no error. Now MentorAgent declares streaming, and streams to a peer only when its card declares it.
- **BUG-199 (S2):** a caller that cancelled an A2A task was answered `Working`, and the task went on. Now cancelling stops the task as Stop does, and the task ends `Canceled`.
- **BUG-210 (S2):** a Stop pressed while a request was delegated to a remote agent did not tell the remote agent, which finished the task. Now MentorAgent cancels the task on the remote agent.
- **BUG-211 (S3):** a Stop during the first message, while the card of a remote agent was being read, kept that agent out of the session. Now the next message reaches it.
- **BUG-226 (S2):** an A2A caller told about the task at once (`ReturnImmediately`) could become anonymous, so its actions with roles were refused. Now the caller is read when the task starts.

### The widget

- **BUG-217 (S1):** a message that held the text that the widget uses inside as a placeholder for a code block, such as `@@BMCODE7@@`, threw out of the render. On Blazor Server the circuit ended, and the page stopped responding until a reload. The text may come from a user, or from the model repeating data that others wrote. Now it is shown as it is.
- **BUG-209 (S3):** the links to the widget's stylesheet and script kept `?v=10` in every release, so a returning visitor's browser could keep the files of the previous release. Now the links carry the package version. If you write the two links yourself, see [Upgrade from 1.1.0](https://docs.mentoragent.net/release-notes/1-2-0#upgrade-from-110).
- **BUG-213 (S3):** under a Content-Security-Policy without `style-src 'unsafe-inline'`, such as `default-src 'self'`, the `Dark` and `Minimal` themes, a `PrimaryColor` and the colors of `<MentorDashboard />` were dropped. Now they work with any option.
- **BUG-215 (S3):** a Markdown link in a reply was shown as raw text. Now an `http` or `https` link opens in a new tab. Any other address stays text.
- **BUG-216 (S3):** a numbered list with a line of text between its steps started again from 1.
- **BUG-214 (S4):** IntelliSense described `MentorTheme.Default` as a light theme. `Default` is dark glass, and the light theme is `Minimal`: see [`Theme`](https://docs.mentoragent.net/reference/options#theme).

### The onboarding tour

- **BUG-206 (S3):** the tour was written from every action, including those that need a role, and `GET /mentor/tour` answers anyone. Now it is written from the actions that need no role.
- **BUG-207 (S4):** a visitor who closed the page while the model wrote the tour ended the generation, and every visitor got the built-in tour until a restart. Now the generation does not depend on any visitor.

### Language and answers

- **BUG-221 (S3):** with a [`Language`](https://docs.mentoragent.net/reference/options#language) other than English, the instructions that MentorAgent writes for the model were in English, and the model could copy them into its reply. Now they follow `Language`.
- **BUG-223 (S2):** with [`RefuseOutOfScope`](https://docs.mentoragent.net/reference/options#refuseoutofscope), a short reply such as "Sì" to the assistant's question was judged on its own, and refused as out of scope. Now it is judged with the end of the assistant's last reply.
- **BUG-222 (S2):** [`RequireLookupForRecordQuestions`](https://docs.mentoragent.net/reference/options#requirelookupforrecordquestions) had no effect without semantic tool filtering, and the model could make up a list of records. Now, without the filter, the first call of a record question must be a declared read that the user may run, or a handoff.

### Agent Framework 1.23 and later

- **BUG-195 (S2):** an agent offered two tools with the same name failed every turn with "Duplicate tool name". Now each agent is offered one tool per name, the one that a call by that name runs, and a warning names the copies.
- **BUG-196 (S2):** [`MentorDeclarativeOptions.ConfigurationSection`](https://docs.mentoragent.net/reference/options#mentordeclarativeoptionsconfigurationsection) gave a definition of a [specialist in YAML](https://docs.mentoragent.net/concepts/agent-levels#specialists-in-yaml) no values. Now the package loads the section itself, as before.

### Fixes in the README

- **BUG-227 (S4):** the option tables of the Server README left out `ConfirmationMessage`, although the 1.1.0 notes said that every option was listed (BUG-163). Now a test checks both READMEs.

## Security

This version fixes no security advisory.

## Breaking changes

### Generative cards only from a declared return type

- **Previous behavior:** any tool result with a non-empty string `kind` was drawn as a generative card.
- **New behavior:** a result is read for cards only when its method declares `MentorCard`, or a sequence of `MentorCard`, as its return type, directly or through a `Task`.
- **Reason:** a record of yours could be drawn as a card, with a link that someone else wrote as a button, and its other fields never reached the model (BUG-212).
- **What to do:** declare the return type of the methods that return cards: see [the fix](https://docs.mentoragent.net/release-notes/1-2-0#generative-cards-need-a-declared-type).
- **APIs affected:** the methods that return cards as `object`, as a `JsonElement` or as a type of your own, with [`EnableGenerativeCards`](https://docs.mentoragent.net/reference/options#enablegenerativecards) on.

## Deprecations

This version deprecates nothing.

## Dependency updates

| Package | From | To | Why |
|---|---|---|---|
| `Microsoft.Agents.AI` and `Microsoft.Agents.AI.Workflows` | 1.18.0 | 1.26.0 | The Agent Framework 1.26 line. In 1.26, `AgentSessionStore.DeleteSessionAsync` is abstract. |
| `Microsoft.Agents.AI.Declarative` (in `Novior.MentorAgent.Declarative`) | 1.18.0-rc1 | 1.26.0-rc1 | The same line. |
| `Microsoft.Extensions.AI` | 10.7.0 | 10.11.0 | It moves with Agent Framework. |
| `ModelContextProtocol`, and `ModelContextProtocol.AspNetCore` in `Novior.MentorAgent.Server` | 1.3.0 | 2.3.0 | The MCP endpoint asks for the stateful transport, so it answers as before. A server that lists a tool without `inputSchema` is no longer connected. |
| `A2A`, and `A2A.AspNetCore` in `Novior.MentorAgent.Server` | 1.0.0-preview2 | 1.0.0-preview3 | See [A2A preview3 with MentorAgent 1.1.0](https://docs.mentoragent.net/reference/known-issues#a2a-preview3-with-mentoragent-110). |

## Known issues

The defects that are still open in 1.2.0, with what to do about each one, are on the [Known issues](https://docs.mentoragent.net/reference/known-issues) page, together with the prerelease packages and the package versions to pin. The limits that are not defects are in [Known limitations](https://docs.mentoragent.net/reference/known-limitations).

## Upgrade from 1.1.0

1. **Upgrade the five packages together.** Each package requires the others at exactly its own version.
2. **Check the methods that return cards.** A method that returns generative cards must declare `MentorCard` or `IEnumerable<MentorCard>` as its return type: see [Generative cards need a declared type](https://docs.mentoragent.net/release-notes/1-2-0#generative-cards-need-a-declared-type).
3. **Register `ReplyStarted` in your hub clients.** A JavaScript, React, Angular or Vue client, or a .NET client of the hub, adds `connection.on("ReplyStarted", () => {})`, or `hub.On<MentorReplyStarted>("ReplyStarted", _ => { })`. Without it, SignalR logs a warning for every reply.
4. **Decide about the AI notice.** The widget now shows it by default. Keep it, write your own text with `AiDisclosureMessage`, or turn it off with `ShowAiDisclosure = false`.
5. **Update the links you write by hand.** If a page links `MentorAgent.css` and `MentorAgent.js` itself, as the `index.html` of a [Blazor WebAssembly](https://docs.mentoragent.net/configure/blazor-webassembly#minimal-configuration) client or of a [.NET MAUI](https://docs.mentoragent.net/configure/maui#minimal-configuration) app does, add `?v=1.2.0` to both links, and change it on every upgrade (BUG-209).
6. **Check `Proactive`.** If you use `MentorshipLevel.Proactive` and want the suggested next steps, set `ProactiveNextSteps = true`.
7. **Check your own session store and MCP servers.** In Agent Framework 1.26, `AgentSessionStore.DeleteSessionAsync` is abstract: an `AgentSessionStore` of your own implements it. An MCP server that lists a tool without `inputSchema` is no longer connected, and a warning names it.
8. **Remote agents on A2A.** A 1.1.0 host that moves `A2A` or `A2A.AspNetCore` to 1.0.0-preview3 on its own cannot answer A2A tasks. Upgrade MentorAgent with them.

If you upgrade from 1.0.0, first read [Upgrade from 1.0.0](https://docs.mentoragent.net/release-notes/1-1-0#upgrade-from-100) in the 1.1.0 notes: 1.1.0 fixed a vulnerable SignalR dependency of the Blazor client (BUG-173). If you still use the `MentorAgent.*` packages, start from [Move to the Novior.MentorAgent packages](https://docs.mentoragent.net/release-notes/1-0-0#move-to-the-noviormentoragent-packages).

## Full list of changes

Every entry of this version is in the [1.2.0 section of CHANGELOG.md](https://github.com/Novior-dev/MentorAgent/blob/main/CHANGELOG.md#120---2026-10-27), which is also the [release notes on NuGet](https://www.nuget.org/packages/Novior.MentorAgent/1.2.0#releasenotes-body-tab).

---

- Previous: [Release notes](https://docs.mentoragent.net/release-notes.md)
- Next: [MentorAgent 1.1.0 release notes](https://docs.mentoragent.net/release-notes/1-1-0.md)
