# 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.

- URL: https://docs.mentoragent.net/get-started/tutorial/actions
- Verified with: MentorAgent 1.0.0
- Last updated: 2026-10-13
- Duration: 15 min
- Packages: Novior.MentorAgent

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](https://docs.mentoragent.net/get-started/quickstart). You have the Cases app with `CaseService`, the model key in user secrets and the widget in the layout.

## 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:

```csharp title="CaseStore.cs"
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

Replace the content of `CaseService.cs`:

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

- `GetCase` and `ListCases` only read, so they use `[MentorAction]` with `ReadOnly = true`. The agent calls them without asking.
- `AddNote` changes data, so it asks for confirmation, as in the Quickstart. Here `RequiresConfirmation = true` writes 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

In `Program.cs`, replace the `AddScoped<CaseService>()` line with:

```csharp title="Program.cs"
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:

```csharp title="Program.cs"
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

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 `GetCaseStatus` was the tool `get_case_status`. Renaming a method renames its tool. Settings that name a tool then stop matching it. One example is the `RequiresApproval` predicate in [Tools you cannot annotate](https://docs.mentoragent.net/concepts/governance#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

Run the app with `dotnet run`, open it and click the widget button.

1. Type "Which cases are open?". The agent calls `list_cases` and names C-1 and C-2. C-3 is waiting for the customer, so it is not open.
2. Type "What do we know about C-3?". The agent calls `get_case` and tells you the case is waiting for the customer, with its note.
3. Type "Add a note to the open case with high priority: customer called back". The agent finds the case with `list_cases`, then calls `add_note`. The conversation keeps the results of earlier tool calls, so the agent can skip `list_cases` when 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".
4. 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](https://docs.mentoragent.net/get-started/troubleshooting#the-agent-does-not-call-your-method).

## Next step

Anyone who reaches the widget can now add a note. In [2 · Require roles and confirmation](https://docs.mentoragent.net/get-started/tutorial/roles-and-confirmation) you decide who may change a case.

To understand where actions fit, read [Level 1: actions](https://docs.mentoragent.net/concepts/agent-levels#level-1-actions) in The three agent levels, and [Roles, confirmation and governance](https://docs.mentoragent.net/concepts/governance).

---

- Previous: [Quickstart](https://docs.mentoragent.net/get-started/quickstart.md)
- Next: [2 · Require roles and confirmation](https://docs.mentoragent.net/get-started/tutorial/roles-and-confirmation.md)
