Portal Community

DI Registration

// In Program.cs / Startup.cs:
builder.Services.AddServerNode<LocalEmbeddingServerNode>(options =>
{
    options.NodeId      = "local-embedding";
    options.DisplayName = "Local Text Embedding Server";
    options.ModelPath   = builder.Configuration["AI:EmbeddingModelPath"]
                          ?? "/opt/models/bge-small-en-v1.5.onnx";
    options.MaxConcurrency = 4;
});

AddServerNode<T> is an extension method that internally does:

public static IServiceCollection AddServerNode<TServerNode>(
    this IServiceCollection services,
    Action<ServerNodeOptions> configure)
    where TServerNode : class, IServerNode
{
    services.Configure(configure);
    services.AddSingleton<TServerNode>();
    services.AddSingleton<IServerNode>(sp => sp.GetRequiredService<TServerNode>());
    services.AddHostedService(sp => sp.GetRequiredService<TServerNode>());
    return services;
}

ServerNodeRegistry

public interface IServerNodeRegistry
{
    void Register(string nodeId, IServerNode node);
    void Deregister(string nodeId);
    bool TryGetNode(string nodeId, out IServerNode? node);
    IReadOnlyCollection<string> RegisteredNodeIds { get; }
}

The registry is a singleton ConcurrentDictionary-backed store. Server nodes call Register at the end of StartAsync and Deregister at the start of StopAsync. The ServerNodeCallExecutor uses TryGetNode to route workflow calls.

Configuration Pattern

// appsettings.json:
{
  "ServerNodes": {
    "LocalEmbedding": {
      "ModelPath":       "/opt/models/bge-small-en-v1.5.onnx",
      "MaxConcurrency":  4,
      "WarmupOnStart":   true
    }
  }
}
// Options class:
public class LocalEmbeddingOptions
{
    public string ModelPath      { get; set; } = string.Empty;
    public int    MaxConcurrency { get; set; } = 2;
    public bool   WarmupOnStart  { get; set; } = true;
}

// Registration with configuration binding:
builder.Services.AddServerNode<LocalEmbeddingServerNode>(options =>
    builder.Configuration.GetSection("ServerNodes:LocalEmbedding")
                         .Bind(options));

Health Endpoint

Once registered, each server node is automatically surfaced in the platform health check:

GET /health/server-nodes
{
  "nodes": [
    {
      "nodeId":   "local-embedding",
      "isReady":  true,
      "status":   "healthy",
      "checkedAt": "2026-05-25T10:00:00Z",
      "diagnostics": {
        "modelLoaded": true,
        "activeRequests": 2,
        "queueDepth": 0
      }
    }
  ]
}

GET /health/server-nodes/local-embedding
{ "nodeId": "local-embedding", "isReady": true, "status": "healthy", ... }
Startup ordering: If a server node depends on another service (e.g., a configuration service that loads the model path from a database), use IHostedService startup ordering via builder.Services.AddHostedService registration order. Services registered earlier start earlier. The server node that depends on the config service should be registered after it.