SDK & Addons
OpenCaddis SDK & Addons
Build custom addons that extend OpenCaddis with new services, settings UI, and configuration — auto-discovered at startup with zero registration code.
The OpenCaddis SDK is in its initial release. The addon interfaces are stable and usable today, but we're actively expanding the surface area. Our goal is to build a full addon marketplace where the community can share and discover addons. Expect the SDK to grow significantly in the coming releases.
Overview
OpenCaddis has two extensibility layers:
| Layer | Package | Purpose | Interface |
|---|---|---|---|
| FabrCore Plugins | Fabr.Sdk |
Add tools (functions) that agents can call — web browsing, file access, API calls, etc. | IFabrPlugin |
| OpenCaddis Addons | OpenCaddis.Sdk |
Extend the application itself — register services, provide settings UI, store addon configuration. | ICaddisAddon |
This page covers OpenCaddis Addons — the application-level extensibility system. For adding agent tools, see the Plugins documentation.
How It Works
At startup, OpenCaddis scans all assemblies in the output directory for classes that:
- Have the
[CaddisAddon]attribute - Implement the
ICaddisAddoninterface
When a match is found, OpenCaddis instantiates the addon and calls ConfigureServices(), giving it full access to the DI container. No manual registration is needed — just drop your compiled DLL into the output directory and restart.
// Force-load assemblies from the output directory
foreach (var dll in Directory.GetFiles(AppContext.BaseDirectory, "*.dll"))
{
try { Assembly.LoadFrom(dll); } catch { }
}
foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
{
foreach (var type in assembly.GetTypes())
{
if (type.GetCustomAttribute<CaddisAddonAttribute>() is not null
&& typeof(ICaddisAddon).IsAssignableFrom(type))
{
var addon = (ICaddisAddon)Activator.CreateInstance(type)!;
addon.ConfigureServices(builder.Services);
}
}
}
SDK Interfaces
ICaddisAddon
The core interface every addon implements.
public interface ICaddisAddon
{
string Name { get; }
void ConfigureServices(IServiceCollection services);
Type? SettingsComponentType => null;
}
| Member | Description |
|---|---|
Name | Display name for the addon. Used in the UI and logs. |
ConfigureServices | Called at startup. Register your services into the DI container here. |
SettingsComponentType | Optional. Return a Blazor component Type to render addon-specific settings in the OpenCaddis UI. Defaults to null (no settings UI). |
CaddisAddonAttribute
Marks a class for auto-discovery. Required alongside ICaddisAddon.
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
public sealed class CaddisAddonAttribute : Attribute
{
public string Name { get; }
public CaddisAddonAttribute(string name) => Name = name?.Trim() ?? string.Empty;
}
ICaddisConfigService
Inject this service to read and write addon-specific configuration. Configuration is persisted to opencaddis.json as extension data alongside agent definitions.
public interface ICaddisConfigService
{
bool ConfigurationExists();
Task<T?> GetAddonConfigAsync<T>(string sectionName) where T : class;
Task SetAddonConfigAsync<T>(string sectionName, T config) where T : class;
}
| Method | Description |
|---|---|
ConfigurationExists() | Returns true if opencaddis.json exists on disk. |
GetAddonConfigAsync<T> | Reads a named section from the config file and deserializes it to T. Returns null if the section doesn't exist. |
SetAddonConfigAsync<T> | Serializes T and writes it as a named section in the config file. Creates the file if it doesn't exist. |
Building an Addon
1. Create the project
dotnet new classlib -n MyAddon -f net10.0
cd MyAddon
dotnet add reference ../OpenCaddis.Sdk/OpenCaddis.Sdk.csproj
2. Implement ICaddisAddon
using Microsoft.Extensions.DependencyInjection;
using OpenCaddis.Sdk;
[CaddisAddon("MyAddon")]
public class MyAddon : ICaddisAddon
{
public string Name => "My Custom Addon";
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<MyCustomService>();
}
// Optional: return a Blazor component for settings UI
public Type? SettingsComponentType => null;
}
3. Use configuration (optional)
If your addon needs persistent configuration, inject ICaddisConfigService into your services.
public class MyCustomService
{
private readonly ICaddisConfigService _config;
public MyCustomService(ICaddisConfigService config)
{
_config = config;
}
public async Task<MySettings> GetSettingsAsync()
{
return await _config.GetAddonConfigAsync<MySettings>("MyAddon")
?? new MySettings();
}
public async Task SaveSettingsAsync(MySettings settings)
{
await _config.SetAddonConfigAsync("MyAddon", settings);
}
}
public class MySettings
{
public string ApiEndpoint { get; set; } = "";
public bool Enabled { get; set; } = true;
}
The configuration is stored as a named section in opencaddis.json:
{
"Agents": [ ... ],
"MyAddon": {
"ApiEndpoint": "https://api.example.com",
"Enabled": true
}
}
4. Deploy
Build your addon and copy the output DLL (and any dependencies) into the OpenCaddis output directory. Restart OpenCaddis — the addon is discovered and registered automatically.
dotnet build -c Release
cp bin/Release/net10.0/MyAddon.dll /path/to/opencaddis/
Addons + FabrCore Plugins
Addons and FabrCore plugins work together. A common pattern is to create an addon that registers services, then create a FabrCore plugin that uses those services to provide agent tools.
// Addon: registers shared services at startup
[CaddisAddon("Weather")]
public class WeatherAddon : ICaddisAddon
{
public string Name => "Weather";
public void ConfigureServices(IServiceCollection services)
{
services.AddHttpClient<WeatherApiClient>();
}
}
// FabrCore Plugin: gives agents a weather tool
[PluginAlias("Weather")]
public class WeatherPlugin : IFabrPlugin
{
private WeatherApiClient _client = default!;
public Task InitializeAsync(
AgentConfiguration config, IServiceProvider sp)
{
_client = sp.GetRequiredService<WeatherApiClient>();
return Task.CompletedTask;
}
[Description("Get the current weather for a city")]
public async Task<string> GetWeather(
string city)
{
return await _client.GetForecastAsync(city);
}
}
When combining addons and plugins in a single assembly, both are discovered automatically — the addon via [CaddisAddon] and the plugin via [PluginAlias]. No extra wiring needed.
What's Coming
The SDK is in its initial release and we have plans to expand it significantly:
A community marketplace where developers can publish, discover, and install addons. Browse, install, and update addons directly from the OpenCaddis UI.
Publish OpenCaddis.Sdk as a NuGet package so addon authors can reference it without cloning the repo.
Beyond settings components — addons will be able to contribute sidebar panels, chat decorators, and custom pages to the OpenCaddis UI.
Security boundaries for addons — permission scoping, resource limits, and trust levels for community-published addons.
We want the addon ecosystem to be the primary way OpenCaddis grows. The built-in plugins demonstrate what's possible; addons let the community build what's needed.
Quick Reference
| Item | Details |
|---|---|
| SDK Project | OpenCaddis.Sdk |
| Target Framework | net10.0 |
| Framework Reference | Microsoft.AspNetCore.App |
| Addon Interface | ICaddisAddon |
| Discovery Attribute | [CaddisAddon("Name")] |
| Config Service | ICaddisConfigService |
| Config Storage | opencaddis.json (extension data) |
| Deployment | Copy DLL to output directory, restart |