Task Breakdown: Job-Runtime Provider Pluggability
Feature ID: job-runtime-providers
Status: Draft (not started)
Last updated: 2026-06-21
Spec: ./spec.md · Plan: ./plan.md · Providers: ./providers.md · Epic: EW-683
Ordered, granular tasks with explicit paths. Phases map to
plan.md§10. Each phase is a candidate sub-PR. Mark[x]as landed. Suggested Jira child-issue grouping noted per phase (e.g.[EW-683 T1–T6]).
CI matrix note (EW-742 T37 cross-cutting): the per-phase
JOB_RUNTIME={…}matrix axes mentioned in T12 / T17 / T21 / T26 / T29 are already live for the 4 non-Trigger providers via EW-742 P6 — mocks-only matrix acrossbullmq/pgboss/temporal/inngestplus real-infra coverage shipped in #1515 (BullMQ + pg-boss with Redis 7-alpine + Postgres 16-alpine service containers) and #1522 (Temporalauto-setup+ Inngest dev-server containers). The real-infra job is cost-gated to direct pushes tomainonly in #1529 (PRs + cascade branches skip it). When the per-provider EW-683 phases land their own conformance-green gates (T17 / T21 / T26 / T29), they extend the same matrix — no separate CI infra build-out required.
Phase 0 — Contract & seam (no behaviour change) · [EW-683 P0]
T1 + T2 ✅ Done in #1354 (cascaded #1360 -> #1362). T3 ✅ Done in #1369. T5 ✅ Done in #1368. T6 ✅ Done in #1370. T4 binding factory ✅ Done in #1376; T4 full cutover ✅ Done in #1513 (all 11 *_DISPATCHER symbols + NOTIFICATION_CHANNEL_DELIVERY_DISPATCHER now resolve via JOB_RUNTIME_PROVIDER_REGISTRY; the 3-symbol KB carve-out in works.module.ts was retired). EW-686 P1 (Trigger.dev re-housed as trigger provider plugin) ✅ Done in #1383 and #1510 (P2 plugin package). Phase 0 + Phase 1 complete.
- T1. Define the
job-runtimecapability viaIJobRuntimeProviderexported frompackages/plugin/src/contracts/capabilities/job-runtime.interface.ts(the existing capabilities barrel was the natural home alongside the other 25+ capability contracts; no separatejob-runtime.category.tsfile needed since capability strings are declared in JSDoc on the interface itself, matching howdns.interface.tshandles it). - T2. Defined
IJobRuntimeProvider,JobRunStatus,ScheduleSpec,JobEnqueueOptions,JobRuntimeDispatchers(plusJobRuntimeId,WorkerHostOptions,WorkerHostHandle) inpackages/plugin/src/contracts/capabilities/job-runtime.interface.ts. Run id naming: kept opaquestring(was originally going to decidetriggerRunIdvsruntimeRunId— punted, both shipping providers will produce strings that fit the existingtriggerRunIdcolumn without rename). - T3. Add
EVER_WORKS_JOB_RUNTIMEto config (packages/agent/src/config/index.ts):getActiveProviderId()default'trigger', validated literal-union, plusisExperimentalProvider()companion for the T6 startup-log warning path. Shipped in #1369. - T4. Binding factory
packages/agent/src/tasks/job-runtime.providers.ts:JOB_RUNTIME_PROVIDER_REGISTRYtoken +InMemoryJobRuntimeProviderRegistrydefault impl +buildJobRuntimeProviders()that binds all 11*_DISPATCHERsymbols to a per-callregistry.getActive()?.dispatchers ?? nulllookup. Binding factory shipped in #1376. Full cutover shipped in #1513:trigger.module.tsswapped the olduseExisting: TriggerServicebindings for...buildJobRuntimeProviders()with nosymbols:filter (all 11 flow through the registry); the 3-symbol KB carve-out (KB_NORMALIZE_MEDIA/KB_TRANSCRIBE/KB_REEMBED_WORK) inapps/api/src/works/works.module.tswas retired once the matchingTriggerService.dispatchXxxmethods landed;NOTIFICATION_CHANNEL_DELIVERY_DISPATCHERalso now resolves the active provider through the registry (custom adapter retained for the{ runId }envelope shape mismatch — Path 2). Tenant-aware overlay (EW-742 P3 / EW-747) extends the registry togetActive(tenantId?)without churning call sites. - T5. Amend Constitution Principle IV (
.specify/memory/constitution.md) → "via the configured job-runtime provider (Trigger.dev default)"; cross-link ADR-015. Shipped in #1368. - T6. Startup log: active runtime id +
experimentalwarning when non-default & not yet conformance-green. Shipped in #1370.
Phase 1 — Re-house Trigger.dev as the trigger provider · [EW-683 P1]
- T7. Scaffold
packages/plugins/job-runtime-trigger/(package.jsoneverworks.plugincategoryjob-runtime, tsup, vitest). - T8. Move
TriggerService(packages/tasks/src/trigger/trigger.service.ts) behindIJobRuntimeProviderin the new plugin (or adapter that wraps it).dispatchers= existing methods;cancel=runs.cancel;registerSchedules= existingschedules.task;startWorkerHost= no-op. - T9. Keep
packages/tasks/as the trigger worker package; wire it to the provider. Generalise/internal/trigger/*→/internal/jobs/*with alias routes;x-trigger-secret→x-internal-secretwith alias. - T10. Gate: existing Trigger.dev e2e suite (
apps/web/e2e/*trigger/worker tests) passes unchanged. No behaviour diff.
Phase 2 — Conformance harness · [EW-683 P2]
- T11. Provider-agnostic runtime conformance suite, e.g. at
packages/plugin/src/contracts/__tests__/job-runtime.conformance.spec.ts(parallel to the type-level shape spec atjob-runtime.spec.tsalready shipped in EW-685 P0): enqueue→run→status, idempotency (same key → one logical run), concurrency (same key serialises), cancel (in-flight aborts →CANCELLED), schedule fires, disabled→null+in-process fallback. - T12. Run harness green against
trigger. Add a CI matrix axisJOB_RUNTIME={trigger}(extend per provider in later phases).
Phase 3 — pg-boss provider (Postgres-native, GA-track) · [EW-683 P3]
- T13. Scaffold
packages/plugins/job-runtime-pgboss/; runtime deppg-boss; settings:PGBOSS_DATABASE_URL(defaults to platformDATABASE_URL),PGBOSS_SCHEMA(pgboss). - T14. Implement
dispatchers.*viaboss.send(name, payload, { id, singletonKey });cancel= cancel-flag +boss.cancel;getRunStatus=boss.getJobById;registerSchedules=boss.schedule(cron). - T15. Worker host:
boss.start()+boss.work(name, handler)running the agent orchestrator; worker entrypoint +pnpmscript. - T16. Cooperative cancel: reuse
throwIfGenerationCancelledcheckpoints against a cancel flag; tuneexpireInHoursfor multi-hour jobs. - T17. Compose profile
pgboss(no Redis, no SaaS); CI matrix axisJOB_RUNTIME=pgboss; conformance green. Mark GA-track.
Phase 4 — BullMQ provider (Redis) · [EW-683 P4]
- T18. Scaffold
packages/plugins/job-runtime-bullmq/; runtime depbullmq; settings:BULLMQ_REDIS_URL,BULLMQ_PREFIX,BULLMQ_CONCURRENCY. - T19. Implement
dispatchers.*viaqueue.add(name, payload, { jobId });registerSchedulesvia repeatable jobs /JobScheduler;getRunStatusviaJob.getState();cancelvia cancel-flag (+ remove if pending). - T20. Worker host:
Worker(name, handler, { concurrency, lockDuration, lockRenewTime })running the orchestrator; replicas; tune lock renewal for multi-hour jobs. - T21. Compose profile
bullmq(+ Redis service); CI matrixJOB_RUNTIME=bullmq; conformance green. Experimental→GA.
Phase 5 — Temporal provider (self-host / remote / Cloud) · [EW-683 P5]
- T22. Scaffold
packages/plugins/job-runtime-temporal/; runtime deps@temporalio/{client,worker,workflow,activity}; settings:TEMPORAL_ADDRESS,TEMPORAL_NAMESPACE,TEMPORAL_TASK_QUEUE,TEMPORAL_TLS_CERT/TEMPORAL_TLS_KEY(Cloud mTLS,x-secret). - T23. Express the generation/import orchestrators as Workflows; agent I/O (AI/search/git) as Activities (calling back over SuperJSON or in-process). Keep workflow code deterministic.
- T24.
dispatchers.*→client.workflow.start(..., { workflowId, taskQueue });cancel→handle.cancel();registerSchedules→ Schedules API;getRunStatus→DescribeWorkflowExecution. - T25. Worker host:
@temporalio/workerWorker bound to the task queue; deployment/sidecar +pnpmscript. - T26. CI:
temporal server start-devservice; matrixJOB_RUNTIME=temporal; conformance green. Experimental→GA.
Phase 6 — Inngest provider (SaaS only) · [EW-683 P6]
- T27. Scaffold
packages/plugins/job-runtime-inngest/; runtime depinngest; settings:INNGEST_EVENT_KEY,INNGEST_SIGNING_KEY(x-secret),INNGEST_APP_ID. No self-host path — doc SSPL rationale in plugin README +providers.md. - T28. Define functions with
createFunction; mountserve()HTTP handler (in API or small service) with signing-key verification;dispatchers.*→inngest.send({ name, data, id }); cron functions for schedules;cancelviacancelOn/REST. - T29. Address per-step limits: express long pipeline as steps where needed; document. CI with Inngest dev/mocked; conformance (push-model variant) green. Experimental.
Phase 7 — Docs, matrix & cleanup · [EW-683 P7]
- T30. Provider deploy guides under
docs/devops/(one per provider: env, worker host, compose/k8s, switch-runtime runbook + in-flight-drain note). - T31. Env reference: add
EVER_WORKS_JOB_RUNTIME+ per-provider vars todocs/environment-variables.md. - T32. Update canonical plugin count/category doc (
docs/plugin-system/built-in-plugins.md/plugin-categories.md) with thejob-runtimecategory + 5 providers (Principle VIII). - T33. k8s manifests: parameterise the worker deployment per active runtime (
.deploy/k8s/k8s-manifest.{dev,stage,prod}.yaml). - T34. Finalise GA vs experimental labels per
plan.md§10; flip CI gates accordingly.
Dependency notes
- T1–T6 block everything (the contract).
- T7–T10 (re-house trigger) block T11–T12 (conformance needs a reference provider).
- T11–T12 block every provider phase (each must pass the suite).
- Phases 3–6 are independent of each other and can parallelise after Phase 2.
- T30–T34 (docs/matrix) trail each provider as it lands.