Skip to main content

Loopai Plugin Development Guide

This guide explains how to create custom plugins for the Loopai framework.


Overview​

Loopai provides three types of plugins for extensibility:

  1. Validator Plugins - Custom validation logic for execution results
  2. Sampler Plugins - Custom sampling strategies for quality assurance
  3. Webhook Handler Plugins - Custom webhook event processing

All plugins implement the IPlugin base interface and register with the IPluginRegistry.


Plugin Types​

1. Validator Plugin (IValidatorPlugin)​

Validates execution results against expected outputs or custom business rules.

Use Cases:

  • Custom schema validation
  • Business rule verification
  • Domain-specific output validation
  • Multi-field constraint checking

Interface:

public interface IValidatorPlugin : IPlugin
{
Task<ValidationResult> ValidateAsync(
ExecutionRecord execution,
ValidationContext context,
CancellationToken cancellationToken = default);
}

Example Implementation:

using Loopai.Core.Plugins;
using Loopai.Core.Models;

public class CustomBusinessRuleValidator : IValidatorPlugin
{
public string Name => "custom-business-rule-validator";
public string Description => "Validates output against custom business rules";
public string Version => "1.0.0";
public string Author => "Your Name";
public bool IsEnabled { get; set; } = true;
public int Priority { get; set; } = 100;

public Task<ValidationResult> ValidateAsync(
ExecutionRecord execution,
ValidationContext context,
CancellationToken cancellationToken = default)
{
// Your validation logic here
var isValid = /* custom validation */;

return Task.FromResult(new ValidationResult
{
IsValid = isValid,
ValidatorType = Name,
Message = isValid ? "Valid" : "Invalid business rule",
ConfidenceScore = isValid ? 1.0 : 0.0
});
}
}

2. Sampler Plugin (ISamplerPlugin)​

Determines which executions should be sampled for validation.

Use Cases:

  • Time-based sampling
  • Error-rate triggered sampling
  • Load-based sampling
  • Pattern-based sampling

Interface:

public interface ISamplerPlugin : IPlugin
{
Task<SamplingDecision> ShouldSampleAsync(
ExecutionRecord execution,
SamplingContext context,
CancellationToken cancellationToken = default);
}

Example Implementation:

using Loopai.Core.Plugins;
using Loopai.Core.Models;

public class ErrorRateSamplerPlugin : ISamplerPlugin
{
private readonly double _errorThreshold;

public ErrorRateSamplerPlugin(double errorThreshold = 0.05)
{
_errorThreshold = errorThreshold;
}

public string Name => "error-rate-sampler";
public string Description => $"Samples when error rate exceeds {_errorThreshold * 100}%";
public string Version => "1.0.0";
public string Author => "Your Name";
public bool IsEnabled { get; set; } = true;
public int Priority { get; set; } = 50;

public Task<SamplingDecision> ShouldSampleAsync(
ExecutionRecord execution,
SamplingContext context,
CancellationToken cancellationToken = default)
{
// Calculate error rate from context
var errorRate = /* calculate from execution history */;
var shouldSample = errorRate > _errorThreshold;

return Task.FromResult(new SamplingDecision
{
ShouldSample = shouldSample,
Reason = $"Error rate: {errorRate:P2}",
SamplerType = Name,
ConfidenceScore = shouldSample ? 1.0 : 0.0
});
}
}

3. Webhook Handler Plugin (IWebhookHandlerPlugin)​

Processes webhook events for integrations and notifications.

Use Cases:

  • Slack/Teams notifications
  • External system integration
  • Email alerts
  • Custom analytics

Interface:

public interface IWebhookHandlerPlugin : IPlugin
{
IEnumerable<WebhookEventType> SupportedEvents { get; }

Task HandleAsync(
WebhookEvent webhookEvent,
WebhookHandlerContext context,
CancellationToken cancellationToken = default);

bool ValidateConfiguration(IReadOnlyDictionary<string, object> configuration);
}

Example Implementation:

using Loopai.Core.Plugins;

public class SlackWebhookHandlerPlugin : IWebhookHandlerPlugin
{
public string Name => "slack-webhook-handler";
public string Description => "Sends notifications to Slack";
public string Version => "1.0.0";
public string Author => "Your Name";
public bool IsEnabled { get; set; } = true;
public int Priority { get; set; } = 100;

public IEnumerable<WebhookEventType> SupportedEvents => new[]
{
WebhookEventType.ProgramExecutionFailed,
WebhookEventType.AccuracyThresholdBreached,
WebhookEventType.CanaryRollback
};

public async Task HandleAsync(
WebhookEvent webhookEvent,
WebhookHandlerContext context,
CancellationToken cancellationToken = default)
{
if (context.IsDryRun)
return;

var slackWebhookUrl = context.Configuration["SlackWebhookUrl"] as string;
var message = FormatSlackMessage(webhookEvent);

await SendToSlackAsync(slackWebhookUrl, message, cancellationToken);
}

public bool ValidateConfiguration(IReadOnlyDictionary<string, object> configuration)
{
return configuration.ContainsKey("SlackWebhookUrl") &&
!string.IsNullOrWhiteSpace(configuration["SlackWebhookUrl"] as string);
}

private string FormatSlackMessage(WebhookEvent webhookEvent)
{
// Format message for Slack
return $"Event: {webhookEvent.EventType} at {webhookEvent.Timestamp}";
}

private async Task SendToSlackAsync(string webhookUrl, string message, CancellationToken ct)
{
// Send to Slack API
}
}

Plugin Registration​

1. Manual Registration​

using Microsoft.Extensions.DependencyInjection;
using Loopai.Core.Plugins;

// In Program.cs or Startup.cs
builder.Services.AddSingleton<IPluginRegistry, PluginRegistry>();

var registry = app.Services.GetRequiredService<IPluginRegistry>();

// Register validators
registry.Register<IValidatorPlugin>(new JsonSchemaValidatorPlugin());
registry.Register<IValidatorPlugin>(new CustomBusinessRuleValidator());

// Register samplers
registry.Register<ISamplerPlugin>(new PercentageSamplerPlugin(0.1));
registry.Register<ISamplerPlugin>(new TimeBasedSamplerPlugin(TimeSpan.FromMinutes(5)));
registry.Register<ISamplerPlugin>(new ErrorRateSamplerPlugin(0.05));

// Register webhook handlers
registry.Register<IWebhookHandlerPlugin>(new SlackWebhookHandlerPlugin());
registry.Register<IWebhookHandlerPlugin>(new ConsoleWebhookHandlerPlugin());

2. Configuration-Based Registration​

appsettings.json:

{
"Loopai": {
"Plugins": {
"Validators": [
{
"Name": "json-schema-validator",
"Type": "validator",
"Enabled": true,
"Priority": 100,
"Configuration": {}
},
{
"Name": "custom-business-rule-validator",
"Type": "validator",
"Enabled": true,
"Priority": 50,
"Configuration": {
"StrictMode": true
}
}
],
"Samplers": [
{
"Name": "percentage-sampler",
"Type": "sampler",
"Enabled": true,
"Priority": 100,
"Configuration": {
"SamplingRate": 0.1
}
}
],
"WebhookHandlers": [
{
"Name": "slack-webhook-handler",
"Type": "webhook-handler",
"Enabled": true,
"Priority": 100,
"Configuration": {
"SlackWebhookUrl": "https://hooks.slack.com/services/..."
}
}
]
}
}
}

Code:

builder.Services.Configure<PluginsConfiguration>(
builder.Configuration.GetSection("Loopai:Plugins")
);

Plugin Lifecycle​

1. Registration​

Plugins are registered with the IPluginRegistry at application startup.

2. Discovery​

Services query the registry to discover available plugins by type.

3. Execution​

Plugins are executed in priority order (higher priority = earlier execution).

4. Filtering​

Only enabled plugins are executed (check IsEnabled property).


Built-In Plugins​

Loopai provides several built-in plugins in Loopai.Core.Plugins.BuiltIn:

Validators​

  • JsonSchemaValidatorPlugin - Validates against JSON schema and expected output

Samplers​

  • PercentageSamplerPlugin - Samples a fixed percentage of executions
  • TimeBasedSamplerPlugin - Samples based on time intervals

Webhook Handlers​

  • ConsoleWebhookHandlerPlugin - Logs events to console (development/testing)

Best Practices​

1. Plugin Naming​

  • Use descriptive, kebab-case names: custom-validator, error-rate-sampler
  • Include organization prefix for clarity: acme-business-validator

2. Versioning​

  • Use semantic versioning: 1.0.0, 1.2.3
  • Increment major version for breaking changes

3. Error Handling​

  • Always handle exceptions gracefully
  • Return meaningful error messages
  • Log errors with sufficient context
public async Task<ValidationResult> ValidateAsync(...)
{
try
{
// Validation logic
}
catch (Exception ex)
{
_logger.LogError(ex, "Validation failed for {PluginName}", Name);
return new ValidationResult
{
IsValid = false,
ValidatorType = Name,
Message = $"Validation error: {ex.Message}"
};
}
}

4. Configuration Validation​

  • Always validate configuration in ValidateConfiguration() for webhook handlers
  • Throw clear exceptions for invalid configuration during plugin construction

5. Performance​

  • Keep validation logic fast (<100ms recommended)
  • Use async operations for I/O-bound work
  • Cache expensive computations when possible

6. Testing​

  • Write unit tests for each plugin
  • Test with various input scenarios
  • Mock external dependencies

Advanced Topics​

Multi-Plugin Coordination​

Execute multiple plugins in sequence:

var validators = registry.List<IValidatorPlugin>();
var results = new List<ValidationResult>();

foreach (var validator in validators)
{
var result = await validator.ValidateAsync(execution, context, ct);
results.Add(result);

// Stop on first failure if needed
if (!result.IsValid && stopOnFirstFailure)
break;
}

Plugin Prioritization​

Plugins execute in priority order:

// Higher priority executes first
var criticalValidator = new CriticalValidator { Priority = 1000 };
var standardValidator = new StandardValidator { Priority = 100 };

registry.Register<IValidatorPlugin>(criticalValidator);
registry.Register<IValidatorPlugin>(standardValidator);

// Executes: criticalValidator → standardValidator

Conditional Execution​

Enable/disable plugins at runtime:

var plugin = registry.Resolve<IValidatorPlugin>("my-validator");
if (plugin != null)
{
plugin.IsEnabled = shouldEnable;
}

Packaging and Distribution​

Create NuGet Package​

<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<PackageId>YourCompany.Loopai.Plugins</PackageId>
<Version>1.0.0</Version>
<Authors>Your Name</Authors>
<Description>Custom plugins for Loopai</Description>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Loopai.Core" Version="0.1.0" />
</ItemGroup>
</Project>
dotnet pack -c Release

Install Plugin Package​

dotnet add package YourCompany.Loopai.Plugins

Example: Complete Custom Plugin​

using Loopai.Core.Plugins;
using Loopai.Core.Models;
using Microsoft.Extensions.Logging;

namespace MyCompany.Loopai.Plugins;

public class AnomalyDetectionSamplerPlugin : ISamplerPlugin
{
private readonly ILogger<AnomalyDetectionSamplerPlugin> _logger;
private readonly double _deviationThreshold;

public AnomalyDetectionSamplerPlugin(
ILogger<AnomalyDetectionSamplerPlugin> logger,
double deviationThreshold = 2.0)
{
_logger = logger;
_deviationThreshold = deviationThreshold;
}

public string Name => "anomaly-detection-sampler";
public string Description => "Samples executions with anomalous latency patterns";
public string Version => "1.0.0";
public string Author => "MyCompany";
public bool IsEnabled { get; set; } = true;
public int Priority { get; set; } = 75;

public Task<SamplingDecision> ShouldSampleAsync(
ExecutionRecord execution,
SamplingContext context,
CancellationToken cancellationToken = default)
{
try
{
// Calculate mean and std deviation from context
var meanLatency = /* calculate from historical data */;
var stdDeviation = /* calculate from historical data */;

// Check if current execution is anomalous
var deviation = Math.Abs(execution.LatencyMs - meanLatency) / stdDeviation;
var isAnomalous = deviation > _deviationThreshold;

if (isAnomalous)
{
_logger.LogWarning(
"Anomalous latency detected: {Latency}ms (mean: {Mean}ms, deviation: {Deviation}σ)",
execution.LatencyMs,
meanLatency,
deviation
);
}

return Task.FromResult(new SamplingDecision
{
ShouldSample = isAnomalous,
Reason = isAnomalous
? $"Anomalous latency: {deviation:F2}σ from mean"
: "Normal latency pattern",
SamplerType = Name,
ConfidenceScore = isAnomalous ? deviation / 10.0 : 0.0,
Metadata = new Dictionary<string, object>
{
["mean_latency_ms"] = meanLatency,
["std_deviation_ms"] = stdDeviation,
["deviation_sigma"] = deviation,
["threshold_sigma"] = _deviationThreshold
}
});
}
catch (Exception ex)
{
_logger.LogError(ex, "Error in anomaly detection sampler");
return Task.FromResult(new SamplingDecision
{
ShouldSample = false,
Reason = $"Error: {ex.Message}",
SamplerType = Name
});
}
}
}

Support​

  • Documentation: Loopai Docs
  • Issues: GitHub Issues
  • Examples: See src/Loopai.Core/Plugins/BuiltIn/ for reference implementations

License​

Plugin development follows the same MIT license as Loopai core.