Portal Community

Two-Step Registration

Every executor must be registered in two places:

  1. DI Container — so it can be constructed with its dependencies injected
  2. ExecutorRegistry — so the engine can look it up by NodeTypeName (typeCode) at runtime

INodeExecutorDependency Pattern

// MyExecutorDependency.cs — in your executor project's Dependency/ folder
using Microsoft.Extensions.DependencyInjection;
using BizFirst.Ai.ProcessEngine.Service.NodeExecution;  // ExecutorRegistry

public class MyExecutorDependency : INodeExecutorDependency
{
    public void RegisterDefaults(IServiceCollection services)
    {
        // Step 1: register in DI (Scoped — one instance per workflow node execution)
        services.AddScoped<MyCustomNodeExecutor>();

        // Step 2: register in ExecutorRegistry — maps typeCode → executor type
        ExecutorRegistry.Register<MyCustomNodeExecutor>(MyCustomNodeExecutor.NodeTypeName);

        // Add more executors in the same plugin if they belong to the same assembly
        services.AddScoped<MyOtherNodeExecutor>();
        ExecutorRegistry.Register<MyOtherNodeExecutor>(MyOtherNodeExecutor.NodeTypeName);
    }
}

Scope: AddScoped vs AddTransient

Use AddScoped for executors. The scope corresponds to the workflow node execution scope — one executor instance is created per node invocation and disposed when execution completes. This is consistent with how Core and Standard executors are registered.

Discovery at Startup

The host application calls NodeExecutorDependency.RegisterNodeExecutors(services) at startup, which scans all loaded assemblies for INodeExecutorDependency implementations and calls RegisterDefaults on each. Your plugin is discovered automatically as long as your assembly is loaded (referenced by the host project or auto-loaded):

// In Program.cs or DI setup
services.RegisterNodeExecutors();  // scans assemblies, calls all INodeExecutorDependency.RegisterDefaults

Dependency Injection in the Executor

External services required by the executor are injected through the constructor and registered in DI in the same RegisterDefaults method:

public void RegisterDefaults(IServiceCollection services)
{
    // Register executor dependencies first
    services.AddScoped<IMyExternalService, MyExternalService>();
    services.AddHttpClient<IMyExternalService, MyExternalService>();

    // Then register the executor
    services.AddScoped<MyCustomNodeExecutor>();
    ExecutorRegistry.Register<MyCustomNodeExecutor>(MyCustomNodeExecutor.NodeTypeName);
}

Verifying Registration

At application startup (debug mode), log the contents of the ExecutorRegistry to confirm your typeCode is registered:

// ExecutorRegistry exposes registered type codes for diagnostics
var registeredKeys = ExecutorRegistry.GetRegisteredTypeCodes();
logger.LogInformation("Registered executors: {Keys}", string.Join(", ", registeredKeys));
TypeCode Must Match the DB NodeType Record MyCustomNodeExecutor.NodeTypeName is the key the engine uses to resolve the executor. It must match the typeCode field in the NodeType database record and the key in the frontend WorkflowCanvas.nodeTypes map. A mismatch results in "executor not found" errors at runtime.