akm docs

bunqueue — evaluation as a replacement/underpinning for akm's workflow engine

Repo: https://github.com/egeominotti/bunqueue · npm bunqueue · site https://bunqueue.dev Evaluated: 2026-08-01 against akm @ 0.9.0-rc.13 (MPL-2.0, Bun CLI). Version examined: 2.8.55 (repo HEAD, pushed 2026-08-01T22:10:18Z).


0. What bunqueue actually is

Two separable subsystems shipped in one package:

  1. A BullMQ-shaped job queue — SQLite/WAL persistence, priorities, delayed jobs, cron (croner), DLQ, rate limits, per-queue concurrency slots, FlowProducer (parent/child DAGs), stall detection, S3 backup, TCP/HTTP servers, MCP server (73 tools). This is the mature, heavily tested core.
  2. A workflow/saga engine (src/client/workflow/, 37 files) — a code DSL (.step().branch().parallel().forEach().waitFor().pivot()) layered on a Queue/Worker pair, with its own SQLite workflow_executions table.

akm's engine maps onto (2), not (1). The two do not compose: the workflow engine's fan-out primitives do not use the queue's concurrency slots (see §3).

Evidence: https://github.com/egeominotti/bunqueue/tree/main/src/client/workflow · https://raw.githubusercontent.com/egeominotti/bunqueue/main/docs/features/workflow-engine.md


1. Server requirement — PARTIAL (fails for akm)

Question Answer Evidence
Fully embedded? Yes, new Queue('q', { embedded: true, dataPath }) — no sockets, no daemon docs/architecture.md:222
Redis/Mongo/PG? No. Zero external infra; deps are only croner + msgpackr package.json (deps: croner@10.0.1, msgpackr@^1.11.8); README "Zero external infrastructure"
Storage bun:sqlite WAL, synchronous=NORMAL, busy_timeout=5000, 256MB mmap; omit dataPath → :memory: docs/features/persistence.md; EngineOptions.dataPath "omit → :memory: for store"

The disqualifier. Embedded mode is documented single-process-only:

"Trade-off: scoped to one process; multiple processes pointing at the same SQLite file is not supported for concurrent writers." — https://github.com/egeominotti/bunqueue/blob/main/docs/architecture.md (Deployment Modes (a), lines 222–228)

The authoritative queue state is the in-memory sharded index, hydrated from SQLite at startup and then served from RAM (docs/features/persistence.md, "What Stays In-Memory"). Two concurrent akm workflow run processes on the same DB would each own a divergent authoritative view.

akm is precisely a multi-process CLI: workflow_runs.engine_lease_holder / engine_lease_until (90s, claimable on expiry) and workflow_run_units.claim_holder / claim_expires_at exist because a second invocation can race the first (src/core/state/migrations.ts:881–955; src/workflows/exec/run-workflow.ts "Run lease" docblock). Also akm task run is invoked by cron/launchd/schtasks, so several runs can legitimately overlap (src/tasks/runner.ts:6).

To get multi-process safety from bunqueue you must run server mode (bunqueue start, TCP :6789 / HTTP :6790) — a resident daemon, which is exactly what akm's zero-daemon design refuses. Also note the workflow engine specifically: "No cross-process lock; within one executor, nodesInFlight holds a claim" (docs/features/workflow-engine.md, Concurrency & Idempotency) — even in server mode the workflow layer is not cross-process safe, because WorkflowStore opens a local bun:sqlite directly.

Verdict: embedded-yes, but single-process-only, which akm's model violates.


2. Durability & crash-resume — PARTIAL

Genuinely good, but weaker and less precise than what akm already has.

What's there

Where it's weaker than akm


3. Orchestration primitives — PARTIAL (the fan-out gap is fatal)

akm needs bunqueue workflow engine Evidence
DAG / flows Ordered node array, sequential cursor (currentNodeIndex); each advance() enqueues exactly one successor. Not a general DAG. docs/features/workflow-engine.md (Execution Model)
Conditional routing .branch(condition) + .path(name, builder) — condition is an arbitrary JS closure same
Fan-out with concurrency cap NO. .parallel(builder) is Promise.allSettled over statically declared sibling steps, unbounded, and .forEach(items, …) is strictly sequential (for (let i = 0; i < items.length; i++)) with only maxIterations (default 1000). ParallelDefinition has no concurrency field. src/client/workflow/loops.ts; src/client/workflow/stepTypes.ts
Result reduction NO aggregation. Loop results write exec.steps["name:i"] per iteration and exec.steps["name"] holds only the last iteration ("the documented contract for downstream steps"). No collect/vote reducer. src/client/workflow/loops.ts
Bounded retry + error classification retry is a plain number (default 3) + backoff w/ jitter. No error-type filter. akm has retry: { max, on: [failureReason] } over a structured failure vocabulary. src/client/workflow/stepTypes.ts (StepOptions.retry: number); src/workflows/exec/unit-dispatch.ts (failureReason)
Per-job timeouts Yes — per-step timeout (default 30_000ms), chunked timers bounded at 2^31−1 ms, aborts ctx.signal docs/features/workflow-engine.md
Beyond akm Saga compensate + pivot(), waitFor(event) HITL gates with transactional parking, subWorkflow, compensation-stuck operator state, 15 event types, injectable clock for deterministic tests same

The bounded-fan-out-with-reduction primitive is the single most load-bearing thing akm needs from an orchestrator (scheduleUnits + collect/vote reducers) and it is the one thing the workflow engine does not have. You can build it on the queue layer instead — FlowProducer.addBulkThen(parallel, final) gives N children + a reducer parent, with per-queue concurrency slots and getParentResults() (docs/features/flow-producer.md) — but that abandons the workflow DSL entirely and you re-hand-roll gates, routing, and step advance on top of raw jobs. Limits there: depth ≤100 edges, batch ≤10,000 jobs, 10MB/job, 64MB aggregate.


4. Long-running jobs (agent CLI spawns, minutes) — PARTIAL

akm's own lease model (90s run lease + per-unit claim_holder/claim_expires_at) is a closer fit and is already written.


5. Runtime & platform — PARTIAL


6. Maturity — PARTIAL / concerning

Metric Value Source
Stars / forks 525 / 17 api.github.com/repos/egeominotti/bunqueue
Created 2026-01-28 (≈6 months old) same
Last push 2026-08-01 (today) same
License MIT same; npm registry
Open issues 0 (11–12 closed) https://github.com/egeominotti/bunqueue/issues?q=is%3Aissue
npm versions 271 published in ~6 months (≈1.5/day), 2 majors, 19 minor lines (1.0→2.8) registry.npmjs.org/bunqueue
GitHub releases 36 pages of releases; latest v2.8.55 (2026-08-01), v2.8.54 same day /releases
npm maintainers 1 — kernelvoid egeominotti@gmail.com (same person as the GitHub owner) registry.npmjs.org/bunqueue
Commit authors egeominotti, kernelvoid (same human), plus claude as co-author /commits/main
Tests >100 test files, property tests (fast-check), model-based testing, Docker-isolated unit/TCP/embedded suites, benchmark policy /tree/main/test; docs/testing.md; ci.yml
Docs Excellent — 44 feature docs + architecture/data-model/protocol/testing + Astro Starlight site /tree/main/docs

Bus factor: 1. Test and doc quality are genuinely above average for a 6-month project; release cadence is too fast to be stable (271 versions, two majors in six months means the API has already broken once and minor lines turn over every ~10 days).

The closed-issue list is the most informative maturity signal — all reported by third parties in the last two months, all in exactly the correctness domains akm would be depending on:

They are fixed, and fixed fast — but concurrency-cap violations and lost completions in mid-2026 mean the invariants are still settling.


7. Integration sketch & % deletable

akm's engine surface: src/workflows/exec/ = 7,070 lines across 12 files; src/workflows/ir/ = 1,635 lines across 5 files. Total 8,705 lines.

Could plausibly be replaced

akm file Lines bunqueue equivalent Real?
exec/scheduler.ts 118 queue concurrency slots No — it's a 20-line policy layer over core/concurrent.ts composing 4 caps (map request ∧ frozen workflow cap ∧ frozen LLM engine cap ∧ live host CPU cap, reapplied at dispatch so a resume on a smaller machine re-clamps). bunqueue's workflow layer has no bounded fan-out at all (§3); the queue layer's slot model can't see the frozen caps.
retry/attempt bookkeeping in native-executor.ts ~120 StepOptions.retry: number No — akm needs retry.on: [failureReason] classification and per-attempt journal rows (<unitId>~r<n>) that bunqueue's single mutable attempts counter cannot express.
run lease ~60 worker lock/heartbeat No — bunqueue's workflow engine has no cross-process lock at all.

Must remain hand-rolled regardless

Estimate

≈0–3% of src/workflows/exec + src/workflows/ir is deletable (at absolute best scheduler.ts's 118 lines ≈ 1.4%, and even that would be a downgrade). The additive cost is large: a second SQLite DB, a timer-driven worker (poll() reschedules every 10ms) that must be explicitly close()d or it holds Bun's event loop open — directly against the "no leaked handles" contract akm documents in run-workflow.ts — and a single-process constraint akm cannot satisfy.

A hypothetical adoption degenerates to: one bunqueue .step() whose handler calls akm's existing executeStepPlan. At that point bunqueue contributes a job row and a retry counter.


8. Risks

Risk Severity Detail
Architecture mismatch Blocking Embedded = single-process; akm is a multi-process CLI with an explicit lease protocol. The alternative (server mode) reintroduces the daemon akm exists to avoid.
Capability gap Blocking No bounded-concurrency data fan-out, no result reduction, no error-classified retry — the three primitives akm's engine is built around.
Abandonment Medium 6 months old, bus factor 1, single npm maintainer. Very active now; no track record through a maintainer gap. MIT means a fork is always legal.
API instability High 271 npm versions in ~6 months, 2 majors, 19 minor lines. Pinning is mandatory; upgrades will be frequent and unbudgeted.
Correctness churn Medium-High Third-party-reported concurrency-cap violations, lost completions on lock expiry, unpersisted queue control state — all within the last two months.
Security Low (embedded) / Medium (server) Two TLS issues (unauthenticated crash; no server-cert verification) reported and fixed; both server-mode only. Embedded path adds no network surface. Supply chain is small: 2 runtime deps (croner, msgpackr) + 1 optional peer (@modelcontextprotocol/sdk).
Windows Medium Zero Windows CI. akm supports Windows via schtasks.
License None MIT consumed by MPL-2.0 is fine; MPL file-level copyleft is unaffected by a permissive dependency.

Recommendation

Do not adopt — neither as a replacement nor as an underpinning of src/workflows/exec + ir.

The blocker is not maturity, it is shape. bunqueue's workflow engine is a code-DSL saga engine for a single long-lived process; akm's is a frozen-data-plan executor for short-lived, potentially concurrent CLI invocations whose journal is a queryable first-class CLI surface. The two disagree on the process model (single vs multi), the plan representation (closures vs frozen IR), the resume key (step name vs input hash), and the fan-out primitive (sequential/unbounded vs bounded-with-reducer).

Worth stealing, not importing (all MIT, all cheap to reimplement):

  1. pivot() — an explicit no-rollback-past-here marker.
  2. compensation-stuck as a deliberately non-terminal state requiring operator action, with resumeCompensation() / abandonCompensation() — a better failure parking model than a plain failed.
  3. Injectable clock() covering timestamps, retry jitter, entropy and timers, with simulatedClock(seed) — turns a 1,794ms retry test into 66ms deterministically. akm's scheduler/retry tests would benefit directly.
  4. Decision logic as pure functions returning values (decideUnwindAction, decideAdmission) so admission/unwind are property-testable — akm already does this for computeStepWorkList; extending it to lease admission is the same idea.
  5. Two-phase stall detection (candidate-mark, then confirm on the next sweep) to avoid false-positive reclaims — relevant if akm ever shortens its 90s lease.

Where bunqueue would be a good fit for akm, if a need ever appears: as the backend for a genuinely daemonized akm service (a long-lived akm serve with a job queue for scheduled tasks), replacing the current cron/launchd/schtasks delegation in src/tasks/. That is a different product decision, and today's design deliberately rejects it.