The AI Assistant and the AI Rule Editor
The Rule Repository has two AI features of its own, both built into its user interface:
- The AI Assistant is a conversation in a dialog of its own, opened by the AI button at the top of RR. A user asks in plain language ("which rules hold an order for review?", "write a test for this rule", "how often did this rule decline last week?") and the assistant does the work through RR's own tools: it finds sources and rules, explains them, writes rules, drafts and tests, runs tests, and reads what production recorded, within that user's permissions.
- The AI Rule Editor is a tab beside the Rule Editor in RR's rule dialogs (New Rule, a draft, a test, a rule version). A user describes a rule and the AI writes it there; the server checks every rule the AI writes against the source object and shows whether it is valid, and switching to the Rule Editor tab carries the latest rule across. It never saves anything itself.
Why you would turn them on. They let business users write and check rules in their own words, explore a large set of rules quickly, and ask questions about production decisions without learning every screen. Every rule the AI writes is checked by the server against the source object, so a rule or rule version it saves is always structurally valid (a draft may hold unfinished work); people review what the rule does, not how it is built. Nothing they do bypasses RR's rules: the AI acts for the signed-in person, under that person's permissions, and it can never make a version live, approve, decline or delete; everything it saves waits for a person's approval.
What it costs you to run. The model calls are made by your server, with your own account at the AI vendor you choose, and billed to you. Neither feature exists until you register at least one model, so an application that never configures them has no AI at all. To let people use their own AI clients (Claude, Copilot and others) with your rules instead, or as well, see AI Assistant vs the MCP Server.
How It Works
- The user types a message in the browser. RR's client sends it to your RR endpoint as an ordinary RR request, with the anti-forgery token, like every other request.
- Your endpoint builds the request's
Configuration as always, including its Assistant property, and calls Context.GetResponse(..).
- RR makes one model call with the conversation, its own instructions and the tools the conversation has loaded. If the model asks for tools, RR runs them in your process, as the signed-in person, and answers the browser with the steps to show.
- The browser asks for the next step until the model answers in words. Each step is a separate request, so no request waits for a whole conversation, and Stop simply means the browser does not ask for the next step.
There is no second endpoint, no background service and no extra port. The model is reached only from your server, through the client you build; the browser never talks to the AI vendor, and the API keys never leave your server.
Requirements
- Your RR endpoint already working (see The Server Endpoint).
- A model that can call tools. RR works with any large language model that has a Microsoft.Extensions.AI
IChatClient and supports tool calling. Anthropic's Claude and OpenAI's GPT models were tested. The fixed part of every call (RR's instructions, the skills and the tool declarations) is some 10,000-15,000 tokens, so the model needs a context window well beyond that, and it must be able to write a rule from exact member names: a small local model may connect and still write poor rules.
- A request timeout that allows one model call. A step usually takes seconds and sometimes a minute. ASP.NET Core has no request timeout by default; on IIS or behind a proxy, allow at least two minutes. On ASP.NET (.NET Framework) the default
executionTimeout is 110 seconds.
- A request size limit that allows attachments, when users may attach files: a file arrives in one request, encoded, about a third larger than the file.
Step 1: Add the Vendor's Package
RR references no AI vendor. You build the client with the vendor's own .NET package and hand it to RR as an IChatClient:
| Vendor |
Package |
What you need |
| Anthropic (Claude) |
Anthropic |
An API key and a model id |
| OpenAI (GPT) |
Microsoft.Extensions.AI.OpenAI |
An API key and a model id |
| Azure OpenAI / Azure AI Foundry |
Azure.AI.OpenAI and Microsoft.Extensions.AI.OpenAI |
The resource's endpoint, a deployment name, and a key or a Microsoft Entra ID identity |
| A server that speaks OpenAI's API: Ollama, LM Studio or vLLM on your own hardware, or another vendor's OpenAI-compatible endpoint |
Microsoft.Extensions.AI.OpenAI |
The server's address and a model name; a key only if the server asks for one |
| Any other vendor (AWS Bedrock, Google Gemini and others) |
The vendor's package or adapter that implements IChatClient |
What that vendor's package asks for |
Step 2: Register the Models Once
Build the clients when your application starts and keep them: the vendors' clients are meant to be reused by every request. Keep the keys out of source control (user secrets, environment variables, a key vault).
using System.ClientModel;
using Anthropic;
using Azure;
using Azure.AI.OpenAI;
using CodeEffects.Rule.Repository;
using Microsoft.Extensions.AI;
using OpenAI;
public static class AiModels
{
public static AssistantSettings Build(IConfiguration configuration)
{
// Anthropic (package: Anthropic)
IChatClient claude = new AnthropicClient { ApiKey = configuration["Ai:AnthropicKey"] }
.AsIChatClient("claude-sonnet-4-5");
// OpenAI (package: Microsoft.Extensions.AI.OpenAI)
IChatClient gpt = new OpenAI.Chat.ChatClient("gpt-5", configuration["Ai:OpenAIKey"])
.AsIChatClient();
// Azure OpenAI (packages: Azure.AI.OpenAI, Microsoft.Extensions.AI.OpenAI): the deployment name, not the model id
IChatClient azure = new AzureOpenAIClient(new Uri("https://contoso.openai.azure.com/"),
new AzureKeyCredential(configuration["Ai:AzureKey"]!))
.GetChatClient("rules-gpt")
.AsIChatClient();
// An OpenAI-compatible server: a local Ollama, LM Studio or vLLM, or another vendor's compatible endpoint
IChatClient local = new OpenAI.Chat.ChatClient("qwen3:32b", new ApiKeyCredential("none"),
new OpenAIClientOptions { Endpoint = new Uri("http://ai-server:11434/v1") })
.AsIChatClient();
return new AssistantSettings
{
Models =
{
new AssistantModel { Key = "claude", DisplayName = "Claude Sonnet", ChatClient = claude,
InputPricePerMillion = 3m, OutputPricePerMillion = 15m,
CachedInputPricePerMillion = 0.30m, CacheWritePricePerMillion = 3.75m },
new AssistantModel { Key = "gpt", DisplayName = "GPT", ChatClient = gpt },
new AssistantModel { Key = "azure-gpt", DisplayName = "GPT (Azure)", ChatClient = azure },
new AssistantModel { Key = "local", DisplayName = "In-house model", ChatClient = local,
ContextBudget = 100_000, ReadsFiles = new HashSet<string>() }
},
Instructions = "Our customers are businesses, never consumers. Amounts are in euros.",
OnUsage = usage => Billing.Record(usage.UserId, usage.ModelKey, usage.InputTokens, usage.OutputTokens),
CanStep = step => Billing.OverQuota(step.UserId) ? "You have used this month's AI allowance." : null
};
}
}
The model ids and prices above are examples: use the ids your vendor lists and the prices from its price list or your contract. Register as many models as you like; the first is the default, and users pick another from the model list in the dialog.
In ASP.NET Core, register the settings as a singleton:
builder.Services.AddSingleton(services => AiModels.Build(builder.Configuration));
Azure with Microsoft Entra ID instead of a key (package Azure.Identity): the application's managed identity or service principal needs a role such as Cognitive Services OpenAI User on the resource.
IChatClient azure = new AzureOpenAIClient(new Uri("https://contoso.openai.azure.com/"), new DefaultAzureCredential())
.GetChatClient("rules-gpt")
.AsIChatClient();
A model on your own hardware keeps every prompt inside your network. Point an OpenAI-compatible client at it as above; most such servers need no key, but the client still sends one, so pass any text. Set ContextBudget to fit the model's context window and ReadsFiles to the file types it reads (an empty set for none).
Step 3: Give Each Request Its Settings
The settings are read from the request's Configuration, like everything else in RR, so you decide per request who gets the AI features and which models:
app.MapPost("/api/rules", async (HttpRequest request, AssistantSettings assistant) =>
{
using var reader = new StreamReader(request.Body);
var requestJson = await reader.ReadToEndAsync();
var config = new Configuration
{
CurrentUserId = request.HttpContext.User.FindFirstValue(ClaimTypes.NameIdentifier),
CurrentUserName = request.HttpContext.User.Identity?.Name,
AccessMode = ActionPermissionMode.AllowOnly,
ActionList = Permissions.For(request.HttpContext.User),
// The AI features for this request: the same models for everyone here; null turns them off for this user
Assistant = request.HttpContext.User.IsInRole("RuleAuthors") ? assistant : null
};
return Results.Content(Context.GetResponse(config, dbProvider, requestJson), "application/json");
}).RequireAuthorization();
Assistant null, or no models: neither feature exists for that request. There is no AI button and no AI Rule Editor tab, and every AI request is refused.
- Different users, different models: build two or more
AssistantSettings at startup (for example, one with a costly model for a few analysts and one with a cheaper model for everyone else) and pick one per request.
Step 4: Grant the Permissions
With models registered, a user still needs a permission for each feature (see Permissions):
| Permission |
Number |
What it allows |
AiAssistantUse |
110 |
The AI button and the AI Assistant dialog |
AiRuleEditorUse |
111 |
The AI Rule Editor tab |
AiConversationViewAll |
112 |
Reading every user's AI Assistant conversations, read-only, and the Usage tab's costs. Requires 110 |
Grant 112 only to administrators. It is a privilege, not a right: viewing another user's conversation leaves no record. An application using the AllowAllExcept access mode grants every permission it does not list, so such an application must list 112 for every user who is not an administrator.
110 and 111 open the features; what the AI may then do is the person's own permission set, narrowed by the rules for agents: it never makes anything live, takes anything offline, approves, declines or deletes, and everything it saves requests approval. In the user activity, the AI appears as an agent acting for the person (by default "AI Assistant").
The Settings
AssistantSettings:
| Setting |
Default |
What it decides |
Models |
empty |
The models users pick from; the first is the default. Empty means no AI features |
AgentId |
"rr-assistant" |
Who the AI is in the user activity: the id of the agent acting for the person |
AgentName |
"AI Assistant" |
The AI's name in the user activity |
MaxSteps |
25 |
The most model calls one turn makes before it stops and offers Continue |
AllowAttachments |
true |
Whether users may attach files in the AI Assistant. Off, the paperclip is not shown and uploads are refused |
MaxAttachmentBytes |
10 MB |
The largest file a user may attach |
KeepAttachments |
false |
Whether attached files stay with their conversation after the turn that used them |
AttachmentStore |
null (RR's database) |
Where attached files are kept (see below) |
ConversationRetentionDays |
90 |
How long a conversation is kept after it was last used. Older ones are deleted, with their files, when a user starts a new conversation |
Instructions |
null |
Your own instructions to the model, added after RR's: your company's terms, what your rules are for, what the AI should not do |
OnUsage |
null |
Called after every model call with the tokens it used: for your own logging, billing or charts |
CanStep |
null |
Called before every step. Return null to let it run, or a message to stop the turn and show the user why |
AssistantModel:
| Setting |
Default |
What it decides |
Key |
(required) |
The model's key, stored with every message it writes. Keep it stable across releases of your application. Letters, digits, dot, dash and underscore, at most 128 characters |
DisplayName |
the key |
The name in the model list |
ChatClient |
(required) |
The connection to the model, built with the vendor's package |
ModelId |
null |
The model id sent with every call; null leaves it to the client |
MaxOutputTokens |
8,192 |
The most tokens one answer may have |
PromptCache |
true |
Whether RR turns on the vendor's prompt cache where the vendor must be asked (Anthropic). Vendors that cache on their own, such as OpenAI, are not affected |
ReadsFiles |
PDF, PNG, JPEG, GIF, WebP |
The file types the model reads. Plain text files are always sent as text. Another type gets a warning in the conversation |
ContextBudget |
400,000 characters |
The most of a conversation sent in one call, about four characters a token. Past it, the oldest tool answers are left out. Lower it for a model with a context under 128,000 tokens |
InputPricePerMillion, OutputPricePerMillion |
null |
The vendor's prices per million tokens, in your currency. With both set, the Usage tab shows costs to holders of 112 |
CachedInputPricePerMillion, CacheWritePricePerMillion |
null |
The prices of tokens read from and written to the vendor's prompt cache. Null prices them as ordinary input |
Customizing
Your own instructions. Instructions is added after RR's own, on every call of both features. Use it for your company's vocabulary, what your sources mean, the language to answer in, or what the AI must not suggest. RR's own rules (never publish, ask before changing an existing rule) cannot be overridden by it.
Quotas and budgets. OnUsage gives you every call's tokens, including cached ones, with the user, the model and whether it was the AI Rule Editor; RR keeps the same numbers for its Usage tab either way. CanStep lets you stop a turn before it costs anything: return a message such as "You have used this month's AI allowance" and the user sees it.
Prompt caching. RR orders its instructions so that their constant part can be cached, and turns Anthropic's cache on itself (PromptCache). Set CachedInputPricePerMillion and CacheWritePricePerMillion so the costs shown match your bill.
Smaller models. Lower ContextBudget and MaxOutputTokens, and empty ReadsFiles for a model that reads no files.
Where attachments live. By default, files go into RR's database and are deleted when their turn ends. KeepAttachments keeps them with their conversation. To keep them outside the database, use a folder:
assistant.AllowAttachments = true;
assistant.MaxAttachmentBytes = 5 * 1024 * 1024;
assistant.KeepAttachments = true;
assistant.AttachmentStore = new FolderAttachmentStore(@"D:\RuleRepository\Attachments");
The folder must exist, be outside your web site's folders, be writable by the application's identity (on IIS, the application pool's), and be shared storage when the application runs on more than one server. For cloud storage, implement IAttachmentStore: three methods, with keys that are always 32 hexadecimal characters chosen by RR, never a user's file name.
public class BlobAttachmentStore : IAttachmentStore
{
private readonly IMyBlobContainer container;
public BlobAttachmentStore(IMyBlobContainer container) => this.container = container;
public void Save(string key, Stream content) => this.container.Upload(key, content);
public Stream Open(string key) => this.container.Exists(key) ? this.container.Download(key) : null!;
public void Delete(string key) => this.container.DeleteIfExists(key);
}
Here IMyBlobContainer stands for your storage client (Azure Blob Storage, Amazon S3 and the like).
No attachments at all. AllowAttachments = false removes the paperclip; your endpoint then needs no room for files.
The AI's limits for tests and data. The test-run limits and whether the AI may read data source items are agent settings, shared with the MCP server: see AI Agent Settings.
What Leaves Your Server
Every model call sends the vendor the conversation, RR's instructions and the answers of the tools the AI used: source and rule definitions, test values, and, when the user asks about production, recorded evaluations. Choose a vendor and a contract whose data terms suit that, or a model on your own hardware. The AI aliases and AI descriptions you put on your source objects are sent too; they exist to help the model. Users' notes are never sent.
Troubleshooting
| What you see |
Why |
| No AI button |
Assistant is null or has no models for this request, or the user lacks 110 |
| No AI Rule Editor tab |
The same, with 111 |
| A step fails after a long wait |
The request timed out on IIS, a proxy or a load balancer before the model answered. Raise the timeout |
| An attachment is refused |
Larger than MaxAttachmentBytes, or larger than your server's request size limit (allow a third more than the file) |
| Costs are not shown |
The model has no prices, or the user lacks 112 |
| The AI writes poor rules |
The model is too small, or the source's fields need better descriptions and AI aliases |