# Module Development This guide explains how to write a new **agent connector module** or **judge (testing) module** for mate. --- ## Module Tiers Every module exposes a `ModuleTier Tier { get; }` property (defined in `mate.Domain.Contracts.Modules.ModuleTier`). The value is shown as a badge on the Settings → Modules card and signals licensing intent: | Value | Meaning | |-------|---------| | `ModuleTier.Free` | No additional licence — available in every installation | | `ModuleTier.Premium` | Premium module — fully functional, but commercial deployment requires a valid licence | ### Current tier assignments | Module category | Tier | |-----------------|------| | All agent connector modules | Free | | All judge / testing modules | Free | | All question-generation modules | Free | | All monitoring modules | Free | | `mate.Modules.RedTeaming.Generic` | **Premium** | When writing a new module, always declare the tier explicitly: ```csharp public ModuleTier Tier => ModuleTier.Free; // or ModuleTier.Premium ``` --- ## Module Contracts All module types live in `src/Core/mate.Domain/Contracts/`. Every module must implement the common base members plus its type-specific interface. ### Common Members (all module types) ```csharp string ModuleId { get; } // unique snake_case identifier, e.g. "copilot_studio" string DisplayName { get; } // human-readable name shown in UI ModuleTier Tier { get; } // Free or Premium (see Module Tiers above) List ConfigSchema { get; } // drives the dynamic config form Task IsHealthy(); // self-check — called by /health/modules Task ValidateConfig(Dictionary config); List GetCapabilities(); // optional feature flags ``` ### `ConfigFieldDefinition` ```csharp public record ConfigFieldDefinition { public string Key { get; init; } // config dictionary key public string Label { get; init; } // form label public string? Description { get; init; } public bool IsRequired { get; init; } public bool IsSecret { get; init; } // masked in UI, stored encrypted public string? DefaultValue { get; init; } public List? SelectOptions { get; init; } // null = free text } ``` --- ## Writing an Agent Connector Module Agent connectors implement `IAgentConnectorModule` (in `mate.Domain.Contracts`). ### 1. Create the project ``` src/Modules/AgentConnectors/mate.Modules.AgentConnector.MyConnector/ mate.Modules.AgentConnector.MyConnector.csproj MyConnectorModule.cs MyConnector.cs ``` Add a `` to `mate.Domain` and `mate.Core`. ### 2. Implement `IAgentConnectorModule` ```csharp public class MyConnectorModule : IAgentConnectorModule { public string ModuleId => "my_connector"; public string DisplayName => "My Connector"; public List ConfigSchema => new() { new() { Key = "ApiUrl", Label = "API URL", IsRequired = true }, new() { Key = "ApiKey", Label = "API Key", IsRequired = true, IsSecret = true }, }; public Task IsHealthy() { // attempt a lightweight API call; return false on failure } public Task ValidateConfig(Dictionary config) { // check required keys are present and well-formed } public IAgentConnector CreateConnector(Dictionary config) { return new MyConnector(config["ApiUrl"], config["ApiKey"]); } public List GetCapabilities() => new() { "text" }; } ``` ### 3. Implement `IAgentConnector` ```csharp public class MyConnector : IAgentConnector { public Task StartConversationAsync(); public Task SendMessageAsync(string conversationId, string message); public Task EndConversationAsync(string conversationId); } ``` ### 4. Register in DI In `src/Host/mate.WebUI/Program.cs`, inside the module registration block: ```csharp builder.Services.AddSingleton(); ``` Also add a `` to `mate.Modules.AgentConnector.MyConnector` in `mate.WebUI.csproj`. --- ## Writing a Judge (Testing) Module Judge modules implement `ITestingModule` and optionally `IJudgeProvider`. ### 1. Create the project ``` src/Modules/Testing/mate.Modules.Testing.MyJudge/ mate.Modules.Testing.MyJudge.csproj MyJudgeModule.cs ``` ### 2. Implement `ITestingModule` ```csharp public class MyJudgeModule : ITestingModule { public string ModuleId => "my_judge"; public string DisplayName => "My Judge"; public string ProviderType => "MyJudge"; // used to filter in UI public List ConfigSchema => new() { new() { Key = "PassThreshold", Label = "Pass Threshold", DefaultValue = "0.7" }, }; public Task IsHealthy() => Task.FromResult(true); public Task ValidateConfig(Dictionary config) => ...; public List GetCapabilities() => new() { "verdict", "score" }; public Task EvaluateAsync(JudgeRequest request) { // score the response and return a JudgeResult with Passed, Score, Rationale } } ``` ### `JudgeRequest` and rubric criteria `JudgeRequest` carries all the context a judge needs per test case: ```csharp public record JudgeRequest { public string Input { get; init; } // the user message sent to the agent public string ExpectedAnswer { get; init; } // the reference/golden answer public string ActualResponse { get; init; } // the agent's actual response public string? SystemPrompt { get; init; } // judge system prompt from settings public double PassThreshold { get; init; } // from judge setting public IReadOnlyList RubricCriteria { get; init; } // see below // ... other fields } ``` `RubricCriteria` is populated by `TestExecutionService.LoadRubricCriteriaAsync` at run start — it queries all **non-draft** `RubricSet` entities whose `JudgeSettingId` matches the active judge and combines their criteria in sort order. Your judge module receives the full list and is responsible for evaluating each one. ```csharp public record EvaluationCriterion( string Name, string EvaluationType, // Contains | NotContains | Regex | Custom | Prompt string? Pattern, // match value or Custom subtype (e.g. "NonEmpty") double Weight, bool IsMandatory ); ``` **Important:** If your judge has built-in default criteria (as `CopilotStudioJudge` does), always **append** `request.RubricCriteria` after your defaults — never replace them: ```csharp var criteria = MyJudgeDefaultCriteria .Concat(request.RubricCriteria) .ToList(); ``` This ensures custom rubric sets extend rather than override built-in safety checks. ### 3. Register in DI ```csharp builder.Services.AddSingleton(); builder.Services.AddSingleton(); ``` --- ## Writing a Red Teaming Module Red-team modules implement `IRedTeamModule` and `IAttackProvider` (both in `mate.Domain.Contracts.RedTeaming`). They are architecturally independent from `ITestingModule` — red-teaming is adversarial security testing, not functional quality testing. ### 1. Create the project ``` src/Modules/RedTeaming/mate.Modules.RedTeaming.MyAttacker/ mate.Modules.RedTeaming.MyAttacker.csproj MyAttackerModule.cs ``` Add `` to `mate.Domain` and `mate.Core`. ### 2. Implement `IAttackProvider` ```csharp public class MyAttackProvider : IAttackProvider { public string ProviderType => "MyAttacker"; public async Task> GenerateProbesAsync( AttackRequest request, CancellationToken ct = default) { // generate adversarial prompts — use an LLM, a catalogue, or static templates // filter by request.Categories, respect request.NumberOfProbes } public async Task EvaluateResponseAsync( AttackProbe probe, string agentResponse, CancellationToken ct = default) { // return null when the agent safely refused the probe // return a RedTeamFinding with Risk, Rationale, ReproductionSteps, Mitigations otherwise } } ``` ### 3. Implement `IRedTeamModule` ```csharp public class MyAttackerModule : IRedTeamModule { public string ModuleId => "my_attacker"; public string DisplayName => "My Attacker"; public string ProviderType => "MyAttacker"; public List ConfigSchema => new() { new() { Key = "Endpoint", Label = "LLM Endpoint", IsRequired = true }, new() { Key = "ApiKey", Label = "API Key", IsRequired = true, IsSecret = true }, }; public Task IsHealthy() => ...; public Task ValidateConfig(Dictionary config) => ...; public IReadOnlyList GetCapabilities() => ["prompt_injection", "jailbreak"]; public void RegisterServices(IServiceCollection services, IConfiguration config) { services.AddSingleton(); services.AddSingleton(); } } ``` ### 4. DI extension (recommended pattern) ```csharp public static class MyAttackerModuleExtensions { public static IServiceCollection AddmateMyAttacker( this IServiceCollection services, IConfiguration config) { services.AddSingleton(); services.AddSingleton(); return services; } } ``` ### 5. Register in `Program.cs` ```csharp // ── Red-teaming modules ────────────────────────────────────────────────────── builder.Services.AddmateMyAttacker(config); ``` Also add a `` in `mate.WebUI.csproj`. No registry or UI change is needed — the module is auto-discovered and shown in Settings → Modules → Red Teaming Modules. ### Key DTOs | Type | Role | |------|------| | `AttackRequest` | Input to `GenerateProbesAsync` — agent description, categories filter, probe count, domain hint, resolved credentials | | `AttackProbe` | One adversarial prompt with category, optional `FailureSignature`, and rationale | | `RedTeamFinding` | Confirmed vulnerability — probe, agent response, `RiskLevel`, rationale, reproduction steps, mitigations | | `RedTeamReport` | Aggregated findings for a complete red-team run | ### Risk levels Assign `RiskLevel` in `EvaluateResponseAsync` based on potential real-world impact: | Risk | Examples | |------|---------| | Critical | Toxic/harmful content generation, successful jailbreak enabling illegal instructions | | High | Prompt injection, data exfiltration, successful privacy leak | | Medium | System-prompt leak, role confusion | | Low | Minor hallucination induction, incomplete refusal | --- ## Module Discovery `mateModuleRegistry` is populated at startup in `Program.cs` by resolving all registered `IAgentConnectorModule`, `ITestingModule`, `IJudgeProvider`, `IMonitoringModule`, and `IRedTeamModule` instances from DI. The WebUI reads the registry to build the Wizard step 1 list, the Settings Modules tab (including the Red Teaming Modules section), and health check pages. No manual registry entry is needed — registering in DI is sufficient. --- ## Naming Convention Project names follow the pattern: ``` mate.Modules.. ``` Examples: `mate.Modules.AgentConnector.CopilotStudio`, `mate.Modules.Testing.HybridJudge`, `mate.Modules.Auth.EntraId`, `mate.Modules.RedTeaming.Generic`. **Use singular form** (`AgentConnector`, not `AgentConnectors`; `RedTeaming` as the folder/category name). --- ## Testing Your Module Add unit tests in `tests/mate.Tests.Unit/` following the existing pattern in `tests/mate.Tests.Unit/Testing/`. At minimum, test: - `ConfigSchema` — all required fields are present - `ValidateConfig` — rejects missing required fields - `IsHealthy` — returns false when the endpoint is unreachable (mock HTTP) - Core evaluation logic --- ## Further Reading - [Architecture](Developer-Architecture) — dependency rules and module contracts - [Backlog E18](Developer-Backlog) — dynamic binary plugin system (E18) - [Backlog E19](Developer-Backlog) — full red-teaming roadmap (run execution, UI, AI-powered modules, compliance reporting)