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, and streaming.

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

// Basic setup
builder.AddFabrCoreServer();

// With additional assemblies for custom agents
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();

// Or with options
app.UseFabrCoreServer(new FabrCoreServerOptions
{
    AdditionalAssemblies = new List<Assembly> { typeof(MyAgent).Assembly }
});

Complete Startup Example

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

builder.AddFabrCoreServer(new FabrCoreServerOptions
{
    AdditionalAssemblies = new() { typeof(MyAgent).Assembly }
});

builder.Services.AddControllers();

var app = builder.Build();

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

await app.RunAsync();

FabrCoreRegistry

FabrCoreRegistry automatically discovers agents, plugins, and tools at startup by scanning loaded assemblies for alias attributes. No manual registration lists needed — decorate your classes and they're available.

Loading Additional Assemblies

If your agents, plugins, or tools are in separate assemblies, pass them via AdditionalAssemblies. This ensures the registry scans those assemblies at startup:

Program.cs — Assembly Loading
builder.AddFabrCoreServer(new FabrCoreServerOptions
{
    AdditionalAssemblies = new List<Assembly>
    {
        typeof(MyCustomAgent).Assembly,
        typeof(MyPluginLibrary).Assembly
    }
});
You Don't Need Custom Discovery

FabrCore handles discovery for agents ([AgentAlias]), plugins ([PluginAlias]), and tools ([ToolAlias]) automatically. For application-level concerns like UI components or middleware, use ASP.NET Core's built-in AddApplicationPart() instead of building custom scanning systems.

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 /fabrapi.

Agent API

MethodEndpointDescription
POST/fabrapi/Agent/createCreate one or more agents for a user
GET/fabrapi/Agent/health/{handle}Get agent health status
POST/fabrapi/Agent/chat/{handle}Send a message and receive a response

All Agent API endpoints require the x-user header to identify the user.

Create Agent — Request
POST /fabrapi/Agent/create
x-user: user123

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

Diagnostics API

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

File & Embeddings API

MethodEndpointDescription
POST/fabrapi/File/uploadUpload a file (multipart/form-data)
GET/fabrapi/File/{fileId}Download a file
GET/fabrapi/File/{fileId}/infoGet file metadata
POST/fabrapi/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

Real-time communication with agents via WebSocket at the /ws endpoint.

JavaScript — Connect
// Browser
const ws = new WebSocket('wss://your-server/ws?userid=user123');

// Send a message
ws.send(JSON.stringify({
  toHandle: "my-agent",
  fromHandle: "user123",
  message: "Hello, agent!"
}));

Authentication requires either the x-fabr-userid header or the userid query parameter.

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

SQL Server Auto-Provisioning

When using SqlServer clustering mode, FabrCore automatically provisions the required Orleans database tables at startup via OrleansSqlServerInitializer. No manual SQL scripts or database migrations are needed.

How It Works

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 assembly. 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;"
  }
}
Documentation