The job queue
Punk::Queue is a Minion-class
job queue: named queues, priorities, retries with backoff, cron, locks,
worker broadcast, and an admin UI. As a plugin it installs keywords of
its own beside Punk's - queue, task, cron - through the same
mechanism any plugin can use.
package MyApp;
use Punk;
use Punk::Plugin::Queue; # compile time: the keywords
plugin 'Queue' => { # runtime: the configuration
dsn => 'dbi:Pg:dbname=myapp',
admin => { prefix => '/queue', guard => 'Web::Auth#admin' },
};
queue 'mail' => { attempts => 5, timeout => 30 };
task 'mail.send' => 'Job::Mail#send';
task 'report.build' => sub {
my ($job, $year) = @_;
return { pages => 3 }; # becomes the job's result
};
cron '0 3 * * *' => 'Reports#nightly';
post '/reports' => sub {
my ($c) = @_;
return $c->json({ job => $c->enqueue('report.build' => [2026]) });
};
Workers
punk-queue worker -q default,mail -j 4 --app app.pl
The supervisor forks and watches -j workers; each dequeues, performs
and records. Workers answer broadcasts (stop, pause) and heartbeat, so
the admin UI can see and manage them. SQLite and Pg backends share one
conformance-tested behaviour.
Retries and state
A failing job retries with backoff up to its queue's attempts; the
transitions - inactive, active, finished, failed - are recorded
with timestamps, and results and errors are kept per attempt. Locks
provide mutual exclusion across workers:
$q->lock('nightly-report', 3600) or return;
Funky, the admin UI
The admin block mounts Funky under a guard you control - the pages
live under the same scope machinery as your own routes.

Overview, jobs (filterable, searchable, bulk retry/remove), a per-job detail view, workers, locks and crons - with live updates over a WebSocket when the server supports it.

Funky is zero-dependency and no-build - it ships as static assets with the plugin, themable, and its templates can be overridden per page to brand it as your own.
Standalone
The queue works without the web tier too:
use Punk::Queue;
my $q = Punk::Queue->new(dsn => 'dbi:SQLite:dbname=queue.db');
$q->migrate;
$q->task(add => sub { my ($job, $a, $b) = @_; $a + $b });
my $id = $q->enqueue(add => [2, 3], priority => 5);