@happyvertical/smrt-jobs
Persistent background work, schedules, retries, runners, queue controls, and operational visibility.
What it gives you
Persistent background work, schedules, retries, runners, queue controls, and operational visibility.
- 01An opt-in allowlist for the methods a job row may call
- 02A per-tenant ceiling on in-flight jobs
- 03Retry counts clamped rather than trusted
- 04Automatic retention for terminal jobs and job events
Build a job before you enqueue it
The fluent builder composes delay(), priority(), retries(), queue(), and timeout() before enqueue(); bg() is the short form for background work. Delivery is at least once. timeoutBehavior chooses fail, warn, or kill, so idempotency belongs in the job method.
Schedules use local wall-clock time
ScheduleRunner fires forward once: cron matching uses local time, is not timezone-aware, and does not backfill missed runs. Worker liveness uses a process-global live set, an off-loop ticker, and lease recovery. ForgeDeliveryCollection and ForgeProjectionRuntime provide durable projections and dead-letter replay.
Say which methods a job may call
A persisted job row names an object type and a method, so the runner must know which methods are reachable. Mark a method with the backgroundEligible decorator. For non-decorator code, call markBackgroundEligible. These operations add an allowlist to the class. A class that marks nothing keeps the default behavior. After one method is marked, the runner refuses every method outside the list.
One tenant cannot drain the queue
Enqueueing counts the non-terminal jobs for a tenant and refuses another job above the limit. The default limit is ten thousand, and each call or job builder can change it. A value of zero disables the check. Jobs without a tenant do not count against the limit. Requested retry counts are clamped to the supported maximum. Thus, a misconfigured caller cannot pin a worker on a poison job.
Terminal outcomes commit with their event
Completion, permanent failure, timeout, stale-worker recovery, and cancel update the job row and append its versioned event in one transaction. If either write fails, neither commits. listTerminalOutcomes on the event collection requires an explicit tenant, reads newest first, and returns only the safe terminal shape without arguments, results, or raw errors.
Retention starts after the runner is stable
TaskRunner schedules its first retention sweep one interval after start, never at startup. The default policy keeps completed or canceled jobs for seven days. It keeps failed jobs and events for thirty days. Dry-run cleanup is available before a deletion policy changes.
Package UI
Components in this package
Import these public components directly. When the package also exports playground entries, the next tab renders its real components with package-owned example data.
Package previews
Try the working views
These previews are owned by the package and use its public components. Mock mode is intended for safe browsing; live mode appears only where the package supports it.
Generated interface
REST
The exact route set comes from each model's @smrt({ api }) policy
and the application manifest—not from a second controller definition.
GET /api/jobs
GET /api/jobs/:id
POST /api/jobs
PATCH /api/jobs/:idWhat stays consistent
- Tenant scope and operation permissions run before model access.
- OpenAPI and generated clients use the same manifest fields.
- Packages can expose read-only or custom action sets.
Generated interface
MCP
AI tools are generated from the methods you explicitly include. Their inputs are validated against the same model metadata used by REST and CLI.
jobs_list({ where, limit, orderBy })
jobs_get({ id })
jobs_create({ data })
jobs_update({ id, data })Agent-safe by construction
- Tool availability can be narrower than the HTTP API.
- Principal execution intersects RBAC, agent ceilings, and persona tools.
- Descriptions and schemas are emitted from source metadata.
Browser-native agent interface
WebMCP
The generated web definitions carry the same tool names, descriptions, and JSON Schema
inputs as MCP. smrt-web registers them with document.modelContext.
import { webMcpToolDefinitions } from '@happyvertical/smrt-virt-web';
import { registerWebMcpTools } from '@happyvertical/smrt-web';
// Omitting an exposure policy registers read-only tools only.
const dispose = registerWebMcpTools(webMcpToolDefinitions);The page keeps its security boundary
- Tools execute through generated REST fetchers as the signed-in page user.
- Authentication, tenant gates, permissions, and field policy stay server-side.
- Feature detection makes registration a safe no-op outside supported browsers.
Generated interface
CLI
The app CLI turns manifest resources into predictable operator commands while keeping package-specific actions close to their models.
smrt jobs list --limit 25 --order-by created_at:desc
smrt jobs get <id>
smrt jobs create --from-file ./record.jsonMade for automation
- Machine-readable output and file-based input.
- The same include/exclude policy as other surfaces.
- Migration, introspection, dispatch, and knowledge commands stay in smrt-cli.