Release 3 background workers for Dendra. They perform asynchronous short- and long-running tasks such as image generation, vendor data extracts, and integration orchestration. These tasks should not block API requests or the UI. Worker processes connect to NATS and run job types dispatched by the dendra-job-scheduler.
For system context, see System Design and the Job API guide. Platform services live in dendra-api-services; browser UIs in dendra-web-apps.
In dendra-api-services, the job scheduler plans and manages jobs over Connect-RPC. One-shot, continuous, queued, and recurring modes are defined in proto/job/dendra/job/scheduler/v1alpha1/service.proto. Workers only execute registered job types when the scheduler starts a run; they do not implement scheduling themselves.
Each job type should still be written for the mode used in production: one-shots finish promptly with a clear outcome. Continuous jobs run repeatedly on an interval and must honor StopJob and per-run timeouts (run_context.go). Queued jobs are one-shots the scheduler runs one at a time per queue to prevent race conditions.
Execution is over NATS: the job scheduler sends StartJob and StopJob and queries run status on worker subjects (e.g. dendra.v3.<workerType>.req.<namespace>.<jobType>.StartJob; see lib/go/jobworker/job_registry.go). Workers publish Online/Offline and job status events back to the scheduler. Control-plane subject layout: packages/go/job-scheduler/NATS.md (api-services repo). Data pipeline and extract-specific subjects: packages/go/extract/NATS.md.
Job scheduler --> NATS --> worker process --> job type
| Layer | Responsibility |
|---|---|
| Worker process | Connect to NATS, register job types, and report worker status. |
| Job type | Code for each specific job type under jobtypes/. |
Each binary has a worker type (backend, extract, load) used in its NATS subject prefix. On Online it announces the job types registered in that binary; the scheduler learns which worker type handles each job type from those events (EmitWorkerStatusEvent).
Many job types can run in one worker binary when they share the same process-level dependencies and scaling profile. Add a new binary under packages/go/<name>/ when isolation helps: native libraries (libvips), memory, deploy cadence, or data-pipeline usage.
This monorepo is where future workers are added: new package, main.go, job-type registry, and Makefile targets.
Variables for a job type are usually prefixed with the job type name in ALL CAPS (same identifier as in registry.go, no separators):
ExtractCampbellScientificLdmp → EXTRACTCAMPBELLSCIENTIFICLDMP_CHECKPOINT_RECORD_INTERVALGenerateLogoAvatarVariantsToMinio → GENERATELOGOAVATARVARIANTSTOMINIO_ACCESS_KEYHandlers typically resolve strings.ToUpper(jobType) + "_<SUFFIX>". Exceptions may exist for variables shared across related job types (e.g. IMAGEGEN_MAX_CONCURRENT_TRANSFORMS for all imagegen job types).
| Path | Role |
|---|---|
lib/go/jobworker | Job registry, NATS subscriptions, run lifecycle, status events |
lib/go/datapipeline | JetStream publish for integration data pipeline subjects |
lib/go/utils | NATS connect, API/scheduler client helpers |
lib/go/influxv1 | InfluxDB 1.x HTTP line-protocol writer, batching, mirror writes |
lib/go/authstrategies | API key auth for outbound RPC |
Protobuf types come from BSR (buf.build/gen/go/dendrascience/job/...); this repo does not host proto/ or release/ trees.
Binary dendra-backend-worker, source packages/go/backend, worker type backend, output output/dendra-backend-worker
Process notes: starts libvips; calls datapipeline.Setup for this binary.
| Job type | Area | Summary |
|---|---|---|
DiscoverAndSyncTables | jobtypes/orchestration | Table discovery and sync for integrations |
OrchestrateIntegration | jobtypes/orchestration/integration | Long-running integration orchestration; reconciliation checkpoints via dataintegration API |
SideEffectsSubscribeToChanges | jobtypes/sideeffects | Subscribe to platform change feed; enqueue queued side-effect handlers |
HandleOrganizationTeardown | jobtypes/sideeffects | Disable integrations when an organization is deleted or disabled |
GenerateLogoAvatarVariantsToMinio | jobtypes/imagegen | Logo/avatar image variants to MinIO/S3 |
GeneratePhotoVariantsToMinio | jobtypes/imagegen | Photo variants to MinIO/S3 |
ReverseSyncSubscribeToChanges | jobtypes/reversesync | Subscribe to Release 2 change feed; dispatches metadata jobs |
ReverseSyncVocabularyMetadata | jobtypes/reversesync | Vocabulary metadata sync |
ReverseSyncOrganizationMetadata | jobtypes/reversesync | Organization metadata sync |
ReverseSyncSiteMetadata | jobtypes/reversesync | Site metadata sync |
ReverseSyncStationMetadata | jobtypes/reversesync | Station metadata sync |
ReverseSyncThingTypeMetadata | jobtypes/reversesync | Thing type metadata sync |
ReverseSyncDatastreamMetadata | jobtypes/reversesync | Datastream metadata sync |
Job-type env (examples): GENERATELOGOAVATARVARIANTSTOMINIO_*, GENERATEPHOTOVARIANTSTOMINIO_*, REVERSESYNC_WEB_API_V2_*; shared IMAGEGEN_MAX_CONCURRENT_TRANSFORMS.
Binary dendra-extract-worker, source packages/go/extract, worker type extract, output output/dendra-extract-worker
Process notes: Campbell extract and prepare jobs call datapipeline.Setup in jobtypes/registry.go. Data-pipeline subjects: packages/go/extract/NATS.md.
| Job type | Area | Summary |
|---|---|---|
ExtractCampbellScientificLdmp | jobtypes/campbellsci | Campbell LDMP extract; integration checkpoints via API; publishes to data pipeline |
PrepareCampbellScientificLdmp | jobtypes/campbellsci | Consumes live raw.ok; resolves storage target; publishes loc-scoped live prep.ok / prep.err |
ExtractHoboTimeFrame | jobtypes/hobo | HOBO time-frame extract; optional JetStream KV on run context; legacy NATS Streaming publish |
ExtractLiCorCloudApi | jobtypes/licor | LI-COR Cloud extract; publishes ObservationBatch on parent live/test raw.ok |
PrepareLiCorCloudApi | jobtypes/licor | Consumes live raw.ok; resolves child logger storage; publishes loc-scoped live prep.ok |
DecodeStationWebhook | jobtypes/webhook | Consumes test+live raw.ok http-raw; publishes decode.ok records or decode.err |
PrepareStationWebhook | jobtypes/webhook | Live decode.ok only; time adjust, SafeFieldName, storage resolve; loc-scoped live prep.ok |
Job-type env (examples): EXTRACTCAMPBELLSCIENTIFICLDMP_*, PREPARECAMPBELLSCIENTIFICLDMP_*, EXTRACTHOBOTIMEFRAME_*; optional TOMLFILEEXTRACTHOBOTIMEFRAME_* (and similar) for TOML provider job secrets.
Binary dendra-load-worker, source packages/go/load, worker type load, output output/dendra-load-worker
Process notes: LiveLoadInfluxV1 calls datapipeline.Setup in jobtypes/registry.go. Mirror URLs are per deployment location via INFLUX_{LOC}_URLS.
| Job type | Area | Summary |
|---|---|---|
LiveLoadInfluxV1 | jobtypes | Consumes live prep.ok for a location; batch-writes to Influx 1.x mirrors; NAK on failure |
Job-type env (examples): LIVELOADINFLUXV1_*, INFLUX_UCNRS_URLS, INFLUX_UCNRS_COERCE_NUMERIC_FLOAT
Binary dendra-monitor-worker, source packages/go/monitor, worker type monitor, output output/dendra-monitor-worker
Subscribes to platform change events (dendra.v3.changes.>), evaluates membership monitoring rules, reports alerts, and sends immediate transactional email notifications.
| Job type | Area | Summary |
|---|---|---|
MonitorSubscribeToChanges | jobtypes/monitor | Change feed subscriber; membership alert + notify pipeline |
Env: MONITOR_RESEND_API_KEY, MONITOR_RESEND_FROM_EMAIL, MONITOR_EMAIL_RATE_LIMIT, MONITOR_EMAIL_MAX_RETRIES
Worker API key permissions (platform worker key used by monitor-worker):
admin:organizationreport:alertcreate:notification_delivery, update:notification_deliveryread:notification_preference, read:user_notification_configread:monitoring_rule, read:notification_templateDocs (under packages/go/monitor/jobtypes/monitor/):
| Doc | Purpose |
|---|---|
IMMEDIATE.md | Issues to clear before/during MVP + proposed fixes |
TESTING.md | MVP release test guidance |
FOLLOWUPS.md | Post-MVP design (JetStream, other planes, digests). Tasks: api-services docs/FOLLOWUPS.md |
Platform design: dendra-api-services/docs/MONITORING.md.
Before testing: see TESTING.md — seed membership rules (or CreateDefaultMonitoringRules), load notification templates via dendra-pytools/migration/notification_templates, verify Resend sender domain (or use mock email when unset).
Add packages/go/<name>/ with main.go, register job types in jobtypes/registry.go, and extend the Makefile with build-<name> / run-<name> targets following backend, extract, and load.
.
├── lib/go/
│ ├── jobworker/ # NATS job control, registry, runs
│ ├── datapipeline/ # Integration data JetStream publish
│ ├── utils/ # NATS, API clients
│ ├── authstrategies/ # Worker API keys
│ └── notify/ # Outbound channel senders (email/Resend)
├── packages/go/
│ ├── backend/ # dendra-backend-worker
│ ├── extract/ # dendra-extract-worker
│ ├── load/ # dendra-load-worker
│ └── monitor/ # dendra-monitor-worker
├── docker/ # backend, extract, load, monitor, combined Dockerfiles
├── output/ # Built binaries
├── sample.env
├── Makefile
└── go.work # Go 1.26 workspace
go.work (toolchain go1.26)make check)DATA_PIPELINE_NATS_SERVERS when using the data pipeline)make run-api and make run-job-scheduler with matching JOB_SCHEDULER_NAMESPACE and worker API key hashes (APIKEY_WORKER_API_SERVER_HASH, APIKEY_WORKER_JOB_SCHEDULER_HASH)cp sample.env .env
# Edit .env: NATS_SERVERS, URLs, WORKER_*_KEY, namespace, job-type blocks as needed
make
In dendra-api-services (separate repository):
cp sample.env .env
# Configure NATS, connectors, auth, etc.
make
make run-api
make run-job-scheduler
Generate worker API keys on the API server (logged to stdout), then set hashes in api-services .env and keys in workers .env:
# In dendra-api-services, after build:
./output/dendra-api-server -init-action=keygen
# APIKEY_WORKER_API_SERVER_HASH=... <-> WORKER_API_SERVER_KEY=...
# APIKEY_WORKER_JOB_SCHEDULER_HASH=... <-> WORKER_JOB_SCHEDULER_KEY=...
Run workers (one or more):
make run-backend # output/dendra-backend-worker
make run-extract # output/dendra-extract-worker
make run-load # output/dendra-load-worker
make run-monitor # output/dendra-monitor-worker
| Target | Action |
|---|---|
make / make main | check + build all workers to output/ |
make check | gofmt, staticcheck, go vet on lib/go, backend, extract, load |
make build-backend | output/dendra-backend-worker |
make build-extract | output/dendra-extract-worker |
make build-load | output/dendra-load-worker |
make run-backend / make run-extract / make run-load | Build and run |
make test-backend / make test-extract / make test-load | go test in each package tree |
make tidy | go mod tidy on all modules |
Run make check before opening a pull request.
.envCopy from sample.env. Loaded via godotenv in each worker main package.
Worker (process): required for any binary:
| Variable | Purpose |
|---|---|
API_SERVER_URL | Connect-RPC base URL (e.g. http://localhost:8080/rpc) |
WORKER_API_SERVER_KEY | Worker API key (den_ak_wkr_...) |
JOB_SCHEDULER_URL | Job scheduler RPC (e.g. http://localhost:8081/rpc) |
WORKER_JOB_SCHEDULER_KEY | Worker job-scheduler key (den_jk_wkr_...) |
JOB_SCHEDULER_NAMESPACE | NATS namespace (default default; must match scheduler) |
NATS_SERVERS | Control-plane NATS for job/worker subjects |
DATA_PIPELINE_NATS_SERVERS | NATS for integration data pipeline (if binary uses datapipeline) |
DATA_PIPELINE_* | JetStream limits, publish retry tuning, and DATA_PIPELINE_VERBOSE |
Job types: optional blocks in sample.env under each job-type prefix. Enable only what you run.
Job and worker messages are defined in the Buf Schema Registry module dendrascience/job. This repo imports generated Go from buf.build/gen/go/dendrascience/job/... (worker API under dendra/job/worker/v1alpha1). Scheduler protos and RPCs live in dendra-api-services (proto/job).
After schema changes in api-services, publish with buf push and bump dependencies in this repo.
Add a job type to an existing worker:
packages/go/<worker>/jobtypes/.jobtypes/registry.go (job type string must match scheduler / jobs.toml).strings.ToUpper(jobType) + "_<SUFFIX>" unless shared with related types.sample.env under the ALL CAPS prefix.Add a worker binary:
packages/go/<name>/ with main.go (copy pattern from backend or extract).build-<name> / run-<name> targets.Data pipeline (extract): packages/go/extract/NATS.md.
| Dockerfile | Contents |
|---|---|
docker/backend.dockerfile | dendra-backend-worker |
docker/extract.dockerfile | dendra-extract-worker |
docker/load.dockerfile | dendra-load-worker |
docker/monitor.dockerfile | dendra-monitor-worker |
docker/combined.dockerfile | All four worker binaries |
Images expect the same .env / environment configuration as local runs. NATS and api-services are not included in the image.
| Resource | Link |
|---|---|
| API services (scheduler, API server) | https://github.com/DendraScience/dendra-api-services |
| Web apps | https://github.com/DendraScience/dendra-web-apps |
| System design | https://docs.dendra.science/technical/system-design/ |
| Job API guide | https://docs.dendra.science/technical/apis/release-3-modules/job-api/ |
| RPC API docs | https://rpc-api-docs.dendra.science/ |
| Buf Schema Registry | https://buf.build/dendrascience/ |
| Job scheduler NATS subjects | api-services NATS.md |
| Extract worker NATS subjects | packages/go/extract/NATS.md |
Content type
Image
Digest
sha256:9c1013481…
Size
42.3 MB
Last updated
about 14 hours ago
docker pull dendra/dendra-job-workers