Cortex

Plugin System

Lifecycle hooks for metrics, audit trails, tracing, and custom processing.

Cortex 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 interface

type Extension interface {
    Name() string
}

Lifecycle hooks

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

Agent lifecycle

InterfaceMethodWhen it fires
RunStartedOnRunStarted(ctx, agentID, runID, input)Agent run begins
RunCompletedOnRunCompleted(ctx, agentID, runID, output, elapsed)Run finishes successfully
RunFailedOnRunFailed(ctx, agentID, runID, err)Run fails with error

Reasoning lifecycle

InterfaceMethodWhen it fires
StepStartedOnStepStarted(ctx, runID, stepIndex)Reasoning step begins
StepCompletedOnStepCompleted(ctx, runID, stepIndex, elapsed)Reasoning step finishes

Tool lifecycle

InterfaceMethodWhen it fires
ToolCalledOnToolCalled(ctx, runID, toolName, args)Tool invocation begins
ToolCompletedOnToolCompleted(ctx, runID, toolName, result, elapsed)Tool succeeds
ToolFailedOnToolFailed(ctx, runID, toolName, err)Tool fails

Persona lifecycle

InterfaceMethodWhen it fires
PersonaResolvedOnPersonaResolved(ctx, agentID, personaName)Persona loaded for run
BehaviorTriggeredOnBehaviorTriggered(ctx, runID, behaviorName)Behavior fires
CognitivePhaseChangedOnCognitivePhaseChanged(ctx, runID, from, to)Phase transition

Checkpoint lifecycle

InterfaceMethodWhen it fires
CheckpointCreatedOnCheckpointCreated(ctx, cpID, runID, reason)Checkpoint created
CheckpointResolvedOnCheckpointResolved(ctx, cpID, decision)Checkpoint resolved

Orchestration lifecycle

InterfaceMethodWhen it fires
OrchestrationStartedOnOrchestrationStarted(ctx, orchID, strategy)Multi-agent orchestration begins
OrchestrationCompletedOnOrchestrationCompleted(ctx, orchID, elapsed)Orchestration finishes
AgentHandoffOnAgentHandoff(ctx, orchID, from, to, payload)Agent-to-agent handoff

Shutdown

InterfaceMethodWhen it fires
ShutdownOnShutdown(ctx)Graceful shutdown

Registry

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.EmitRunStarted(ctx, agentID, runID, input)
// Only calls extensions that implement RunStarted

Error handling

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

// Inside registry.EmitRunStarted:
if err := hook.OnRunStarted(ctx, agentID, runID, input); err != nil {
    r.logger.Warn("extension hook error", "hook", "OnRunStarted", "error", err)
}

Built-in extensions

  • observability.MetricsExtension — Counters for all lifecycle events
  • audithook.Extension — Bridges events to an audit trail backend

Writing a custom extension

type SlackNotifier struct {
    webhookURL string
}

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

func (s *SlackNotifier) OnRunFailed(ctx context.Context, agentID id.AgentID, runID id.AgentRunID, err error) error {
    // Send Slack notification
    return postToSlack(s.webhookURL, fmt.Sprintf("Agent %s run %s failed: %v", agentID, runID, err))
}

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

On this page