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.
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 discoveryIFabrCoreChatClientService(singleton) — Chat client factoryIEmbeddings(singleton) — Embedding generation with cached clientIFileStorageService(singleton) — File storage with TTLFileCleanupBackgroundService— Automatic file cleanupAgentRegistryCleanupService— Automatic agent registry cleanup
UseFabrCoreServer
Registers FabrCore middleware, including the WebSocket endpoint for real-time agent communication.
var app = builder.Build();
app.UseFabrCoreServer();
// Or with options
app.UseFabrCoreServer(new FabrCoreServerOptions
{
AdditionalAssemblies = new List<Assembly> { typeof(MyAgent).Assembly }
});
Complete Startup Example
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:
builder.AddFabrCoreServer(new FabrCoreServerOptions
{
AdditionalAssemblies = new List<Assembly>
{
typeof(MyCustomAgent).Assembly,
typeof(MyPluginLibrary).Assembly
}
});
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
| Attribute | Target | Description |
|---|---|---|
[AgentAlias("name")] | Class | Registers a FabrCoreAgentProxy under one or more aliases |
[PluginAlias("name")] | Class | Registers an IFabrCorePlugin for agent use |
[ToolAlias("name")] | Method | Registers a static method as a callable tool |
All attributes support AllowMultiple = true, so a single component can have multiple aliases:
[AgentAlias("assistant")]
[AgentAlias("my-assistant")]
public class AssistantAgent : FabrCoreAgentProxy
{
// Reachable via either alias
}
Registry API
| Method | Returns | Description |
|---|---|---|
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:
| Method | Endpoint | Description |
|---|---|---|
GET | /fabrcoreapi/Discovery | Returns all registered agents, plugins, and tools |
{
"agents": [
{ "typeName": "MyApp.AssistantAgent", "aliases": ["assistant", "my-assistant"] }
],
"plugins": [
{ "typeName": "MyApp.WeatherPlugin", "aliases": ["weather"] }
],
"tools": [
{ "typeName": "MyApp.MathTools.Add", "aliases": ["add-numbers"] }
]
}
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
| Method | Endpoint | Description |
|---|---|---|
POST | /fabrapi/Agent/create | Create 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.
POST /fabrapi/Agent/create
x-user: user123
[{
"handle": "my-agent",
"agentType": "MyAgent",
"models": "default",
"systemPrompt": "You are a helpful assistant."
}]
Diagnostics API
| Method | Endpoint | Description |
|---|---|---|
GET | /fabrapi/Diagnostics/agents | List all registered agents |
GET | /fabrapi/Diagnostics/agents/{key} | Get specific agent info |
GET | /fabrapi/Diagnostics/agents/statistics | Agent count statistics |
POST | /fabrapi/Diagnostics/agents/purge | Purge deactivated agents |
File & Embeddings API
| Method | Endpoint | Description |
|---|---|---|
POST | /fabrapi/File/upload | Upload a file (multipart/form-data) |
GET | /fabrapi/File/{fileId} | Download a file |
GET | /fabrapi/File/{fileId}/info | Get file metadata |
POST | /fabrapi/Embeddings | Generate a vector embedding for a single text |
POST | /fabrcoreapi/embeddings/batch | Generate 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:
{
"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" }
]
}
{
"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
| Condition | Error (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." |
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
| Method | Endpoint | Description |
|---|---|---|
POST | /fabrcoreapi/ChatCompletion | Send 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
{
"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
{
"Text": "The extracted response text...",
"Model": "gpt-4o-mini",
"Usage": { "InputTokens": 150, "OutputTokens": 80 }
}
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.
// 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:
| Stream | Namespace | Purpose |
|---|---|---|
| AgentChat | AgentChat | Request/response messaging |
| AgentEvent | AgentEvent | Fire-and-forget events |
Background Services
| Service | Interval | Description |
|---|---|---|
FileCleanupBackgroundService | Configurable | Removes expired files and orphaned entries |
AgentRegistryCleanupService | Every 6 hours | Purges 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:
| Subsystem | Schema | Purpose |
|---|---|---|
| Clustering | orlns | Silo membership and liveness tables for cluster coordination |
| Persistence | orlns | Grain state storage tables for agent and client state |
| Reminders | orlns | Persistent reminder registration tables |
| Streaming | orlns | Pub/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.
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.
// appsettings.json — tables are auto-provisioned on first startup
{
"Orleans": {
"ClusterId": "prod",
"ServiceId": "fabrcore",
"ClusteringMode": "SqlServer",
"ConnectionString": "Server=localhost;Database=FabrCore;Trusted_Connection=True;"
}
}