Sentinel
1.x
Docs/Sentinel/Plugin System
Open

Reading2 min
Updated31 Jul 2026
Sourcev1/infrastructure/plugins.mdx

Sentinel uses an opt-in plugin system where extensions subscribe to lifecycle events by implementing specific interfaces. The base interface requires only a Name() method — all hooks are optional.

Base interface01

type Extension interface {
    Name() string
}

Lifecycle hooks02

There are 16 hook interfaces organized by category. Extensions implement only the hooks they need.

Eval lifecycle

InterfaceMethodWhen it fires
EvalRunStartedOnEvalRunStarted(ctx, suiteID, runID, model)Evaluation run begins
EvalRunCompletedOnEvalRunCompleted(ctx, suiteID, runID, passRate, elapsed)Run finishes successfully
EvalRunFailedOnEvalRunFailed(ctx, suiteID, runID, err)Run fails with error

Case lifecycle

InterfaceMethodWhen it fires
CaseStartedOnCaseStarted(ctx, runID, caseID)Case evaluation begins
CaseCompletedOnCaseCompleted(ctx, runID, caseID, score, elapsed)Case evaluation finishes
CaseFailedOnCaseFailed(ctx, runID, caseID, err)Case evaluation fails

Regression

InterfaceMethodWhen it fires
RegressionDetectedOnRegressionDetected(ctx, suiteID, baselineID, delta)Performance regression found

Baseline

InterfaceMethodWhen it fires
BaselineSavedOnBaselineSaved(ctx, suiteID, baselineID)Baseline saved

Red team

InterfaceMethodWhen it fires
RedTeamStartedOnRedTeamStarted(ctx, suiteID, attackCount)Red team evaluation begins
RedTeamCompletedOnRedTeamCompleted(ctx, suiteID, bypassCount, elapsed)Red team evaluation finishes

Persona evaluation

InterfaceMethodWhen it fires
PersonaEvalStartedOnPersonaEvalStarted(ctx, runID, personaName)Persona-aware evaluation begins
PersonaEvalCompletedOnPersonaEvalCompleted(ctx, runID, personaName, dimensions)Persona-aware evaluation finishes

Prompt versioning

InterfaceMethodWhen it fires
PromptVersionCreatedOnPromptVersionCreated(ctx, suiteID, pvID, version)Prompt version created

Comparison

InterfaceMethodWhen it fires
ComparisonCompletedOnComparisonCompleted(ctx, suiteID, models, elapsed)Multi-model comparison finishes

Shutdown

InterfaceMethodWhen it fires
ShutdownOnShutdown(ctx)Graceful shutdown

Registry03

The plugin.Registry type-caches extensions at registration time:

registry := plugin.NewRegistry(logger)
registry.Register(metricsExt)
registry.Register(auditExt)

When events are emitted, only extensions implementing the relevant hook are called:

registry.EmitEvalRunStarted(ctx, suiteID, runID, model)
// Only calls extensions that implement EvalRunStarted

Error handling04

Hook errors are logged but never propagated. Hooks must not block the evaluation pipeline.

Built-in extensions05

  • observability.MetricsExtension — Counters for all lifecycle events

  • audithook.Extension — Bridges events to an audit trail backend

Writing a custom extension06

type SlackNotifier struct {
    webhookURL string
}

func (s *SlackNotifier) Name() string { return "slack-notifier" }

func (s *SlackNotifier) OnRegressionDetected(ctx context.Context, suiteID id.SuiteID, baselineID id.BaselineID, delta float64) error {
    return postToSlack(s.webhookURL, fmt.Sprintf("Regression detected in suite %s: %.1f%% drop", suiteID, delta*100))
}

// Register:
eng, _ := engine.New(
    engine.WithExtension(&SlackNotifier{webhookURL: "https://hooks.slack.com/..."}),
)