Home / Docs / Server

Server

Server

The FabrCore server hosts your agents on an Orleans cluster. It provides REST and WebSocket APIs for client communication and background services for housekeeping.

Extension Methods

AddFabrCoreServer

Configures FabrCore server infrastructure including Orleans, services, streaming, and automatic discovery of the entry assembly plus referenced FabrCore dependencies.

Program.cs
var builder = WebApplication.CreateBuilder(args);

// Basic setup
builder.AddFabrCoreServer();

// Only needed for a runtime-loaded assembly outside the dependency graph
builder.AddFabrCoreServer(new FabrCoreServerOptions
{
    AdditionalAssemblies = new List<Assembly>
    {
        typeof(MyCustomAgent).Assembly
    }
});

What it configures:

  • Orleans silo with clustering, persistence, and reminders
  • IFabrCoreRegistry (singleton) — Agent, plugin, and tool discovery
  • IFabrCoreChatClientService (singleton) — Chat client factory
  • IEmbeddings (singleton) — Embedding generation with cached client
  • IFileStorageService (singleton) — File storage with TTL
  • FileCleanupBackgroundService — Automatic file cleanup
  • AgentRegistryCleanupService — Automatic agent registry cleanup

UseFabrCoreServer

Registers FabrCore middleware, including the WebSocket endpoint for real-time agent communication.

Program.cs
var app = builder.Build();

app.UseFabrCoreServer();

Complete Startup Example

Program.cs — Full Setup
var builder = WebApplication.CreateBuilder(args);

builder.AddFabrCoreServer();

builder.Services.AddControllers();

var app = builder.Build();

app.UseFabrCoreServer();
app.MapControllers();

await app.RunAsync();

Cloud Server Configuration

A host normally reads its model configurations and API keys from a local fabrcore.json. With the Cloud Server feature enabled it pulls that same payload from a remote server instead and reports periodic heartbeats — useful when a fleet of silos should share one source of configuration rather than each shipping its own secrets file.

The feature is disabled by default and existing hosts are unaffected. It is configured entirely through appsettings.json; no fabrcore.json is required.

appsettings.json
{
  "FabrCore": {
    "HostUrl": "https://agents.example.com",
    "CloudServer": {
      "Enabled": true,
      "Url": "https://forge.vulcan365.ai",
      "ApiKey": "<per-cluster API key>",
      "ClusterId": null,
      "Environment": null
    },
    "RemoteAdministration": {
      "Enabled": true,
      "PollWait": "00:00:20"
    }
  }
}
OptionDefaultDescription
EnabledfalseOpt in to cloud-delivered configuration
Urlhttps://forge.vulcan365.aiBase URL; override for a self-hosted server
ApiKeynullPer-cluster key presented as a bearer token
ClusterIdOrleans ClusterOptions.ClusterIdCluster identifier sent to the server
EnvironmentIHostEnvironment.EnvironmentNameUsed for appsettings-style layering
RefreshInterval00:05:00How often the host checks for updated configuration
RequestTimeout00:00:30Timeout for individual requests
CacheLastKnownGoodtrueCache the last good config to disk so the host can start while the server is unreachable
CacheFilePathfabrcore.cloud-cache.jsonCache location, relative to the content root
StartupFailureBehaviorFailFail stops startup with a clear error; StartDegraded starts with no configuration and returns 404 on model lookups until a sync succeeds
The API key is the operator's responsibility

Securing ApiKey — user secrets, environment variables, or a vault-backed configuration provider — is up to you. The disk cache has the same secrets-on-disk profile as fabrcore.json, so treat it the same way.

Remote administration

Remote administration lets the cloud server dispatch administrative calls into the host. It is disabled by default and requires CloudServer:Enabled to be true — a host cannot enable remote administration without also enabling its cloud connection. It reuses FabrCore:CloudServer:ApiKey; there is no separate credential.

FabrCore:HostUrl is the only remote-administration target. It must be an absolute http(s) URL reachable from the host process, and requests outside /fabrcoreapi/ are rejected. The host logs a startup warning for a non-loopback URL, because the Cloud Server API key then travels the network.

PollWait validates to 55 seconds but is clamped to 25

Connect is a long poll, not an ordinary request. PollWait defaults to 20 seconds and configuration validation rejects anything above 55, but the effective server wait is clamped to 1–25 seconds — so a configured 50 is silently honored as 25. The host adds a 10-second transport buffer on top for its per-attempt timeout, and the connect channel uses a dedicated transport so app-wide HttpClient resilience defaults (notably Aspire's 10-second attempt timeout) cannot cut the poll short. A 204 after the wait is a normal empty queue, not a failure.

The protocol is vendor-neutral

FabrCore Forge is the first-party server, but the wire contract is open: protocol constants and DTOs ship in FabrCore.Core under FabrCore.Core.CloudServer, paths are versioned (/fabrcore-cloud/v1/…), and all JSON is camelCase. A host configured against any conforming server behaves identically. Envelopes with a schemaVersion newer than the host supports are rejected and the last-known-good configuration is kept.

FabrCoreRegistry

FabrCoreRegistry discovers agents, plugins, tools, and provider registrations at startup by scanning the application entry assembly and referenced FabrCore dependency graph for alias attributes. No routine assembly list is required — decorate your classes and reference their projects or packages.

Discovery Scope and Dynamic Assemblies

In v1.7.1, referenced agent and plugin projects are found automatically. Use AdditionalAssemblies only for assemblies loaded dynamically or otherwise outside the application's dependency graph:

Program.cs — Assembly Loading
builder.AddFabrCoreServer(new FabrCoreServerOptions
{
    AdditionalAssemblies = new List<Assembly>
    {
        typeof(MyCustomAgent).Assembly,
        typeof(MyPluginLibrary).Assembly
    }
});
Exact registry scope

FabrCoreServerOptions.RegistryAssemblies exposes the exact assembly set used to build the registry. This keeps service discovery and runtime registry construction aligned and prevents a plugin from appearing in one path but not the other. For UI components or MVC controllers, continue to use ASP.NET Core application parts.

How Discovery Works

Assembly Scan Find Alias Attributes Index by Alias Ready to Resolve
AttributeTargetDescription
[AgentAlias("name")]ClassRegisters a FabrCoreAgentProxy under one or more aliases
[PluginAlias("name")]ClassRegisters an IFabrCorePlugin for agent use
[ToolAlias("name")]MethodRegisters a static method as a callable tool

All attributes support AllowMultiple = true, so a single component can have multiple aliases:

Multi-Alias Example
[AgentAlias("assistant")]
[AgentAlias("my-assistant")]
public class AssistantAgent : FabrCoreAgentProxy
{
    // Reachable via either alias
}

Registry API

MethodReturnsDescription
GetAgentTypes()List<RegistryEntry>All discovered agents with their aliases
GetPlugins()List<RegistryEntry>All discovered plugins with their aliases
GetTools()List<RegistryEntry>All discovered tools with their aliases
FindAgentType(alias)Type?Resolve an agent alias to its concrete type

Discovery REST Endpoint

The registry is exposed via a built-in REST endpoint, allowing clients to discover available agents, plugins, and tools at runtime:

MethodEndpointDescription
GET/fabrcoreapi/DiscoveryReturns all registered agents, plugins, and tools
Discovery Response
{
  "agents": [
    { "typeName": "MyApp.AssistantAgent", "aliases": ["assistant", "my-assistant"] }
  ],
  "plugins": [
    { "typeName": "MyApp.WeatherPlugin", "aliases": ["weather"] }
  ],
  "tools": [
    { "typeName": "MyApp.MathTools.Add", "aliases": ["add-numbers"] }
  ]
}
Dev Experience

The discovery endpoint makes it easy to verify your agents, plugins, and tools are correctly registered. Hit /fabrcoreapi/Discovery during development to confirm everything was picked up by the assembly scan — no guesswork needed.

REST API

All endpoints are prefixed with /fabrcoreapi.

Agent API

MethodEndpointDescription
POST/fabrcoreapi/Agent/createCreate or intentionally reconfigure one or more agents
POST/fabrcoreapi/Agent/blueprintIdempotently ensure a principal's baseline agent set
GET/fabrcoreapi/Agent/health/{handle}Get agent health status
POST/fabrcoreapi/Agent/chat/{handle}Send a message and receive a response

All Agent API endpoints require the x-user-handle header to identify the caller principal. For an idempotent, application-owned way to provision a standard agent set, see Blueprints.

Create Agent — Request
POST /fabrcoreapi/Agent/create
x-user-handle: principal123

[{
  "handle": "my-agent",
  "agentType": "MyAgent",
  "models": "default",
  "systemPrompt": "You are a helpful assistant."
}]

Diagnostics API

MethodEndpointDescription
GET/fabrcoreapi/Diagnostics/agentsList all registered agents
GET/fabrcoreapi/Diagnostics/agents/{key}Get specific agent info
GET/fabrcoreapi/Diagnostics/agents/statisticsAgent count statistics
POST/fabrcoreapi/Diagnostics/agents/purgePurge deactivated agents

File & Embeddings API

MethodEndpointDescription
POST/fabrcoreapi/File/uploadUpload a file (multipart/form-data)
GET/fabrcoreapi/File/{fileId}Download a file
GET/fabrcoreapi/File/{fileId}/infoGet file metadata
POST/fabrcoreapi/EmbeddingsGenerate a vector embedding for a single text
POST/fabrcoreapi/embeddings/batchGenerate embeddings for multiple texts in a single request (up to 2,048 items)

Batch Embeddings

The batch endpoint accepts a list of items, each with a caller-provided id and text:

POST /fabrcoreapi/embeddings/batch — Request
{
  "items": [
    { "id": "item-1", "text": "First document to embed" },
    { "id": "item-2", "text": "Second document to embed" },
    { "id": "item-3", "text": "Third document to embed" }
  ]
}
Response
{
  "results": [
    { "id": "item-1", "vector": [0.0123, -0.0456, ...], "dimensions": 1536 },
    { "id": "item-2", "vector": [0.0789, -0.0321, ...], "dimensions": 1536 },
    { "id": "item-3", "vector": [0.0654, -0.0987, ...], "dimensions": 1536 }
  ]
}

Validation Rules

ConditionError (400)
Items is null or empty"Items list must not be empty."
Any item has empty id"Item at index {i} has an empty Id."
Any item has empty text"Item at index {i} (Id='{id}') has empty Text."
More than 2,048 items"Batch size {n} exceeds maximum of 2048."
Error Handling

All endpoints return consistent error responses: 200 (Success), 400 (Bad Request), 404 (Not Found), 500 (Internal Server Error).

Chat Completions API

FabrCore exposes an OpenAI-compatible chat completions endpoint via the ChatCompletionController, allowing external systems to interact with configured LLM models using standard OpenAI client libraries.

Endpoint

MethodEndpointDescription
POST/fabrcoreapi/ChatCompletionSend a chat completion request to the configured LLM

The endpoint uses IFabrCoreChatClientService to resolve the model by name from fabrcore.json. It is designed for single-turn completions — no streaming or tool calling.

Request Format

POST /fabrcoreapi/ChatCompletion — Request
{
  "Messages": [
    { "Role": "user", "Content": "Extract entities from this text..." }
  ],
  "Options": {
    "Model": "gpt-4o-mini",
    "MaxOutputTokens": 2048,
    "Temperature": 0.2
  }
}

Options is optional. Supported options: Model (defaults to "default"), MaxOutputTokens, Temperature, TopP, TopK, StopSequences, FrequencyPenalty, PresencePenalty.

Response Format

Response
{
  "Text": "The extracted response text...",
  "Model": "gpt-4o-mini",
  "Usage": { "InputTokens": 150, "OutputTokens": 80 }
}
Client Fallback Pattern

On a client host (AddFabrCoreClient), use IFabrCoreHostApiClient.GetChatCompletionAsync() which POSTs to this endpoint on the server host. On a server host (AddFabrCoreServer), resolve IFabrCoreChatClientService directly from DI.

WebSocket API

WebSocket v2 provides authenticated real-time messaging at /ws. Browser clients first obtain a 30-second, single-use ticket from POST /fabrcoreapi/ws/ticket, then connect with the fabrcore.v2 and fabrcore.ticket.<token> subprotocols. Native clients can use the same ticket flow through FabrCore.Client.WebSocket.

v1.7.1 wire contract

Query-string identity and raw AgentMessage payloads are no longer accepted. Clients send typed operations such as message.send, event.send, health.get, and tracked.list. Durable clients use sequence acknowledgements, replay, and explicit gap recovery.

See the WebSocket v2 guide for the handshake, typed client, operation catalog, delivery guarantees, and migration checklist.

Orleans Streaming

FabrCore uses Orleans streams for agent communication:

StreamNamespacePurpose
AgentChatAgentChatRequest/response messaging
AgentEventAgentEventFire-and-forget events

Background Services

ServiceIntervalDescription
FileCleanupBackgroundServiceConfigurableRemoves expired files and orphaned entries
AgentRegistryCleanupServiceEvery 6 hoursPurges agents deactivated more than 7 days ago

Orleans Provider Packages & Auto-Provisioning

FabrCore.Host ships with in-memory Localhost mode built in. Production backends live in dedicated provider packages that are discovered automatically — reference the package, set Orleans:ClusteringMode, and provide a connection string. No registration code is required (explicit options.UseSqlServer() / options.UseAzureStorage() calls are also available).

ModeNuGet PackageClusteringGrain StateRemindersStreams
Localhostbuilt into FabrCore.HostIn-memoryIn-memoryIn-memoryIn-memory
SqlServerFabrCore.Host.SqlServerSQL ServerSQL ServerSQL ServerIn-memory
AzureStorageFabrCore.Host.AzureStorageAzure TablesAzure Blobs (default) or TablesAzure TablesAzure Queues (default) or in-memory

Both provider packages auto-provision their backing resources at startup (controlled by Orleans:AutoInitDatabase, default true) and fail fast with a clear error when the connection string is invalid. Custom backends can implement IFabrCoreOrleansProvider and register via options.UseOrleansProvider(...).

SQL Server: Automatic Table Deployment

The initializer runs embedded SQL scripts that create tables for all four Orleans subsystems under a dedicated orlns schema:

SubsystemSchemaPurpose
ClusteringorlnsSilo membership and liveness tables for cluster coordination
PersistenceorlnsGrain state storage tables for agent and client state
RemindersorlnsPersistent reminder registration tables
StreamingorlnsPub/sub state tables for Orleans stream delivery

The SQL scripts are embedded resources within the FabrCore.Host.SqlServer package. The initializer checks whether the schema and tables already exist before running, making it safe for repeated startups.

Dedicated Schema

All Orleans tables are created under the orlns schema to avoid naming conflicts with your application's tables. This keeps the Orleans infrastructure cleanly separated from your domain data.

C# — SQL Server Clustering Configuration
// appsettings.json — tables are auto-provisioned on first startup
{
  "Orleans": {
    "ClusterId": "prod",
    "ServiceId": "fabrcore",
    "ClusteringMode": "SqlServer",
    "ConnectionString": "Server=localhost;Database=FabrCore;Trusted_Connection=True;"
  }
}

Azure Storage: Tables, Blobs, and Queues

With the FabrCore.Host.AzureStorage package, the host runs entirely on an Azure Storage account: tables for clustering, reminders, and stream pub/sub state; blobs for grain state; queues for streams. At startup the provider creates the tables, the grain-state blob container, and the stream queues if they do not exist.

SubsystemAzure ServiceNotes
ClusteringTable StorageSilo membership and liveness (OrleansSiloInstances)
Grain persistenceBlob Storage (default)Agent conversation state can exceed the 1 MB table entity limit; Table Storage is opt-in for known-small state
RemindersTable StoragePersistent reminder registrations
StreamsQueue Storage (default)Durable delivery; queue names derived deterministically from ServiceId
appsettings.json — Azure Storage Configuration
// Resources are auto-provisioned on first startup.
// For local development, run Azurite and use "UseDevelopmentStorage=true".
{
  "Orleans": {
    "ClusterId": "prod",
    "ServiceId": "fabrcore",
    "ClusteringMode": "AzureStorage",
    "ConnectionString": "DefaultEndpointsProtocol=https;AccountName=...;AccountKey=...",
    "AzureStorage": {
      "GrainStorage": "Blob",
      "ContainerName": "fabrcore-grainstate",
      "Streams": "AzureQueue",
      "StreamQueueCount": 8
    }
  }
}
The AzureStorage section is optional

The values shown above are the defaults — omit the entire Orleans:AzureStorage section and you get blob grain storage and durable queue streams out of the box. StreamQueueCount must match across all silos in a cluster.

Documentation