LevelUp.Bifrost 0.5.0

dotnet add package LevelUp.Bifrost --version 0.5.0
                    
NuGet\Install-Package LevelUp.Bifrost -Version 0.5.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="LevelUp.Bifrost" Version="0.5.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="LevelUp.Bifrost" Version="0.5.0" />
                    
Directory.Packages.props
<PackageReference Include="LevelUp.Bifrost" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add LevelUp.Bifrost --version 0.5.0
                    
#r "nuget: LevelUp.Bifrost, 0.5.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package LevelUp.Bifrost@0.5.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=LevelUp.Bifrost&version=0.5.0
                    
Install as a Cake Addin
#tool nuget:?package=LevelUp.Bifrost&version=0.5.0
                    
Install as a Cake Tool

Bifrost

Build License .NET

A work-orchestration library for .NET 10.

Bifrost runs background work (such as emails, webhooks, and sandboxed jobs) on a pool of workers behind a bounded channel. You give it a work type and a handler; it owns admission, dispatch, retries, dead-lettering, autoscaling, and observability around them. It's built on System.Threading.Channels, trim- and AOT-compatible, offers a priority dispatch backed by a state-of-the-art, scalable concurrent priority queue, and is split into focused packages so you depend only on the parts you use.

How it works

Define a work type and a handler, register them, enqueue:

public record EmailJob(string To, string Subject, string Body);

public class EmailHandler(IEmailService email) : IWorkHandler<EmailJob>
{
    public ValueTask HandleAsync(EmailJob job, CancellationToken ct) =>
        email.SendAsync(job.To, job.Subject, job.Body, ct);
}

services.AddWorkOrchestrator<EmailJob>(o =>
{
    o.Capacity = 128;
    o.WorkerCount = 2;
})
.WithHandler<EmailHandler>();

Enqueue from anywhere you've injected IWorkOrchestrator<EmailJob>:

var result = await orchestrator.EnqueueAsync(new EmailJob(to, "Welcome", body));
if (!result.IsAccepted)
{
    // result.Reason is CapacityExceeded, WatermarkExceeded, or Shutdown.
    // A full queue is a return value, not an exception you have to catch.
}

What you compose on

Past the core queue, every capability is an opt-in decorator you add to the orchestrator.

  • Autoscaling. Workers scale on queue utilization between a floor and a ceiling, with high/low watermarks and a cooldown.
  • Resilience. Polly retry, timeout, and circuit breaker around each handler call.
  • Dead-letter queue. Work that exhausts its retries, or gets rejected at admission, lands in a DLQ instead of vanishing.
  • Health checks. ASP.NET Core health check integration.
  • OpenTelemetry. Queue-wait and rejection metrics, tagged by work class.
  • Event stream. Subscribe to enqueue, complete, and dead-letter events.
  • Priority dispatch. Class-aware ordering when interactive and batch work share a queue. Off by default; see below.
services.AddWorkOrchestrator<EmailJob>(/* ... */)
    .WithHandler<EmailHandler>()
    .WithAutoscaling(s => { s.MinWorkers = 1; s.MaxWorkers = 16; s.HighWatermark = 0.8; s.LowWatermark = 0.3; })
    .WithResilience(r => { r.RetryCount = 3; r.UseExponentialBackoff = true; })
    .WithHealthChecks()
    .WithOpenTelemetry();

Packages

Package What it is
LevelUp.Bifrost.Core Abstractions, zero dependencies
LevelUp.Bifrost The orchestrator and its decorators
LevelUp.Bifrost.Concurrency Concurrent priority queues (MultiQueue + locking), no Bifrost dependencies
LevelUp.Bifrost.HealthChecks ASP.NET Core health checks
LevelUp.Bifrost.OpenTelemetry Metrics
LevelUp.Bifrost.Resilience Polly integration
dotnet add package LevelUp.Bifrost

Priority dispatch

By default everything runs through one strict-FIFO queue. With some workloads, it is useful to reorder queue elements. Priority dispatch provides this functionality with class-aware ordering.

Measure. Tag work with a WorkClass (Interactive, Default, Batch) and enable OpenTelemetry. The bifrost.orchestrator.queue_wait histogram shows what each class actually waits.

services.AddWorkOrchestrator<SandboxJob>(/* ... */)
    .WithClassifier(job => job.UserInitiated ? WorkClass.Interactive : WorkClass.Batch)
    .WithOpenTelemetry();

Enable once a threshold you picked in advance gets crossed (say, interactive p95 queue-wait over 500 ms while batch work is co-resident with latency-sensitive work):

services.AddWorkOrchestrator<SandboxJob>(/* ... */).UsePriorityDispatch();

Priority dispatch orders the queue by a virtual-time key, so interactive work jumps ahead by at most a bounded window (default 30s), and anything that has waited longer than the window outranks fresh arrivals. That window is the starvation bound. Under pressure, admission sheds the lowest class first: Batch at 0.90× capacity, Default at 0.95, Interactive to full.

Two bindings ship, and Auto (the default) picks one at construction from hardware processor count and capacity:

Binding Ordering Built for
Locking Exact min-key under a global lock Few workers, longer work items, low contention
MultiQueue Relaxed two-choice, bounded rank error Many workers hammering numerous work items

When to stay on FIFO

  • You have no measured head-of-line problem. FIFO keeps producer-wait backpressure and exact ordering; don't trade those away for a problem you haven't seen.
  • Your producers can't handle rejection. Priority dispatch rejects at admission instead of blocking. If that doesn't work for you, even with DLQ routing, stay on FIFO.
  • You need strict ordering. Priority dispatch reorders by design, and MultiQueue relaxes ordering further within the priority order.
  • Your work doesn't run through IWorkOrchestrator. Bifrost earns its keep as a pipeline — resilience, DLQ, autoscaling, and observability around the queue. For standalone job scheduling, NCronJob or TickerQ fit better; the scheduling design doc covers the comparison.

The interface

public interface IWorkOrchestrator<TWork> : IAsyncDisposable
{
    // Admission outcomes are values, not exceptions. A rejected enqueue returns
    // EnqueueResult.Rejected(reason); only a canceled ct throws.
    ValueTask<EnqueueResult> EnqueueAsync(TWork work, WorkClass workClass = WorkClass.Default, CancellationToken ct = default);
    bool TryEnqueue(TWork work, WorkClass workClass = WorkClass.Default);

    void Run(TWork work, WorkClass workClass = WorkClass.Default);    // throws when full
    bool TryRun(TWork work, WorkClass workClass = WorkClass.Default); // false when full

    int PendingCount { get; }
    int ActiveWorkers { get; }
    int Capacity { get; }

    Task StopAsync(CancellationToken ct = default);
    Task DrainAsync(CancellationToken ct = default);
}

Handlers implement one method:

public interface IWorkHandler<TWork>
{
    ValueTask HandleAsync(TWork work, CancellationToken ct);
}

Build

git clone https://github.com/lvlup-sw/bifrost.git
cd bifrost
dotnet build src/Bifrost.sln -c Release
dotnet test --solution src/Bifrost.sln

Tests run on TUnit over the Microsoft Testing Platform, so they take --solution or --project rather than a bare dotnet test. Design notes live in docs/design and docs/designs.

License

Apache 2.0. See LICENSE.

Product Compatible and additional computed target framework versions.
.NET net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages (4)

Showing the top 4 NuGet packages that depend on LevelUp.Bifrost:

Package Downloads
LevelUp.Bifrost.HealthChecks

Channel-based work orchestration library with autoscaling, resilience, health checks, and OpenTelemetry support.

LevelUp.Bifrost.OpenTelemetry

Channel-based work orchestration library with autoscaling, resilience, health checks, and OpenTelemetry support.

LevelUp.Bifrost.Resilience

Channel-based work orchestration library with autoscaling, resilience, health checks, and OpenTelemetry support.

LevelUp.Bifrost.Scheduling

Channel-based work orchestration library with autoscaling, resilience, health checks, and OpenTelemetry support.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
0.5.0 48 6/16/2026
0.4.0 908 3/16/2026
0.3.5 139 3/15/2026
0.3.0 557 3/3/2026
0.2.0 130 3/3/2026
0.1.0 562 2/3/2026