| name | planforge-new-worker |
| description | Scaffold a background worker using BackgroundService with PeriodicTimer, structured logging, graceful shutdown, and health checks. |
| metadata | {"author":"plan-forge","source":".github/prompts/new-worker.prompt.md"} |
description: "Scaffold a background worker using BackgroundService with PeriodicTimer, structured logging, graceful shutdown, and health checks."
agent: "agent"
tools: [read, edit, search]
Create New Background Worker
Scaffold a hosted background service following .NET patterns.
Required Pattern
public sealed partial class {Name}Worker(
IServiceScopeFactory scopeFactory,
ILogger<{Name}Worker> logger) : BackgroundService
{
private static readonly TimeSpan Interval = TimeSpan.FromMinutes(5);
[LoggerMessage(Level = LogLevel.Information, Message = "{Name}Worker started โ interval {Interval}")]
partial void LogStarted(TimeSpan interval);
[LoggerMessage(Level = LogLevel.Error, Message = "{Name}Worker iteration failed")]
partial void LogIterationFailed(Exception ex);
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
LogStarted(Interval);
using var timer = new PeriodicTimer(Interval);
while (await timer.WaitForNextTickAsync(stoppingToken))
{
try
{
await using var scope = scopeFactory.CreateAsyncScope();
var service = scope.ServiceProvider.GetRequiredService<I{Name}Service>();
await service.ProcessAsync(stoppingToken);
}
catch (OperationCanceledException) when (stoppingToken.IsCancellationRequested)
{
break;
}
catch (Exception ex)
{
LogIterationFailed(ex);
}
}
}
}
Registration
builder.Services.AddHostedService<{Name}Worker>();
builder.Services.AddScoped<I{Name}Service, {Name}Service>();
Health Check Integration
public class {Name}HealthCheck() : IHealthCheck
{
public Task<HealthCheckResult> CheckHealthAsync(
HealthCheckContext context, CancellationToken ct = default)
{
var lastRun = ;
var isHealthy = DateTime.UtcNow - lastRun < TimeSpan.FromMinutes(15);
return Task.FromResult(isHealthy
? HealthCheckResult.Healthy($"Last run: {lastRun}")
: HealthCheckResult.Unhealthy($"Last run: {lastRun}"));
}
}
Rules
- Use
PeriodicTimer (not Task.Delay) for interval-based work
- Create a new DI scope per iteration (
IServiceScopeFactory)
- Never let exceptions kill the worker โ catch and log
- Respect
CancellationToken for graceful shutdown
- Add a health check so orchestrators know the worker is alive
- Use source-generated logging for hot-path messages
Reference Files