Workhorse
Getting started

What is Workhorse?

A durable job queue that lives inside PostgreSQL, so your jobs commit with your data.

Every application eventually needs work that outlives a request: send the email, charge the card, rebuild the report. The moment that work moves to a separate broker, you have two systems that can disagree — an order row without its job, or a job for an order that rolled back.

Workhorse removes the second system. It is a durable job queue built from PostgreSQL tables and versioned SQL functions, with supported SDKs for TypeScript, Python, and Go. There is no broker, no Redis, and no PostgreSQL extension — one database owns your business data, your queued work, your execution state, and the evidence of what happened.

The core promise

Queue.enqueue accepts your open transaction as its last argument. If the transaction commits, the job exists; if it rolls back, PostgreSQL removes the business write and the job together.

import { Pool, Queue } from "@stablemates/workhorse";

const pool = new Pool({ connectionString: process.env.DATABASE_URL });
const queue = new Queue(pool);

const client = await pool.connect();
try {
  await client.query("BEGIN");
  await client.query("INSERT INTO orders (id) VALUES ($1)", [orderId]);
  await queue.enqueue("order.fulfill", { orderId }, {}, client);
  await client.query("COMMIT");
} finally {
  client.release();
}

A Worker claims the job and runs the handler you registered with Worker.handle. Each claim grants a temporary lease and a fence token. If the worker dies, the lease expires, another worker recovers the job, and the fence token blocks the dead worker from writing stale results.

Handlers run outside database transactions, so delivery is at least once: a handler can run twice after a crash. Named checkpoints make that safe. If a handler restarts, ctx.checkpoint replays each completed stage's stored result instead of running it again.

import { Worker } from "@stablemates/workhorse";

const worker = new Worker(queue).handle("order.fulfill", async (payload, ctx) => {
  const charge = await ctx.checkpoint("charge", () => chargeCard(payload.orderId));
  // If the process dies here, the retry skips "charge" and resumes below.
  const label = await ctx.checkpoint("label", () => printLabel(payload.orderId));
  return { chargeId: charge.chargeId, labelId: label.labelId };
});
await worker.run();

Beyond checkpoints, jobs get retries with configurable backoff, durable sleeps that release the worker slot, cron-style schedules, idempotent enqueue, dead letters with redrive, and immutable history you can query with Admin.getJob and Admin.getJobTimeline.

When to use it

If your application already runs on PostgreSQL and needs reliable background work, Workhorse fits. Choose its TypeScript, Python, or Go SDK and connect it to PostgreSQL 15 or newer.

Workhorse is a public beta. It is usable for evaluation and early production adoption, but any minor release may break compatibility, including the schema. There is no upgrade path between 0.x releases; ordered migrations begin at 1.0.0.

Workhorse supports strict priority, dependency fan-in, child jobs, external signals, and human decisions. Orchestration remains application code rather than a separate workflow definition. External calls still need provider idempotency keys. If the queue must scale independently of the database, use a dedicated broker.

Which package do I need?

  • @stablemates/workhorse provides Queue, Admin, Worker, schema tools, process orchestration, and the CLI.
  • stablemates-workhorse provides synchronous and asynchronous Python queue clients and workers.
  • github.com/stablemates/workhorse/go provides Go queue clients and workers over pgx or database/sql.
  • @stablemates/workhorse-dashboard provides the operator interface and its framework-neutral server host.
  • @stablemates/workhorse-drizzle adapts Drizzle databases and transactions to the core Queryable protocol.
  • @stablemates/workhorse-prisma adapts Prisma clients and interactive transactions.
  • @stablemates/workhorse-typeorm adapts TypeORM data sources and transactional entity managers.
  • @stablemates/workhorse-kysely adapts Kysely databases and transactions.

TypeScript applications start with @stablemates/workhorse, which includes its default PostgreSQL driver. Python includes Psycopg by default and offers an asyncpg extra, while Go includes pgx. TypeScript applications add an adapter when they want to enqueue inside an ORM transaction.

Next

  • Installation — add the package and install its schema
  • Quickstart — run a job, kill the worker, watch it finish anyway
  • Core concepts — states, ownership, and why a handler can run twice

Exact lifecycle guarantees, tables, and protocol boundaries: architecture reference.