Registering the Executor
Executors are discovered at application startup through the INodeExecutorDependency plugin pattern. Each executor assembly provides a INodeExecutorDependency implementation that registers its executors into both the DI container and the ExecutorRegistry.
Two-Step Registration
Every executor must be registered in two places:
- DI Container — so it can be constructed with its dependencies injected
- 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));
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.