1 · Turn service methods into actions
Tutorial step 1: expose the methods of a case service as actions the agent can call, with descriptions the model understands.
In this tutorial, you'll:
- Keep the cases in a store that every action shares
- Write two reads and one confirmed write, with descriptions the model can match
- Predict the tool name the model sees for each method
- Watch the agent chain two actions to answer one request
On this page
In this step you give the Cases app sample data and three actions: find a case, list cases and add a note. You also see what the model reads about each action, because it uses that to choose one.
Before you start: finish the Quickstart. You have the Cases app with CaseService, the model key in user secrets and the widget in the layout.
Add a case store
Section titled “Add a case store”The Quickstart's methods return fixed text. Give them something to read and change: a small store with three cases. Create CaseStore.cs in the project folder:
public record SupportCase(
string Number, string Title, string Status, string Priority, IReadOnlyList<string> Notes);
public class CaseStore
{
private readonly Lock gate = new();
private readonly List<SupportCase> cases =
[
new("C-1", "Cannot sign in to the portal", "Open", "High", []),
new("C-2", "Invoice shows the wrong address", "Open", "Normal", []),
new("C-3", "Export to Excel times out", "Waiting for customer", "Normal",
["Asked the customer for the export log."]),
];
public SupportCase? Find(string number)
{
lock (gate) return cases.Find(c => Is(c, number));
}
public IReadOnlyList<SupportCase> List(string? status)
{
lock (gate)
return [.. cases.Where(c => string.IsNullOrEmpty(status)
|| c.Status.Equals(status, StringComparison.OrdinalIgnoreCase))];
}
public void AddNote(string number, string text)
=> Update(number, c => c with { Notes = [.. c.Notes, text] });
public void Close(string number, string resolution)
=> Update(number, c => c with { Status = "Closed", Notes = [.. c.Notes, $"Closed: {resolution}"] });
private void Update(string number, Func<SupportCase, SupportCase> change)
{
lock (gate)
{
var i = cases.FindIndex(c => Is(c, number));
if (i < 0)
throw new KeyNotFoundException($"There is no case {number}.");
cases[i] = change(cases[i]);
}
}
private static bool Is(SupportCase c, string number)
=> c.Number.Equals(number, StringComparison.OrdinalIgnoreCase);
}The store keeps the cases in memory, so they go back to their starting state each time the app restarts. In your own app, the actions call your database or the services you already have.
Rewrite the service
Section titled “Rewrite the service”Replace the content of CaseService.cs:
using System.ComponentModel;
using Novior.MentorAgent.Attributes;
public class CaseService(CaseStore store)
{
[MentorAction(
Description = "Gets a support case by its number, with its status, priority and notes",
ReadOnly = true)]
public SupportCase? GetCase(
[Description("The case number, for example C-1")] string caseNumber)
=> store.Find(caseNumber);
[MentorAction(
Description = "Lists support cases, optionally only those with a given status",
ReadOnly = true)]
public IReadOnlyList<SupportCase> ListCases(
[Description("Open, Waiting for customer or Closed. Leave empty for every case")] string? status = null)
=> store.List(status);
[MentorAction(
Description = "Adds a note to a support case",
RequiresConfirmation = true)]
public string AddNote(string caseNumber, string text)
{
store.AddNote(caseNumber, text);
return $"Note added to case {caseNumber}.";
}
}GetCaseandListCasesonly read, so they use[MentorAction]withReadOnly = true. The agent calls them without asking.AddNotechanges data, so it asks for confirmation, as in the Quickstart. HereRequiresConfirmation = truewrites the rule in the code.[Description]on a parameter tells the model what value to pass, for example the format of a case number.- The methods return data, not sentences. MentorAgent sends the returned object to the model as JSON, and the model writes the answer.
Register the store
Section titled “Register the store”In Program.cs, replace the AddScoped<CaseService>() line with:
builder.Services.AddSingleton<CaseStore>(); // one store for the whole app
builder.Services.AddScoped<CaseService>();AddSingleton keeps one store for the whole app, so a note added in one conversation is still there in the next.
Then, inside AddMentorAgent, after options.AppName, describe the app:
options.AppDescription = "Tracks customer support cases: their status, priority and notes";The description becomes part of the instructions the model receives. A real sentence helps it tell which requests belong to your app.
Check the tool names
Section titled “Check the tool names”The model never sees your C# methods. It sees tools, each with a name, a description and its parameters. MentorAgent derives the name from the method: snake case, with a trailing Async dropped.
| Method | Tool name | Description the model reads |
|---|---|---|
GetCase | get_case | Gets a support case by its number, with its status, priority and notes |
ListCases | list_cases | Lists support cases, optionally only those with a given status |
AddNote | add_note | Adds a note to a support case |
- The Quickstart's
GetCaseStatuswas the toolget_case_status. Renaming a method renames its tool. Settings that name a tool then stop matching it. One example is theRequiresApprovalpredicate in Tools you cannot annotate. - Write each description for the model, not for other developers: what the action does and what it returns, in the words your users use. The model picks a tool by comparing the request with these descriptions.
Try it
Section titled “Try it”Run the app with dotnet run, open it and click the widget button.
- Type "Which cases are open?". The agent calls
list_casesand names C-1 and C-2. C-3 is waiting for the customer, so it is not open. - Type "What do we know about C-3?". The agent calls
get_caseand tells you the case is waiting for the customer, with its note. - Type "Add a note to the open case with high priority: customer called back". The agent finds the case with
list_cases, then callsadd_note. The conversation keeps the results of earlier tool calls, so the agent can skiplist_caseswhen step 1 already listed the cases. The confirmation box shows the description and the arguments, such as "Adds a note to a support case — Case Number: C-1 · Text: customer called back". - Confirm, then type "Show me C-1". The note is there.
In a new conversation the request in step 3 uses two actions: the agent chains tools on its own when a request needs more than one. If the agent answers without calling a tool, see The agent does not call your method.
Next step
Section titled “Next step”Anyone who reaches the widget can now add a note. In 2 · Require roles and confirmation you decide who may change a case.
To understand where actions fit, read Level 1: actions in The three agent levels, and Roles, confirmation and governance.
What you learned
Section titled “What you learned”- Keep the cases in a store that every action shares
- Write two reads and one confirmed write, with descriptions the model can match
- Predict the tool name the model sees for each method
- Watch the agent chain two actions to answer one request
See also
Section titled “See also”- Attributes — The parameters of MentorAction, MentorAgent, MentorTeam, TeamMember, MentorPage and MentorSkill, with their defaults and rules.
- Add MentorAgent to an existing app — Add MentorAgent to a Blazor Web App you already have: install the package, register the services, mark your methods and place the widget.
