@msm-core/jobs
v0.8.0
Published
Durable job/mission orchestration engine (CAS + idempotency state machine, cron missions, HITL approval). Pure decision logic; storage, queue, lock, clock injected via ports (no mongoose/bullmq/ioredis dependency).
Readme
@msm-core/jobs
Durable job / mission orchestration engine — a crash-safe state machine for long-running, resumable, human-gated agent work.
Pure decision logic over injected ports. The engine carries NO mongoose / bullmq /
ioredis dependency (only jsonwebtoken, for resume tokens). Storage, queue, lock, clock and
the step executor are supplied by the consumer via the interfaces in @msm-core/jobs/port —
exactly the way @msm-core/memory keeps persistence out of its core. A platform writes thin
adapters (e.g. Mongo + BullMQ + Redis) and gets durable jobs.
What it does
- CAS + idempotency state machine — version-guarded compare-and-swap on every transition; an append-only step timeline keyed by a unique idempotency key. Crash-safe and exactly-once.
- Wait + resume —
waiting_time(delayed resume) andwaiting_event(signed resume token, one-time-use nonce). A wait-reconciler re-enqueues due jobs as a durability backstop. - Pluggable workflows — declarative
WorkflowDefinitions (data, not code) resolve each step. - Pluggable step executor —
run_interaction_taskruns whatever the consumer wires (e.g. an LLM agent turn); pure transitions (wait_time/complete/ …) are no-ops. - HITL approval hook — a step can transition to
awaiting_approvaland resume on a decision.
Usage
import { createJobEngine, createWorkflowRegistry, DEFAULT_JOB_ENGINE_CONFIG } from "@msm-core/jobs";
import type { JobStore, QueuePort /* … */ } from "@msm-core/jobs/port";
const engine = createJobEngine({
jobStore, queue, lock, nonce, ops, clock, // your adapters (implement ./port)
registry: createWorkflowRegistry(definitions), // manifest-fed workflow definitions
stepExecutor, // run_interaction_task → your agent turn
config: { ...DEFAULT_JOB_ENGINE_CONFIG, resumeToken: { secret, ttlSeconds: 86400 } },
});
await engine.orchestrateJobStep({ jobId, tenantId, reason: "create" });
await engine.reconcileWaitingTimeJobs(); // periodic durability backstopThe ports (JobStore.claim / finalizeStep / insertStep, QueuePort, LockPort,
NoncePort, ClockPort, JobOpsPort, StepExecutorPort, WorkflowRegistry) are documented
in port.ts. JobStore.insertStep must throw DuplicateStepError on a repeated idempotency key
(the adapter maps its store's duplicate-key error onto it).
