Skip to content
github-actions[bot] edited this page Jul 23, 2026 · 3 revisions

Overview

Hooks are a powerful extension mechanism in F-Mesh that allow you to execute custom logic at specific points during mesh execution without modifying the core framework. Hooks provide a clean way to add cross-cutting concerns like logging, monitoring, debugging, and validation.

Hooks are available at multiple levels:

  • F-Mesh level - Lifecycle hooks for the entire mesh
  • Component level - Hooks for individual component activation
  • Port level - Hooks for port operations

All hooks share a common characteristic: they can return errors, allowing you to implement fail-fast behavior when needed.

For complete API documentation, see:


F-Mesh Level Hooks

F-Mesh level hooks allow you to intercept execution at key points in the mesh lifecycle and during each wave of component activations.

Available Hooks

Hook When It Runs Context Provided
OnComponentAdded When a component is added via AddComponents *ComponentAddedContext (mesh and component)
BeforeRun Before the mesh starts execution *FMesh
AfterRun After the mesh completes execution *FMesh
BeforeCycle At the start of each wave of component activations *CycleContext (cycle number and mesh instance)
AfterCycle At the end of each wave of component activations *CycleContext

Note: the mesh registers a default BeforeRun hook that validates mesh structure on every run. You can add your own hooks freely, but don't clear the BeforeRun group without re-adding equivalent validation.

Setup Example

fm, err := fmesh.New("monitored-mesh",
    fmesh.WithUnlimitedCycles(),
    fmesh.WithUnlimitedTime(),
)
if err != nil {
    // handle error
}
_ = fm.AddComponents(c1, c2, c3)
fm.SetupHooks(func(h *fmesh.Hooks) {
        h.BeforeRun(func(fm *fmesh.FMesh) error {
            fmt.Printf("Starting mesh '%s'\n", fm.Name())
            return nil
        })
        
        h.BeforeCycle(func(ctx *fmesh.CycleContext) error {
            fmt.Printf("Wave starting (cycle #%d)\n", ctx.Cycle.Number())
            return nil
        })
        
        h.AfterCycle(func(ctx *fmesh.CycleContext) error {
            fmt.Printf("Wave complete (cycle #%d)\n", ctx.Cycle.Number())
            return nil
        })
        
        h.AfterRun(func(fm *fmesh.FMesh) error {
            fmt.Printf("Mesh '%s' completed\n", fm.Name())
            return nil
        })
    })

Use Cases

  • Logging: Track mesh execution flow and timing
  • Monitoring: Collect metrics about cycle duration and component activations
  • Debugging: Inspect mesh state at specific execution points
  • Validation: Verify invariants before/after execution
  • Testing: Inject test-specific behavior without modifying components

Component Level Hooks

Component hooks provide fine-grained control over individual component activation lifecycle.

Available Hooks

Hook When It Runs Context Provided
OnCreation During component.New (after all options and plugins) *Component
BeforeActivation Before the component's activation function runs *Component
OnActivation Appended after the main activation function — runs sequentially in the same activation and shares its error path (first error aborts the chain) *Component (it is an ActivationFunc)
AfterActivation After activation completes (regardless of outcome) *ActivationContext
OnSuccess When activation function returns nil *ActivationContext
OnError When activation function returns an error *ActivationContext
OnPanic When activation function panics *ActivationContext
OnWaitingForInputs When component is waiting for inputs *ActivationContext

The ActivationContext provides access to:

  • Component - The component instance
  • Result - The activation result including status code and error

Setup Example

c, _ := component.New("processor",
    component.WithInputs("in"),
    component.WithOutputs("out"),
    component.WithActivationFunc(func(c *component.Component) error {
        // Component logic here
        return nil
    }),
)
c.SetupHooks(func(h *component.Hooks) {
        h.BeforeActivation(func(c *component.Component) error {
            c.Logger().Printf("Starting activation\n")
            return nil
        })
        
        h.OnSuccess(func(ctx *component.ActivationContext) error {
            ctx.Component.Logger().Printf("Activation succeeded\n")
            return nil
        })
        
        h.OnError(func(ctx *component.ActivationContext) error {
            ctx.Component.Logger().Printf("Activation failed: %v\n", ctx.Result.ActivationError())
            // Could return error here to escalate it
            return nil
        })
        
        h.OnPanic(func(ctx *component.ActivationContext) error {
            ctx.Component.Logger().Printf("Activation panicked: %v\n", ctx.Result.ActivationError())
            return nil
        })
        
        h.AfterActivation(func(ctx *component.ActivationContext) error {
            ctx.Component.Logger().Printf("Activation completed with code: %v\n", ctx.Result.Code())
            return nil
        })
    })

Use Cases

  • Error Recovery: Log errors or implement fallback behavior
  • Performance Monitoring: Measure component execution time
  • Debugging: Inspect input/output signals at each activation
  • Audit: Track which components activated and their results
  • Testing: Verify component behavior without modifying activation function

Port Level Hooks

Port hooks allow you to intercept signal operations on individual ports. Hooks must be set up on each port separately.

Available Hooks

Hook When It Runs Context Provided
OnSignalsAdded After signals are added to port buffer *SignalsAddedContext
OnClear When port buffer is cleared *ClearContext
OnInboundPipe When a pipe is created to this port *InboundPipeContext
OnOutboundPipe When this port creates a pipe *OutboundPipeContext

Context Details:

  • SignalsAddedContext: Port, SignalsAdded (slice of signals)
  • ClearContext: Port, SignalsCleared (count)
  • InboundPipeContext: DestinationPort, SourcePort
  • OutboundPipeContext: SourcePort, DestinationPort

Setup Example

c, _ := component.New("logger",
    component.WithInputs("data", "config"),
    component.WithActivationFunc(myFunc),
)

// Setup hook on individual input port
c.InputByName("data").SetupHooks(func(h *port.Hooks) {
    h.OnSignalsAdded(func(ctx *port.SignalsAddedContext) error {
        fmt.Printf("Port '%s' received %d signals\n", 
            ctx.Port.Name(), 
            len(ctx.SignalsAdded))
        
        // Access individual signals
        for _, sig := range ctx.SignalsAdded {
            fmt.Printf("  Signal payload: %v\n", sig.PayloadOrNil())
        }
        return nil
    })
    
    h.OnClear(func(ctx *port.ClearContext) error {
        fmt.Printf("Port '%s' cleared %d signals\n", 
            ctx.Port.Name(), 
            ctx.SignalsCleared)
        return nil
    })
})

// Setup hooks on output port
_ = c.AddOutputs("result")
c.OutputByName("result").SetupHooks(func(h *port.Hooks) {
    h.OnOutboundPipe(func(ctx *port.OutboundPipeContext) error {
        fmt.Printf("Pipe created: %s -> %s\n", 
            ctx.SourcePort.Name(), 
            ctx.DestinationPort.Name())
        return nil
    })
})

Use Cases

  • Signal Tracking: Monitor all signals flowing through specific ports
  • Validation: Verify signal payloads match expected types
  • Debugging: Log signal flow for troubleshooting
  • Metrics: Count signals per port
  • Topology Discovery: Track pipe connections

Hook Execution Order

Understanding the order in which hooks execute is important for building predictable systems.

During Mesh Execution

  1. BeforeRun (F-Mesh)
  2. For each wave of activations:
    • BeforeCycle (F-Mesh)
    • For each scheduled component (concurrent):
      • BeforeActivation (Component)
      • Activation function runs
      • OnSuccess OR OnError OR OnPanic OR OnWaitingForInputs (Component)
      • AfterActivation (Component)
    • Signals forwarded through pipes
    • AfterCycle (F-Mesh)
  3. AfterRun (F-Mesh)

Error Handling in Hooks

Hooks can return errors, which allows you to implement fail-fast behavior:

fm.SetupHooks(func(h *fmesh.Hooks) {
    h.BeforeRun(func(fm *fmesh.FMesh) error {
        if !validateConfiguration(fm) {
            return fmt.Errorf("invalid mesh configuration")
        }
        return nil
    })
    
    h.BeforeCycle(func(ctx *fmesh.CycleContext) error {
        if ctx.Cycle.Number() > 1000 {
            return fmt.Errorf("runaway mesh detected")
        }
        return nil
    })
})

When a hook returns an error:

  • The error is propagated up and can stop mesh execution
  • Subsequent hooks in the same group may not execute (hook groups are fail-fast)
  • A failing component-level hook re-codes the activation result to ActivationCodeHookFailed, which counts as an activation error — under StopOnFirstErrorOrPanic the mesh stops and the hook error surfaces in Run()'s return
  • A failing BeforeCycle/AfterCycle hook aborts the run; a failing OnComponentAdded hook fails AddComponents

Best Practices

Keep Hooks Lightweight

Hooks run synchronously and can impact performance. Keep hook logic simple and fast:

// Good: lightweight logging
h.AfterCycle(func(ctx *fmesh.CycleContext) error {
    log.Printf("Cycle %d done\n", ctx.Cycle.Number())
    return nil
})

// Bad: expensive operations
h.AfterCycle(func(ctx *fmesh.CycleContext) error {
    // Don't do this - sends HTTP request on every cycle
    sendMetricsToServer(ctx)
    return nil
})

Use Appropriate Hook Level

Choose the right level for your hook:

  • Use F-Mesh hooks for mesh-wide concerns (startup, shutdown, metrics)
  • Use Component hooks for component-specific logic (error handling, validation)
  • Use Port hooks for signal-level concerns (tracing, validation)

Don't Modify Component Logic

Hooks should observe and extend behavior, not replace core logic:

// Good: observe and log
h.OnError(func(ctx *component.ActivationContext) error {
    log.Printf("Component failed: %v\n", ctx.Result.ActivationError())
    return nil // Let the error propagate normally
})

// Bad: changing component behavior
h.BeforeActivation(func(c *component.Component) error {
    // Don't manipulate signals here - do it in activation function
    c.InputByName("in").Clear()
    return nil
})

Chain Multiple Hooks

You can add multiple hooks of the same type - they execute in order:

fm.SetupHooks(func(h *fmesh.Hooks) {
    h.BeforeRun(logStartup)
    h.BeforeRun(validateConfig)
    h.BeforeRun(initializeMetrics)
})

Common Patterns

Execution Timer

var startTime time.Time

fm.SetupHooks(func(h *fmesh.Hooks) {
    h.BeforeRun(func(fm *fmesh.FMesh) error {
        startTime = time.Now()
        return nil
    })
    
    h.AfterRun(func(fm *fmesh.FMesh) error {
        duration := time.Since(startTime)
        fmt.Printf("Mesh executed in %v\n", duration)
        return nil
    })
})

Component Performance Profiling

componentTimings := make(map[string]time.Duration)

c.SetupHooks(func(h *component.Hooks) {
    var start time.Time
    
    h.BeforeActivation(func(c *component.Component) error {
        start = time.Now()
        return nil
    })
    
    h.AfterActivation(func(ctx *component.ActivationContext) error {
        duration := time.Since(start)
        componentTimings[ctx.Component.Name()] += duration
        return nil
    })
})

Signal Flow Tracing

// Setup tracing on specific input ports
c.InputByName("data").SetupHooks(func(h *port.Hooks) {
    h.OnSignalsAdded(func(ctx *port.SignalsAddedContext) error {
        for _, sig := range ctx.SignalsAdded {
            fmt.Printf("[TRACE] Signal received on %s.%s: %v\n",
                ctx.Port.ParentComponent().Name(),
                ctx.Port.Name(),
                sig.PayloadOrNil())
        }
        return nil
    })
})

Hooks vs Activation Function

Understanding when to use hooks versus activation functions:

Feature Activation Function Hooks
Purpose Core component logic Cross-cutting concerns
Required Yes (for processing) No (optional)
Modifies signals Yes Should not
Performance impact Direct Overhead
Reusability Component-specific Can be shared

Use activation functions for: Business logic, signal processing, computations

Use hooks for: Logging, monitoring, debugging, validation, testing


Hooks make F-Mesh more extensible and observable without compromising the simplicity of component design. They provide clean separation between core logic and auxiliary concerns.

Hook context types

Type Fields Used by
fmesh.ComponentAddedContext FMesh, Component OnComponentAdded
fmesh.CycleContext FMesh, Cycle BeforeCycle, AfterCycle
component.ActivationContext Component, Result AfterActivation, OnSuccess, OnError, OnPanic, OnWaitingForInputs
port.SignalsAddedContext Port, SignalsAdded OnSignalsAdded
port.ClearContext Port, SignalsCleared OnClear
port.InboundPipeContext DestinationPort, SourcePort OnInboundPipe
port.OutboundPipeContext SourcePort, DestinationPort OnOutboundPipe

For reading ActivationResults after a run, see Inspecting a run.

Clone this wiki locally